Отображение QInputDialog и других объектов графического интерфейса из различных потоков - PullRequest
1 голос
/ 10 июня 2019

У меня есть сервер веб-сокетов, работающий на python, и для каждого нового соединения будет создаваться новый поток, и запросы будут обслуживаться.

В основном потоке [Gui-thread] я инициализирую QApplication ([]). случай использования, когда я обрабатываю запрос, я хотел подождать и получить текстовый ответ от пользователя через QInputDialog. когда я запускаю его, запускается цикл обработки событий, но он не показывает графический интерфейс. потому что все элементы графического интерфейса могут отображаться из самого Gui-потока.

Я пробовал разные подходы, используя QSignals / slots и Pypubsub, но не смог добиться того, что требуется. пожалуйста, предложите некоторую идею, чтобы закончить вариант использования. псевдокод приветствуется.

Ниже приведен код, который я попробовал. Я использую поток в приведенных ниже примерах, потому что, как я уже упоминал, каждый запрос от соединения выполняется с потоком, назначенным для соединения. и текст из QInputDialog требуется потоком.

Заранее спасибо.

ниже приведен код сервера websockets, который обслуживает запрос, вызывающий функцию server_extentions, я должен показывать 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. [проверьте комментарий в коде, Как вызвать функцию с 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()

1 Ответ

2 голосов
/ 10 июня 2019

Для приложений этого типа лучше реализовать подход с использованием рабочих потоков.В этом подходе основная идея состоит в том, чтобы реализовать объекты QObject, переместить их в новый поток и вызвать слоты асинхронно (через QEvents, pyqtSignals, QTimer.singleShot(...), QMetaObject::invokeMethod(...) и т. Д.), Чтобы задачи выполнялись в потоке, в котором используется объект 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)
...