简体   繁体   English

从嵌套子部件向 Qt 中的祖先发送信号(PyQT5)

[英]Send signals from Nested children widgets to ancestors in Qt (PyQT5)

I have a window that contains two main widgets, both of which are containers of other widgets, which in turn may contain further widgets.我有一个 window,其中包含两个主要小部件,这两个小部件都是其他小部件的容器,而其他小部件又可能包含更多小部件。

Each Widget is a separate class ( to maintain code readability ), and child widgets are instantiated directly inside this class, which is similar to something like React components structure.每个Widget都是一个单独的class(保持代码可读性),子widgets直接在这个class内部实例化,类似于React组件结构。

I would like to call some methods from any widget to a top level one, or send signals from a deeply nested widget to one near top level without having to use something akin to "self.parent().parent().parent().doStuff(args)", it works but if hierarchy changes this will raise bugs, it gets harder to maintain the more complex my GUI gets.我想从任何小部件调用一些方法到顶级小部件,或者从深度嵌套的小部件发送信号到接近顶层的小部件,而不必使用类似于“self.parent().parent().parent()”的东西.doStuff(args)”,它可以工作,但如果层次结构发生变化,这将引发错误,我的 GUI 变得越复杂,就越难维护。

Edit: Here is a simplified example of what I'm trying to achieve:编辑:这是我要实现的简化示例:

from PySide2.QtWidgets import *
import sys


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.central = QWidget()
        self.central.setObjectName("Central_Widget")
        self.setCentralWidget(self.central)
        mainLayout = QHBoxLayout(self.central)
        self.central.setStyleSheet("""
        #Central_Widget{
            background-color: #2489FF;
        }""")

        #init mainView widget
        self.leftSide = leftWindow(self.central)
        self.rightSide = rightWindow(self.central)

        mainLayout.addWidget(self.leftSide)
        mainLayout.addWidget(self.rightSide)

class leftWindow(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self.setObjectName("leftWindow")
        layout = QVBoxLayout()
        self.setLayout(layout)
        self.label = QLabel(" Left ")
        self.leftButton = customBtn(" Left Button ", "someSVGHere", self)
        layout.addWidget(self.label)
        layout.addWidget(self.leftButton)

        self.setStyleSheet("border : 2px solid white;")


class rightWindow(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self.setObjectName("rightWindow")
        layout = QVBoxLayout()
        self.setLayout(layout)
        self.label = QLabel(" Right ")
        self.rightButton = customBtn(" Right Button ", "someSVGHere", self)
        layout.addWidget(self.label)
        layout.addWidget(self.rightButton)

        self.setStyleSheet("border : 2px solid black;")


class customBtn(QWidget):
    def __init__(self, text, svgIcon, parent):
        super().__init__(parent)
        layout = QVBoxLayout()
        self.setLayout(layout)
        self.btnText = QLabel(text)
        self.Icon = svgIcon
        layout.addWidget(self.btnText)
        # setup the icon and the stylesheet etc ....

    def mouseReleaseEvent(self, event):
        # if it's the right button getting clicked, change the stylesheet of the central widget in main window
        # How do I do that here ???
        return super().mouseReleaseEvent(event)
        

# Launcher
if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MainWindow()
    win.setWindowTitle("nesting test")
    win.show()
    sys.exit(app.exec_())

In the above example, I want to change the background of the central widget in the MainWindow class using the right button inside the rightWindow widget, in this example it's only 2 layers of nesting so it's not really deep, but in my App I would have up to 5 or 6 layers.在上面的示例中,我想使用 rightWindow 小部件内的右键更改 MainWindow class 中中央小部件的背景,在这个示例中它只有 2 层嵌套,所以它不是很深,但在我的应用程序中我会有最多 5 或 6 层。 ideally, each class would be in a separate file but that doesn't change this example.理想情况下,每个 class 都在一个单独的文件中,但这不会改变这个例子。

I unfortunately cannot use QT designer because I'm building a modern GUI app, so every single widget will be a custom one, and I would like to keep my code fragmented instead of instantiating everything in a single class like what QT designer generates.不幸的是,我不能使用 QT 设计器,因为我正在构建一个现代 GUI 应用程序,所以每个小部件都是自定义的,我想保持我的代码碎片化,而不是像 QT 设计器生成的那样在单个 class 中实例化所有内容。

Generally speaking, when dealing with object hierarchy, child objects should not attempt to directly interact their parents.一般来说,在处理 object 层级时,子对象不应试图直接与父对象交互。 In fact, they should always be able to "work" on their own, no matter of their parent, and even when they have no parent at all.事实上,他们应该总是能够自己“工作”,不管他们的父母是谁,甚至在他们根本没有父母的时候。

Considering this, instead of connecting a signal to a slot of a [great-][grand]parent, it should be that parent that connects the [great-][grand]child signal to its own slot.考虑到这一点,而不是将信号连接到 [great-][grand]parent 的插槽,应该是将 [great-][grand]child 信号连接到它自己的插槽的父级。 This perspective is important, because it's also what allows us to connect sibling objects that are (nor could) be aware of each other.这个观点很重要,因为它也允许我们连接彼此(也可能)知道彼此的兄弟对象。

class customBtn(QWidget):
    customSignal = pyqtSignal(Qt.MouseButton)

    def mouseReleaseEvent(self, event):
        self.customSignal.emit(event.button())


class MainWindow(QMainWindow):
    def __init__(self):
        # ...
        self.leftSide = leftWindow(self.central)
        self.leftSide.leftButton.customSignal.connect(self.something)

    def something(self):
        # whatever

If the structure has many levels, it's also good practice to create custom signals for the intermediate classes, since signals can also be chained as long as they have a compatible signature (the target signal must have the same argument types or fewer arguments with same types).如果结构有很多级别,那么为中间类创建自定义信号也是一种很好的做法,因为信号也可以链接起来,只要它们具有兼容的签名(目标信号必须具有相同的参数类型或更少的arguments 具有相同的类型).

class leftWindow(QWidget):
    customSignal = pyqtSignal()
    def __init__(self, parent):
        # ...
        self.leftButton = customBtn(" Left Button ", "someSVGHere", self)
        self.leftButton.customSignal.connect(self.customSignal)

class MainWindow(QMainWindow):
    def __init__(self):
        # ...
        self.leftSide = leftWindow(self.central)
        self.leftSide.customSignal.connect(self.something)

In the case above, the signal of leftWindow doesn't have any arguments, so it's compatible with the button signal, as those arguments will be automatically discarded.在上面的例子中, leftWindow的信号没有任何 arguments,因此它与按钮信号兼容,因为那些 arguments 将被自动丢弃。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM