使用来自另一个 Python 进程的数据更新小部件,而不会阻止它



我正在运行一个Python脚本来提取和本地化一些文件。我想使用QDialog来显示通过QProgressBar的进度状态,以及正在复制的文件列表。

首先我要说的是,本地化脚本不能集成到 PyQt 脚本中 - 否则我知道解决方案将非常简单。我需要将本地化脚本与 UI 分开,并让它们同时运行。

我考虑过通过线程从本地化脚本运行 UI,以避免它阻塞本地化过程。但是在这一点上,我不知道如何更新 UI 元素,因为我没有可以调用和更新的实例,因为我已经使用线程启动了它。

这是对话框代码的简化示例:

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是 UI .py文件的名称。

当然,这样主脚本就不会被 ui 卡住,这是我的目标之一 - 但我不知道在这种情况下如何更新进度条和标签。我发现了几个讨论类似问题的线程,但没有一个对我有确切帮助。

我希望我的描述足够清楚。

更新:

我在这里找到了解决方案,使用管道。即使我不确定这是解决这个问题的适当方法,它肯定做到了。我没有使用两个 PyQt GUI 之间的"双向通信"(如链接中所述),而是修改了代码,以便在我的 GUI 和本地化脚本之间进行双向通信。

解决此问题的一种方法是在单独的进程中运行对话框,然后使用某种形式的 IPC 发送更新。下面的解决方案使用 Qt 的QLocalServerQLocalSocket类将用 json 编码的dict传递给对话进程。每当收到新数据时,服务器都会发出信号,对话框连接到该信号以处理更新。当发送进程退出时,服务器进程将自动关闭。

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

相关内容

最新更新