显示来自不同线程的 QInputDialog 和其他 gui 对象

Showing QInputDialog and other gui objects from various threads

我在 python 中有一个 websocket 服务器 运行ning,对于每个新连接,都会创建一个新线程并处理请求。

在主线程 [Gui-thread] 中,我正在初始化 QApplication([])。用例是,当我处理请求时,我想等待并通过 QInputDialog 从用户那里获得文本响应。 每当我 运行 它时,都会有一个事件循环 运行ning 但没有显示图形用户界面。因为所有的 gui 元素都可以从 Gui-thread 本身显示。

我尝试了使用 QSignals/slots 和 Pypubsub 的各种方法,但无法达到要求。请提出一些想法来完成用例。非常感谢伪代码。

下面提到的代码是我试过的一些例子。我在下面的示例中使用线程,因为正如我提到的,来自连接的每个请求都是使用分配给该连接的线程执行的。线程需要来自 QInputDialog 的文本。

提前致谢。

下面是服务请求调用 server_extentions 函数的 websockets 服务器代码,每次收到传入请求时我都必须显示 QInputDialog。

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()


----编辑-----

下面是一些大概的想法,我尝试使用 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()

-----编辑 2 ------

下面是我用 QSignal 试过的东西。 [查看代码中的注释,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()

对于此类应用程序,最好实施 worker-thread 方法。这种方法的主要思想是实现 QObjects,将它们移动到新线程并异步调用插槽(通过 QEvents、pyqtSignals、QTimer.singleShot(...)QMetaObject::invokeMethod(...) 等),以便任务在线程中执行Live 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)