PyQt:在窗口之间传递信息

问题描述

这个问题已经被问过了,我想提出我的解决方案,想知道这是可以接受的模式还是有更好的选择。

使用QGIS我开始使用PyQt作为GUI(以前主要是tkinter),并且遇到了这样的情况:我在画布上进行选择,然后打开另一个窗口以提供有关功能的信息。我要在画布上标记选定的功能,因此需要其他窗口提供信息。

我在下面的PyQt中做了一个最小的工作示例(需要使用Python 3.8导入PyQt5),其中有两个窗口,一个叫做Button,另一个display,其中display显示了多少次该按钮被按下。

为了进行通信,我在QTimer窗口中定义了一个display,该窗口会轮询Button窗口(在这种情况下,每250 ms)以获取所需的信息。问题是在PyQt中有更好的方法吗?

import sys
from PyQt5.QtWidgets import QWidget,QVBoxLayout,QLabel,QApplication,QPushButton
from PyQt5.QtCore import QTimer

POLL_INTERVAL = 250  # in ms
WINSIZE = (200,50)  # w,h
BUTTON_WIN_POS = (200,200)  # w,h
disPLAY_WIN_POS = (450,h

class ButtonWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.button_pressed_counter = 0
        self.initUI()

    def initUI(self):
        vBox = QVBoxLayout()
        button = QPushButton('Press me')
        button.clicked.connect(self.press_button)
        vBox.addWidget(button)

        self.setLayout(vBox)
        self.move(*BUTTON_WIN_POS)
        self.setwindowTitle('Button ... ')
        self.resize(*WINSIZE)
        self.show()

    def press_button(self):
        self.button_pressed_counter += 1

    @property
    def button_count(self):
        return self.button_pressed_counter

class displayWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.button_window = ButtonWindow()

        self.button_count = 0
        self.button_counter = QTimer()
        self.button_counter.timeout.connect(self.get_button_count)

        self.initUI()

    def initUI(self):
        vBox = QVBoxLayout()
        self.text_lbl = QLabel()
        vBox.addWidget(self.text_lbl)
        self.text_lbl.setText(f'button count: {self.button_count}')

        self.setLayout(vBox)
        self.move(*disPLAY_WIN_POS)
        self.setwindowTitle('display ... ')
        self.resize(*WINSIZE)
        self.show()

        self.button_counter.start(POLL_INTERVAL)

    def get_button_count(self):
        new_button_count = self.button_window.button_count
        if self.button_count == new_button_count:
            pass

        else:
            self.button_count = new_button_count
            self.text_lbl.setText(f'button count: {self.button_count}')


def main():
    app = QApplication([])
    _ = displayWindow()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

解决方法

不是,至少在这种情况下,这不是一个好的解决方案。另外,在处理交互式元素时,依靠计时器进行“轮询”很少是一个好的解决方案:它不可靠,不允许单击的按钮和结果之间立即做出响应,并且使代码不必要地复杂。

信号和插槽正是为此目的而存在的:允许对象之间通信的通用接口;因此请在“源”类中实现自定义信号,然后将其连接到“目标”中。

class ButtonWindow(QWidget):
    pressCountChanged = pyqtSignal(int)
    # ...

    def press_button(self):
        self.button_pressed_counter += 1
        self.pressCountChanged.emit(self.button_pressed_counter)


class DisplayWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.button_window = ButtonWindow()
        self.button_window.pressCountChanged.connect(self.get_button_count)

        self.button_count = 0

        self.initUI()

显然,必须删除与QTimer相关的所有内容。

请注意,如果您正在使用另一个窗口获取应更改主界面行为的“功能”,则应考虑改用QDialog,并最终决定是否将其用作模式(通常通过{{ 1}}),它可以在关闭对话框之前阻止与其他窗口的交互,并(可能)返回所需的值。