将相同的对象添加到 QTabWidget

Adding the same object to a QTabWidget

我有一些可切换的 QPushButton 需要添加到 QTabWidget 的所有选项卡中。每个选项卡都应跟踪每个可切换 QPushButton 的当前状态,这就是我添加相同对象而不是创建新按钮的原因。小部件的位置并不重要,只要我在所有选项卡上都有相同的 tbutton 对象即可。 (即,当在选项卡 1 上启用 tbutton 时,它应该在选项卡 2 和 3 上启用)。

下面是我正在使用的代码。请注意,在此示例中,我只显示了一个可切换的 QPushButton.

import sys
from PyQt5.QtWidgets import QTabWidget, QPushButton, QApplication, QWidget, QHBoxLayout

if __name__ == '__main__':

    app = QApplication(sys.argv)
    tabW = QTabWidget()
    tbutton = QPushButton("foo")
    tbutton.setCheckable(True)

    w1 = QWidget()
    layout1 = QHBoxLayout(w1)
    layout1.addWidget(QPushButton("bar"))
    layout1.addWidget(tbutton)

    w2 = QWidget()
    layout2 = QHBoxLayout(w2)
    layout2.addWidget(QPushButton("baz"))
    layout2.addWidget(tbutton)

    tabW.addTab(w1, "Tab 1")
    tabW.addTab(w2, "Tab 2")
    tabW.addTab(tbutton, "Tab 3")
    tabW.show()
    sys.exit(app.exec_())

当应用程序为 运行 时,只有选项卡 3 包含 tbutton。虽然使用 QTabWidgetsetCornerWidget 方法可以始终显示单个按钮,但当添加的角部件更复杂(即更多可切换按钮)时,修改布局会更加困难。

小部件不能在 parents and/or 之间“共享”多次显示。每次将小部件添加到新布局时,它都会从以前的布局中删除。

虽然您可以通过在每次更改选项卡时将其添加到布局来重新设置窗口小部件的父级,但这确实不是一个实用的解决方案,尤其是在涉及复杂的布局系统时,并且重新设置父级通常仅在非常特殊的情况下使用,在这种情况下,由于其复杂的状态(例如,工具栏),确实在其他地方使用相同的实例很重要。由于您只是在使用一个按钮,因此重复使用同一个实例没有什么意义。

一个更合乎逻辑和安全的解决方案是为每个选项卡创建一个按钮,link它们的状态使用信号,然后如果您还需要将切换信号连接到另一个功能,只需连接到一个其中(并非全部):

if __name__ == '__main__':

    app = QApplication(sys.argv)
    tabW = QTabWidget()

    w1 = QWidget()
    layout1 = QHBoxLayout(w1)
    layout1.addWidget(QPushButton("bar"))
    tbutton1 = QPushButton("foo", checkable=True)
    layout1.addWidget(tbutton1)

    w2 = QWidget()
    layout2 = QHBoxLayout(w2)
    layout2.addWidget(QPushButton("baz"))
    tbutton2 = QPushButton("foo", checkable=True)
    layout2.addWidget(tbutton2)

    tbutton3 = QPushButton("foo", checkable=True)

    tabW.addTab(w1, "Tab 1")
    tabW.addTab(w2, "Tab 2")
    tabW.addTab(tbutton3, "Tab 3")

    buttons = tbutton1, tbutton2, tbutton3
    def toggleButtons(state):
        for button in buttons:
            button.setChecked(state)
    for button in buttons:
        button.toggled.connect(toggleButtons)

    tabW.show()
    sys.exit(app.exec_())

以上指示是因为编写的代码会触发某种程度的递归,因为信号将针对具有不同状态的按钮的所有 setChecked 调用发出。由于 Qt 写得很好,所有状态变化只有在新状态实际上不同时才会发出信号,但是如果你想做的事情更正确,只需暂时阻止信号:

def toggleButtons(state):
    for button in buttons:
        blocked = button.blockSignals(True)
        button.setChecked(state)
        button.blockSignals(blocked)

但是,最好的解决方案是使用 QSignalBlocker,它的作用几乎相同,但更安全:

def toggleButtons(state):
    for button in buttons:
        with QtCore.QSignalBlocker(button):
            button.setChecked(state)

这有一个问题:由于信号只会发出一次,对于实际触发的按钮,如果您需要在状态更改时执行其他操作,您可以在 toggleButtons 中执行或者您将功能连接到 所有 按钮。

如果您想将“组切换”的逻辑与对状态变化作出反应的实际功能分开,更好的解决方案是使用自定义信号,但您必须使用 class。

class TabWidget(QTabWidget):
    stateChanged = QtCore.pyqtSignal(bool)


if __name__ == '__main__':

    app = QApplication(sys.argv)
    tabW = TabWidget()

    # ...

    buttons = tbutton1, tbutton2, tbutton3
    def toggleButtons(state):
        for button in buttons:
            with QtCore.QSignalBlocker(button):
                button.setChecked(state)
        tabW.stateChanged.emit(state)

    for button in buttons:
        button.toggled.connect(toggleButtons)

    def doSomething(state):
        print('state changed', state)

    tabW.stateChanged.connect(doSomething)

    tabW.show()

    sys.exit(app.exec_())

请考虑这个“程序”流程适合教育目的,但使用 class 并在其中实现 UI 和逻辑通常是更好的做法,然后上面的函数应该成为 class 中的方法,以便它们可以在实例的上下文中运行。