PyQt5中pyqtSignal跨线程传递子窗口如何保留控件功能
问题原因
Qt的GUI运行有强制规则:所有QWidget派生的UI控件,必须在GUI主线程内创建和操作,非主线程直接创建UI控件属于未定义行为,会出现事件无法派发、按钮无响应、绘制异常甚至崩溃的问题。
按钮失效的核心原因:
- 当把
SubWindow()实例化写在__init__中时,SignalStore对象本身是在主线程初始化的,因此创建出的子窗口归属主线程,事件循环可以正常派发按钮点击事件,功能正常。 - 当把
SubWindow()实例化写在run方法中时,run方法的代码运行在子线程,创建出的子窗口线程亲和性绑定到了子线程,而子线程没有运行Qt GUI事件循环,系统的点击消息无法派发到按钮的槽函数,因此按钮失去响应。
实现方案
如果必须将self.sub_window = SubWindow()语句保留在run方法内,可以在子窗口实例化完成后、显示前,调用moveToThread将子窗口及其所有子控件(按钮)的线程亲和性转移到GUI主线程,即可让按钮功能恢复正常。
注意:该方案属于非标准兼容写法,Qt官方推荐所有UI创建逻辑都放在主线程执行,子线程仅处理耗时非UI任务。该方案生效的前提是
moveToThread操作必须在子窗口调用show()、完成底层窗口资源绑定前执行,否则操作会失效。
修改后完整代码
# -*- coding: utf-8 -*- from threading import currentThread from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, qApp import sys from PyQt5.QtCore import pyqtSignal, QObject, QThread class SubWindow(QWidget): def __init__(self): super(SubWindow, self).__init__() self.resize(400, 400) self.button = QPushButton(self) self.button.setText('push me to print ***') self.button.move(200, 200) self.button.clicked.connect(self.print_) def print_(self): print('***') class SignalStore(QThread): window_signal = pyqtSignal(object) def __init__(self): super(SignalStore, self).__init__() def run(self): # 保留实例化语句在run方法内 self.sub_window = SubWindow() # 将子窗口及子控件的线程归属转移到GUI主线程 self.sub_window.moveToThread(qApp.thread()) self.window_signal.emit(self.sub_window) class MainWindow(QWidget): def __init__(self): super(MainWindow, self).__init__() self.resize(400, 400) self.button = QPushButton(self) self.button.setText('push me to get subwindow') self.button.move(200, 200) self.button.clicked.connect(self.send_signal) self.med_signal = SignalStore() self.med_signal.window_signal.connect(self.get_sub_window) def send_signal(self): self.med_signal.start() def get_sub_window(self, para): self.sub_window = para self.sub_window.show() if __name__ == '__main__': app = QApplication(sys.argv) win = MainWindow() win.show() sys.exit(app.exec_())
补充说明
qApp是PyQt提供的全局单例,指向当前运行的QApplication实例,qApp.thread()就是GUI主线程的对象引用。moveToThread会自动把QObject的所有子对象一并转移到目标线程,因此子窗口上的按钮不需要单独调用该方法,会随父窗口一起绑定到主线程。- 不要在子线程中对已经转移到主线程的子窗口做任何UI修改操作,跨线程操作UI依然会引发异常。
内容的提问来源于stack exchange,提问作者ydx
相关产品推荐
相关产品推荐

