常见的场景
在界面的程序设计中,一旦需要执行的任务操作需要很长时间才能完成时,往往需要一个单独的线程来独立负责,如果直接放在界面的主线程中,则会导致界面消息长时间不能得到响应,界面卡死。而一旦使用到多线程则,则需要考虑诸如线程间的通信、线程的同步等问题,在这里仅针对工作线程与界面线程交互的问题,这个数据属于通信问题,不过对象比较特殊,且时在python的环境下。
问题描叙
在我的项目中,使用Qt做界面,用python的threading库创建工作线程,在工作线程中需要完成一系列的任务,当每个部分完成后,需要和界面进行交互,如最简单的处理进度和当前步骤结果信息的在界面上的输出。而本文主要是总结以下,怎样在工作线程中控制界面的显示或控件设置问题。
解决方案
解决方案有如下几种:
在线程中获取,界面窗体对象进行直接操作
这是最简单直接的方法,这个方案的关键在于窗体对象的获取,我们可以有多种方法来获取窗体对象:
- 通过全局变量传递
- 将线程的回调函数设为有参数的,创建时通过窗体对象作为参数传递进去
- 通过对操作系统的API调用,来查找需要的窗口对象(在python中没有实验过)
当获得了窗体对象后,就可以任意调用其中的成员函数和成员变量了。但是当我使用PyQt5的的时候,这种方法时有问题的。
通过获取的窗体对象,读取或者改写与界面无关的成员时,没有问题;但是一旦涉及到UI控件的一些写操作时久有可能出问题,如:在工作线程使用QTextEdit的setText()函数里面改变QTextEdit控件的显示文字时,程序会卡死。不过这里说的是可能,使用诸如改变窗口的setWindowTitle()设置窗口名称、改变QLabel的文字、改变QLineEdit的内容,都没有出现问题。
后面到网上搜索,发现QT文档上是不允许这样跨线程修改UI控件这样的操作的,所以我觉得这种方法可以用来获取窗口类成员的值比较合适,如果要对界面做修改则需要考虑其他办法。
使用通过发送信号实现
由于在工作线程中直接操作界面控件可能会出现一些问题,所以比较标准的做法是通过发送信号来实现通信。
在MFC中可以先自定义消息,然后用sendmessage或postmessage来将消息发送给对应的界面窗体。而在Qt中则可以使用信号与槽机制来实现。实现方法如下:
- 第一步,导入QObject模块和pyqtSignal函数
1
from PyQt.QtCore import QObject
- 第二步,创建一个继承与QObject的类,其成员为信号,通过使用pyqtSignal函数来创建自定义信号
1
2
3class UpdataUI(QObject):
UpdateTextSignal = pyqtSignal(str)
UpdateResultSignal = pyqtSignal(str, str)- 第三步,实例化信号类,将信号connnect到界面操作函数作为的槽,如下代码initUserSignal函数的参数是传入的窗体类对象
1
2
3
4def initUserSignal(wnd):
update = UpdataUI()
update.UpdateTextSignal.connect(wnd.appendText)
update.UpdateResultSignal.connect(wnd.setTestResult)- 第四步,在需要使用槽函数的地方发送其连接的消息
1
2update.UpdateTextSignal.emit('\nRun: OK')
update.UpdateResultSignal.emit('True', '\nCheak MD5: OK')