Manthri Anvesh
Manthri Anvesh

Reputation: 95

Showing QInputDialog and other gui objects from various threads

I have a websocket server running in python, and for every new connection a new thread will be created and the requests will be served.

In the main-thread [Gui-thread],i am initialing QApplication([]). the use case is, when i process the request i wanted to wait and get text response from the user through QInputDialog. when ever i run it, there is a event-loop running but is not showing the gui. because all the gui elements can be displayed from Gui-thread itself.

I have tried various approaches using QSignals/slots and Pypubsub but unable to achieve what is required. please do suggest some idea to get the use-case done. a pseudo-code is well appreciated.

Below mentioned code are some examples i tried. i am using thread in below examples because, as i mentioned each request from a connection is executed with the thread assigned to the connection. and the text from QInputDialog is required by the thread.

thanks in advance.

below is the websockets server code which serves the request calling server_extentions function, i have to show QInputDialog everytime i get a incoming request.

import websockets
import asyncio
from PyQt5.QtWidgets import QInputDialog, QApplication

app = QApplication([])

async def server_extentions(websocket, path):
    try:
        while(True):
            request = await websocket.recv()

            # this is where i need to show input dialog.
            text, ok = QInputDialog.getText(None, "Incoming message", request)
            if ok:
                response = text
            else:
                response = "NO REPLY"

            await websocket.send(response)
    except websockets.ConnectionClosed as exp:
        print("connection closed.")


start_server = websockets.serve(server_extentions, '127.0.0.1', 5588)
loop = asyncio.get_event_loop()

try:
    loop.run_until_complete(start_server)
    loop.run_forever()
finally:
    loop.run_until_complete(loop.shutdown_asyncgens())
    loop.close()


----edit-----

Below is some general idea, i tried using pypubsub.

import threading
import pubsub.pub
from PyQt5.QtWidgets import QInputDialog, QApplication


class MainThread:

    def __init__(self):
        self.app = QApplication([])
        pubsub.pub.subscribe(self.pub_callback, "lala")

    def pub_callback(self):
        print("this is Main thread's pub callback.")
        QInputDialog.getText(None, "main-thread", "lala call back : ")

    def start_thread(self):
        self.th = threading.Thread(target=self.thread_proc)
        self.th.start()

    def thread_proc(self):
        pubsub.pub.sendMessage("lala")


m = MainThread()
m.start_thread()

-----edit 2 -------

below is something i tried with QSignal. [check the comment in the code, How to call a function with Mainthread].

import threading
from PyQt5.QtWidgets import QInputDialog, QApplication
from PyQt5.QtCore import pyqtSignal, QObject, QThread


class TextDialog(QObject):
    sig = pyqtSignal(str)

    def __init__(self):
        QObject.__init__(self)

    def get_text(self):
        print("class Thread2, showing QInputDialog.")
        text, ok = QInputDialog.getText(None, "Lala", "give me some text : ")
        if ok:
            self.sig.emit(text)
            return 
        self.sig.emit("NO TEXT")
        return 


class Thread1:

    def thread_proc(self):
        td = TextDialog()
        td.sig.connect(self.get_text_callback)
        td.moveToThread(m.main_thread)
        # here i dont understand how to invoke MainThread's show_dialog with main thread. [GUI Thread]
        #m.show_dialog(td)


    def get_text_callback(self, txt):
        print("this is get_text_callback, input : " + str(txt))

class MainThread:

    def __init__(self):
        self.app = QApplication([])
        self.main_thread = QThread.currentThread()

    def main_proc(self):
        th1 = Thread1()
        th = threading.Thread(target=th1.thread_proc)
        th.start()

    def show_dialog(self, text_dialog: TextDialog):
        print("got a call to MainThread's show_dialog.")
        text_dialog.get_text()

m = MainThread()
m.main_proc()

exit()

Upvotes: 1

Views: 460

Answers (1)

eyllanesc
eyllanesc

Reputation: 243955

For this type of applications it is better to implement the worker-thread approach. This approach the main idea is to implement QObjects, move them to a new thread and invoke the slots asynchronously (through QEvents, pyqtSignals, QTimer.singleShot(...), QMetaObject::invokeMethod(...), etc) so that the tasks are executed in the thread that Live the QObject.

import threading
from functools import partial
from PyQt5 import QtCore, QtWidgets


class TextDialog(QtCore.QObject):
    sig = QtCore.pyqtSignal(str)

    @QtCore.pyqtSlot()
    def get_text(self):
        print("class Thread2, showing QInputDialog.")
        text, ok = QtWidgets.QInputDialog.getText(
            None, "Lala", "give me some text : "
        )
        if ok:
            self.sig.emit(text)
            return
        self.sig.emit("NO TEXT")
        return


class Worker1(QtCore.QObject):
    @QtCore.pyqtSlot(QtCore.QObject)
    def thread_proc(self, manager):
        print(
            "current: {}- main: {}".format(
                threading.current_thread(), threading.main_thread()
            )
        )
        manager.td.sig.connect(self.get_text_callback)
        QtCore.QTimer.singleShot(0, manager.show_dialog)

    @QtCore.pyqtSlot(str)
    def get_text_callback(self, txt):
        print(
            "current: {}- main: {}".format(
                threading.current_thread(), threading.main_thread()
            )
        )
        print("this is get_text_callback, input : %s" % (txt,))


class Manager(QtCore.QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.td = TextDialog()

    @QtCore.pyqtSlot()
    def show_dialog(self):
        print("got a call to MainThread's show_dialog.")
        self.td.get_text()


class Application:
    def __init__(self):
        print(
            "current: {}- main: {}".format(
                threading.current_thread(), threading.main_thread()
            )
        )
        self.app = QtWidgets.QApplication([])
        # By default if after opening a window all the windows are closed
        # the application will be terminated, in this case after opening
        # and closing the QInputDialog the application will be closed avoiding 
        # that it will be noticed that get_text_callback is called, 
        # to avoid the above it is deactivated behavior.
        self.app.setQuitOnLastWindowClosed(False)
        self.manager = Manager()

    def main_proc(self):
        #
        self.thread = QtCore.QThread()
        self.thread.start()
        self.worker = Worker1()
        # move the worker so that it lives in the thread that handles the QThread
        self.worker.moveToThread(self.thread)
        # calling function asynchronously
        # will cause the function to run on the worker's thread
        QtCore.QTimer.singleShot(
            0, partial(self.worker.thread_proc, self.manager)
        )

    def run(self):
        return self.app.exec_()


if __name__ == "__main__":
    import sys

    m = Application()
    m.main_proc()
    ret = m.run()
    sys.exit(ret)

Upvotes: 2

Related Questions