PyQt5 QGroupBox with QCheckBox - 关闭自动禁用
PyQt5 QGroupBox with QCheckBox - dismiss auto disable
我想使用一个可以选中的 QGroupBox,但是当 QGroupBox 复选框未选中时,我不会禁用 QGroupBox 的内容小部件。
checked : bool This property holds whether the group box is checked
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.
我想在 QGroupBox 标题栏中有一个复选框,但我不想应用上述功能。
复选框逻辑将是select-all、select-none,因此当取消选中复选框时,用户可以修改内部QGroupBox 复选框元素。
我想保留如下 interface-ui:
也许我必须使用带有 QPaintEvent 或 QSS 样式表的 QFrame,但我不是这方面的专家。
编辑: 如果可能的话,我也想要 triState QGroupBox 复选框。
编辑 2: 我试试这个代码:
self.main_self.ui_visible_player_list_fields_window.groupBox.changeEvent(QtCore.QEvent.EnabledChange).connect(lambda event:event.ignore())
但是有错误。
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'
编辑: 我认为下面的代码可以解决问题:
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)
但事实并非如此:(
我添加这段代码:
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)
在切换方法中并且是 child_widget_checkbox.stateChanged.connect
方法并且有效。
唯一剩下的就是QGroupBox的三态来完整回答这个问题。
前提:可能不是个好主意
UI 元素有已知的约定,用户 使用 它们并期望对众所周知的元素类型执行“操作”也会导致已知结果。
虽然组框更像是 fringe 情况,但 Qt 的默认行为遵循约定:切换复选框会导致切换其内容的启用状态。
由于 UI 元素应始终尝试遵循约定并使用户操作的可能结果尽可能可预测,因此更好的解决方案是添加“顶级”按钮组将所有框设置为选中或未选中(“全部选中”和“选中 none”)。
为什么不起作用?
首先,changeEvent
不是信号,因此您不能尝试“连接”它(总是在 Qt 文档中查找函数类型,如果它是一个信号,除了它的定义之外,它还会有一个 [signal]
符号。
它是一个事件处理程序,它需要一个事件 instance 作为参数,而不是类型。
然后,切换组框的复选框会更改其 children 的状态,而不是组框的状态,因此您永远不会收到EnabledChange
切换时的事件。
如果您考虑一下,这是很明显的:如果 whole 组框在切换其复选框时被禁用,您将永远无法再次单击它来启用它,因为输入事件默认情况下会被忽略禁用的小部件,标题 和 复选框也将显示为禁用。
可能的解决方案
有多种可能的解决方案,包括子类化 QFrame 并绘制复选框和标题,但使其符合当前样式将非常(而且不必要)困难。
最好的选择通常是 less 更改默认行为的选择。
在这种情况下,我的建议是做两件事:
- 连接到组框的
toggled
信号并覆盖默认行为(恢复启用状态,除非 明确 为该小部件设置);
- 覆盖
paintEvent
并在实际绘制之前更改用于样式和画家的 QStyleOptionGroupBox state
;
由于状态是在内部为组框定义的,我们需要覆盖它的值:initStyleOption()
添加选项的状态标志 State_On
当复选框被(理论上)选中时,或 State_Off
未选中时(因此,无论 child 小部件是否启用),但我们必须根据复选框状态设置选项状态。为此,我们必须选中所有复选框并验证是否选中了 all,是否选中了 any,或者 none 是.
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_()
我想使用一个可以选中的 QGroupBox,但是当 QGroupBox 复选框未选中时,我不会禁用 QGroupBox 的内容小部件。
checked : bool This property holds whether the group box is checked
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.
我想在 QGroupBox 标题栏中有一个复选框,但我不想应用上述功能。
复选框逻辑将是select-all、select-none,因此当取消选中复选框时,用户可以修改内部QGroupBox 复选框元素。
我想保留如下 interface-ui:
也许我必须使用带有 QPaintEvent 或 QSS 样式表的 QFrame,但我不是这方面的专家。
编辑: 如果可能的话,我也想要 triState QGroupBox 复选框。
编辑 2: 我试试这个代码:
self.main_self.ui_visible_player_list_fields_window.groupBox.changeEvent(QtCore.QEvent.EnabledChange).connect(lambda event:event.ignore())
但是有错误。
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'
编辑: 我认为下面的代码可以解决问题:
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)
但事实并非如此:(
我添加这段代码:
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)
在切换方法中并且是 child_widget_checkbox.stateChanged.connect
方法并且有效。
唯一剩下的就是QGroupBox的三态来完整回答这个问题。
前提:可能不是个好主意
UI 元素有已知的约定,用户 使用 它们并期望对众所周知的元素类型执行“操作”也会导致已知结果。
虽然组框更像是 fringe 情况,但 Qt 的默认行为遵循约定:切换复选框会导致切换其内容的启用状态。
由于 UI 元素应始终尝试遵循约定并使用户操作的可能结果尽可能可预测,因此更好的解决方案是添加“顶级”按钮组将所有框设置为选中或未选中(“全部选中”和“选中 none”)。
为什么不起作用?
首先,changeEvent
不是信号,因此您不能尝试“连接”它(总是在 Qt 文档中查找函数类型,如果它是一个信号,除了它的定义之外,它还会有一个 [signal]
符号。
它是一个事件处理程序,它需要一个事件 instance 作为参数,而不是类型。
然后,切换组框的复选框会更改其 children 的状态,而不是组框的状态,因此您永远不会收到EnabledChange
切换时的事件。
如果您考虑一下,这是很明显的:如果 whole 组框在切换其复选框时被禁用,您将永远无法再次单击它来启用它,因为输入事件默认情况下会被忽略禁用的小部件,标题 和 复选框也将显示为禁用。
可能的解决方案
有多种可能的解决方案,包括子类化 QFrame 并绘制复选框和标题,但使其符合当前样式将非常(而且不必要)困难。
最好的选择通常是 less 更改默认行为的选择。
在这种情况下,我的建议是做两件事:
- 连接到组框的
toggled
信号并覆盖默认行为(恢复启用状态,除非 明确 为该小部件设置); - 覆盖
paintEvent
并在实际绘制之前更改用于样式和画家的 QStyleOptionGroupBoxstate
;
由于状态是在内部为组框定义的,我们需要覆盖它的值:initStyleOption()
添加选项的状态标志 State_On
当复选框被(理论上)选中时,或 State_Off
未选中时(因此,无论 child 小部件是否启用),但我们必须根据复选框状态设置选项状态。为此,我们必须选中所有复选框并验证是否选中了 all,是否选中了 any,或者 none 是.
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_()