Based on a question I asked on SO, I have come up with the following solution. As I am very new to Python Qt, I would like a review rather than just add it as an answer in SO.
The code runs a script in another thread. That script needs to open a file dialog to get another file from the user. To do that, I pass a signal to the script which is connected to run_cb()
in the GUI thread. run_cb()
will run any function given to it (callback).
The script emits this signal and passes the callback function it wants to run on the GUI thread. Inside the callback (getFileName_cb()
), I fire another signal that returns the result of the callback to the script thread.
In this way, the script doesn't need to know anything about the GUI (which is one of my requirements). It just needs to know that the signal passed to it can be used to run a function on the GUI thread.
Whilst it works, I am not sure if it the correct way to do this?
Edit: I recognised that in my first implementation, got_file_and_continue()
was now running in the GUI thread. So I've now changed it to wait for the callback to be done and continue in the original script thread. Although, I don't like the while
loop - would appreciate any comments on this too.
import threading
import time
import sys
from PySide2.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QPushButton, QApplication, QFileDialog
from PySide2 import QtCore
class script_runner(QtCore.QObject):
get_file_cb_signal = QtCore.Signal(object)
def __init__(self) -> None:
super().__init__()
self.get_file_cb_signal.connect(self.got_file_and_continue)
self.cb_done = None
self.filename = None
def getFileName_cb(self):
response = QFileDialog.getOpenFileName(
parent=None
)
self.get_file_cb_signal.emit(response[0])
return response[0]
@QtCore.Slot()
def got_file_and_continue(self, filepth):
print("Got_f_and_c:", threading.get_ident())
print("Finally", filepth)
self.cb_done = True
self.filename = filepth
def run_script_in_bg(self, script_pth, cb_signal):
# Assume this opens script_pth
# and that script needs to get user to choose another file
print(f"{script_pth} is running. Now it needs to open file dialog and get result")
self.cb_done = False
print("BG_task:", threading.get_ident())
cb_signal.emit(self.getFileName_cb)
while not self.cb_done:
time.sleep(0.1)
print("Moving on with:", self.filename, threading.get_ident())
class TextEditDemo(QWidget):
run_cb_signal = QtCore.Signal(object)
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("TEST")
self.resize(600,540)
self.textEdit = QTextEdit()
self.btnPress1 = QPushButton("Run")
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
layout.addWidget(self.btnPress1)
self.setLayout(layout)
self.btnPress1.clicked.connect(self.btnPress1_Clicked)
self.run_cb_signal.connect(self.run_cb)
self.sr = script_runner()
def btnPress1_Clicked(self):
script_pth = self.getFileName()
print("Main:", threading.get_ident())
if script_pth:
print('number of current threads is ', threading.active_count())
threading.Thread(target=self.sr.run_script_in_bg, args=(script_pth, self.run_cb_signal, )).start()
@QtCore.Slot()
def run_cb(self, cb):
print("Run cb", threading.get_ident())
try:
resp = cb()
self.textEdit.append("Script using: " + resp)
except Exception as e:
print(e)
def getFileName(self):
response = QFileDialog.getOpenFileName(
parent=None
)
self.textEdit.append("Calling script: " + response[0])
return response[0]
def main():
app = QApplication(sys.argv)
win = TextEditDemo()
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()