【问题标题】:Adding the same object to a QTabWidget将相同的对象添加到 QTabWidget
【发布时间】:2021-09-12 15:38:25
【问题描述】:

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

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

【问题讨论】:

  • 您的帖子不清楚。 我需要添加到所有标签是什么意思?您能否更详细地解释一下,每个按钮应该在哪里?
  • 可能,您每次使用按钮更改页面时都希望重新设置按钮。setParent

标签: python python-3.x pyqt5


【解决方案1】:

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

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

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

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 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_())

请考虑这个“程序化”流程对于教育目的来说很好,但是使用一个类并在其中实现 UI 和逻辑通常是更好的做法,然后上述函数应该成为该类中的方法,所以他们可以在实例的上下文中采取行动。

【讨论】:

  • 信号阻断器似乎是必需的,而不仅仅是正确的。没有它,在您的第一个示例中,该插槽将被调用 3 次。要使插槽真正有用,它必须做的不仅仅是同步检查状态 - 但最好只同步一次。
  • @ekhumoro 我同意,这就是为什么我指定如果需要状态更改来执行其他操作,它应该只连接到按钮的切换信号之一第一种情况。我会编辑和澄清,谢谢。
猜你喜欢
  • 2019-01-27
  • 1970-01-01
  • 2016-10-05
  • 1970-01-01
  • 1970-01-01
  • 2014-12-20
  • 1970-01-01
  • 2019-02-08
相关资源
最近更新 更多