简体   繁体   English

带有 QCheckBox 的 PyQt5 QGroupBox - 关闭自动禁用

[英]PyQt5 QGroupBox with QCheckBox - dismiss auto disable

I want to use a QGroupBox which will be checkable, but i don't the content widgets of QGroupBox to be disabled when the QGroupBox checkbox is Unchecked.我想使用一个可检查的 QGroupBox,但是当 QGroupBox 复选框未选中时,我不会禁用 QGroupBox 的内容小部件。

From reference:来自参考:

checked : bool This property holds whether the group box is checked checked : bool 该属性保存是否选中组框

If the group box is checkable, it is displayed with a check box.如果组框是可勾选的,它会显示一个复选框。 If the check box is checked, the group box's children are enabled;如果选中该复选框,则启用组框的子项; otherwise, the children are disabled and are inaccessible to the user.否则,孩子将被禁用并且用户无法访问。

By default, checkable group boxes are also checked.默认情况下,还会选中可检查的组框。

I want to have a checkbox in QGroupBox title bar, but i don't want the above feature to be applied.我想在 QGroupBox 标题栏中有一个复选框,但我不希望应用上述功能。

The checkbox logic will be select-all, select-none, so when the checkbox is unselected the user can modify inner QGroupBox checkbox elements.复选框逻辑将是全选,全选,因此当复选框未选中时,用户可以修改内部 QGroupBox 复选框元素。

I want to keep an interface-ui like the following:我想保留如下所示的界面 ui:

Maybe i have to use a QFrame with QPaintEvent or QSS stylesheet, but i am not expert with this.也许我必须使用带有 QPaintEvent 或 QSS 样式表的 QFrame,但我不是这方面的专家。

在此处输入图片说明

Edit: I also want triState for QGroupBox checkbox if possible.编辑:我也想三态为QGroupBox复选框,如果可能的。

Edit 2: I try this code:编辑2:我试试这个代码:

self.main_self.ui_visible_player_list_fields_window.groupBox.changeEvent(QtCore.QEvent.EnabledChange).connect(lambda event:event.ignore())

but it has errors.但它有错误。

 self.main_self.ui_visible_player_list_fields_window.groupBox.changeEvent(QtCore.QEvent.EnabledChange).connect(lambda event:event.ignore())
TypeError: changeEvent(self, QEvent): argument 1 has unexpected type 'Type'

Edit: I think the following code will solve the problem:编辑:我认为以下代码将解决问题:

class Custom_QGroupBox(QtWidgets.QGroupBox):

    def __init__(self, parent=None):
        super(Custom_QGroupBox, self).__init__(parent)

    def changeEvent(self, event):
        if event.type() == QtCore.QEvent.EnabledChange:
            self.blockSignals(True)
            self.setEnabled(True)
            event.ignore()
            self.blockSignals(False)
        else:
            return super(Custom_QGroupBox, self).changeEvent(event)

but it doesn't :(但它没有:(

I add this code:我添加这个代码:

self.main_self.ui_visible_player_list_fields_window.groupBox.blockSignals(True)
        self.main_self.ui_visible_player_list_fields_window.groupBox.setEnabled(True)
        
        for child_widget in self.main_self.ui_visible_player_list_fields_window.groupBox.children():
            try:
                child_widget.setEnabled(True)
            except:
                pass
        self.main_self.ui_visible_player_list_fields_window.groupBox.blockSignals(False)

in toggle method and is child_widget_checkbox.stateChanged.connect method and it works.在切换方法中,是child_widget_checkbox.stateChanged.connect方法,它可以工作。

The only thing is remaining to fully answer the question is the tristate of QGroupBox.唯一要完全回答这个问题的是 QGroupBox 的三态。

Premise: probably not a good idea前提:可能不是一个好主意

There are known conventions for UI elements, users are used to them and expect that doing an "action" on a well known element type would cause an also known result. UI 元素有一些已知的约定,用户已经习惯了它们并期望对众所周知的元素类型执行“操作”会导致同样已知的结果。
While a group box is more of a fringe case, the default behavior of Qt follows the convention: toggling the checkbox results in toggling the enabled state of its contents.虽然分组框更像是一种边缘情况,但 Qt 的默认行为遵循约定:切换复选框会导致切换其内容的启用状态。

Since UI elements should always try to follow conventions and make the possible outcome of an user action as much predictable as possible, a better solution would be to add a "top level" group of buttons that would set all boxes as checked or unchecked ("Check all" and "Check none").由于 UI 元素应始终尝试遵循约定并使用户操作的可能结果尽可能可预测,因此更好的解决方案是添加一组“顶级”按钮,将所有框设置为选中或未选中(“全部检查”和“不检查”)。

Why doesn't it work?为什么不起作用?

First of all, a changeEvent is not a signal, so you cannot try to "connect" it (always look for the function type in the Qt documentation, if it's a signal, it will have a [signal] notation besides its definition).首先, changeEvent不是信号,所以你不能尝试“连接”它(总是在 Qt 文档中寻找函数类型,如果它是一个信号,除了它的定义之外,它还会有一个[signal]符号)。
It is an event handler, and it expects an event instance as argument, not a type.它是一个事件处理程序,它需要一个事件实例作为参数,而不是一个类型。

Then, toggling the checkbox of a group box changes the state of its children , not that of the group box, so you'll never receive a EnabledChange event when toggling it.然后,切换组框的复选框会更改其子项的状态,而不是组框的状态,因此在切换时您永远不会收到EnabledChange事件。
That's quite obvious if you think about it: if the whole group box gets disabled when toggling its check box, you can never click it again to enable it, as input events are ignored by default for disabled widgets, and both the title and checkbox would be shown as disabled too.如果您考虑一下,这很明显:如果在切换复选框时整个组框被禁用,您将永远无法再次单击以启用它,因为默认情况下禁用的小部件的输入事件将被忽略,并且标题复选框都将也显示为禁用。

Possible solution可能的解决方案

There are various possible solutions, including subclassing QFrame and draw the checkbox and title, but making it compliant with the current style would be very (and unnecessarily) difficult.有多种可能的解决方案,包括子类化 QFrame 并绘制复选框和标题,但使其符合当前样式将非常(并且不必要地)困难。
The best choice is usually the one that does less changes to the default behavior.最佳选择通常是对默认行为进行较少更改的选择。

In this case, my suggestion is to do two things:在这种情况下,我的建议是做两件事:

  • connect to the toggled signal of the group box and override what the default behavior does (restore the enabled state unless explicitly set for that widget);连接到分组框的toggled信号并覆盖默认行为的作用(恢复启用状态,除非为该小部件明确设置);
  • override the paintEvent and change the QStyleOptionGroupBox state used for the style and painter before actually drawing it;在实际绘制之前覆盖paintEvent并更改用于样式和画家的QStyleOptionGroupBox state

Since the state is internally defined for the group box, we need to override its value(s): initStyleOption() adds the option's state flag State_On when the checkbox is (theoretically) checked, or State_Off when unchecked (so, whether the child widgets are enabled or not), but we have to set the option state based on the checkbox states instead.由于状态是为组框内部定义的,我们需要覆盖它的值: initStyleOption()在复选框(理论上)被选中时添加选项的状态标志State_On ,或者在未选中时添加State_Off (因此,子部件是否是否启用),但我们必须根据复选框状态设置选项状态。 In order to do that, we have to check all check boxes and verify whether all of them are checked, any of them is checked, or none is.为了做到这一点,我们必须选中所有复选框并验证是否所有复选框都被选中,其中任何一个被选中,或者没有。

from PyQt5 import QtCore, QtWidgets

class Custom_QGroupBox(QtWidgets.QGroupBox):
    checkAllIfAny = True
    def __init__(self, *args, **kwargs):
        super(Custom_QGroupBox, self).__init__(*args, **kwargs)
        self.setCheckable(True)
        self.checkBoxes = []
        self.toggled.connect(self.toggleCheckBoxes)

    def addCheckBox(self, cb):
        self.checkBoxes.append(cb)
        cb.toggled.connect(self.update)
        cb.destroyed.connect(lambda: self.removeCheckBox(cb))

    def removeCheckBox(self, cb):
        try:
            self.checkBoxes.remove(cb)
            cb.toggled.disconnect(self.update)
        except:
            pass

    def allStates(self):
        return [cb.isChecked() for cb in self.checkBoxes]

    def toggleCheckBoxes(self):
        if self.checkAllIfAny:
            state = not all(self.allStates())
        else:
            state = not any(self.allStates())

        for widget in self.children():
            if not widget.isWidgetType():
                continue
            if not widget.testAttribute(QtCore.Qt.WA_ForceDisabled):
                # restore the enabled state in order to override the default
                # behavior of setChecked(False); previous explicit calls for
                # setEnabled(False) on the target widget will be ignored
                widget.setEnabled(True)
                if widget in self.checkBoxes:
                    widget.setChecked(state)

    def paintEvent(self, event):
        opt = QtWidgets.QStyleOptionGroupBox()
        self.initStyleOption(opt)
        states = self.allStates()
        if all(states):
            # force the "checked" state
            opt.state |= QtWidgets.QStyle.State_On
            opt.state &= ~QtWidgets.QStyle.State_Off
        else:
            # force the "not checked" state
            opt.state &= ~QtWidgets.QStyle.State_On
            if any(states):
                # force the "not unchecked" state and set the tristate mode
                opt.state &= ~QtWidgets.QStyle.State_Off
                opt.state |= QtWidgets.QStyle.State_NoChange
            else:
                # force the "unchecked" state
                opt.state |= QtWidgets.QStyle.State_Off
        painter = QtWidgets.QStylePainter(self)
        painter.drawComplexControl(QtWidgets.QStyle.CC_GroupBox, opt)


app = QtWidgets.QApplication([])
groupBox = Custom_QGroupBox('Group options')

layout = QtWidgets.QGridLayout(groupBox)
o = 0
for c in range(2):
    for r in range(4):
        o += 1
        cb = QtWidgets.QCheckBox('Option {}'.format(o))
        groupBox.addCheckBox(cb)
        layout.addWidget(cb, r, c)

groupBox.show()
app.exec_()

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

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