Обновите виджет с данными из другого процесса Python, не блокируя его - PullRequest
1 голос
/ 11 апреля 2019

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

Позвольте мне начать с того, что скрипт локализации не может быть интегрирован в скрипт PyQt - иначе я знаю, что решение будет довольно простым. Мне нужно держать скрипт локализации отделенным от пользовательского интерфейса и запускать их одновременно.

Я думал о запуске пользовательского интерфейса из сценария локализации через поток, чтобы не блокировать процесс локализации. Но в этот момент я понятия не имею, как обновить элементы пользовательского интерфейса, поскольку у меня нет экземпляра, который я могу вызвать и обновить, так как я запустил его с потоком.

Это упрощенный пример кода диалога:

from PyQt5 import QtCore, QtWidgets
import sys

class Ui_dialog_main(object):

    def setupUi(self, dialog_main):
        dialog_main.setWindowTitle("Test")
        dialog_main.resize(390, 120)

        self.progress_bar = QtWidgets.QProgressBar(dialog_main)
        self.progress_bar.setGeometry(QtCore.QRect(10, 60, 371, 30))

        self.label_localizing = QtWidgets.QLabel(dialog_main)
        self.label_localizing.setGeometry(QtCore.QRect(10, 10, 81, 25))
        self.label_localizing.setText("Package:")

        QtCore.QMetaObject.connectSlotsByName(dialog_main)


def start():
    app = QtWidgets.QApplication(sys.argv)
    dialog_main = QtWidgets.QDialog()
    ui = Ui_dialog_main()
    ui.setupUi(dialog_main)
    dialog_main.show()
    sys.exit(app.exec_())

И вот как я запускаю поток в файле локализатора:

thread = Thread(target=LocManager.start)
thread.start()

где LocManager - это имя файла .py пользовательского интерфейса.

Конечно, таким образом основной скрипт не застревает в интерфейсе, что является одной из моих целей - но я не знаю, как обновить индикатор выполнения и метку в этой ситуации. Я нашел несколько тем, обсуждающих похожие проблемы, но ничего, что точно помогло бы мне.

Надеюсь, мое описание было достаточно ясным.

UPDATE:

Я нашел решение для этого здесь , используя трубы. Даже если я не уверен, что это правильный способ решения этой проблемы, это определенно помогло. Вместо того чтобы работать с «Двунаправленной связью» между двумя графическими интерфейсами PyQt, как описано в ссылке, я изменил код, чтобы иметь двунаправленную связь между моим графическим интерфейсом и сценарием локализации.

1 Ответ

0 голосов
/ 15 апреля 2019

Один из способов решить эту проблему - запустить диалог в отдельном процессе, а затем использовать некоторую форму IPC для отправки обновлений.В приведенном ниже решении используются классы Qt QLocalServer и QLocalSocket для передачи dict, закодированного с помощью json, в процесс диалога.Сервер выдает сигнал при получении новых данных, к которому подключается диалоговое окно для обработки обновлений.Когда процесс отправки завершается, процесс сервера автоматически отключается.

test.py :

import time
from dlg_server import send_data

for message in 'One Two Three Four Five'.split():
    send_data(message=message)
    time.sleep(2)

dlg_server.py :

import sys, os, json, atexit
from PyQt5 import QtCore, QtWidgets, QtNetwork

SERVER = 'dlg_server'
_tries = 0

def send_data(**data):
    socket = QtNetwork.QLocalSocket()
    socket.connectToServer(SERVER, QtCore.QIODevice.WriteOnly)
    if socket.waitForConnected(500):
        socket.write(json.dumps(data).encode('utf-8'))
        if not socket.waitForBytesWritten(2000):
            raise RuntimeError('could not write to socket: %s' %
                  socket.errorString())
        socket.disconnectFromServer()
    elif socket.error() == QtNetwork.QAbstractSocket.HostNotFoundError:
        global _tries
        if _tries < 10:
            if not _tries:
                if QtCore.QProcess.startDetached(
                    'python', [os.path.abspath(__file__)]):
                    atexit.register(lambda: send_data(shutdown=True))
                else:
                    raise RuntimeError('could not start dialog server')
            _tries += 1
            QtCore.QThread.msleep(100)
            send_data(**data)
        else:
            raise RuntimeError('could not connect to server: %s' %
                socket.errorString())
    else:
        raise RuntimeError('could not send data: %s' % socket.errorString())


class Server(QtNetwork.QLocalServer):
    dataReceived = QtCore.pyqtSignal(object)

    def __init__(self):
        super().__init__()
        self.newConnection.connect(self.handleConnection)
        if not self.listen(SERVER):
            raise RuntimeError(self.errorString())

    def handleConnection(self):
        data = {}
        socket = self.nextPendingConnection()
        if socket is not None:
            if socket.waitForReadyRead(2000):
                data = json.loads(str(socket.readAll().data(), 'utf-8'))
                socket.disconnectFromServer()
            socket.deleteLater()
        if 'shutdown' in data:
            self.close()
            self.removeServer(self.fullServerName())
            QtWidgets.qApp.quit()
        else:
            self.dataReceived.emit(data)


class Dialog(QtWidgets.QDialog):
    def __init__(self):
        super().__init__()
        self.setGeometry(50, 50, 200, 30)
        layout = QtWidgets.QVBoxLayout(self)
        self.label = QtWidgets.QLabel()
        layout.addWidget(self.label)

    def handleDataReceived(self, data):
        self.show()
        self.label.setText(data.get('message', ''))

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    dialog = Dialog()
    server = Server()
    server.dataReceived.connect(dialog.handleDataReceived)
    app.exec_()
...