将 QGroupBox 复选框可视化更改为扩展器
Changing QGroupBox checkbox visual to an expander
我已经将 QGroupBox 的复选框的行为修改为 hide/show 组的子项,有效地充当扩展器。它运行良好,但唯一的问题是默认复选框图标看起来像是 enabling/disabling 组的,而不是扩展它。我想用扩展器样式的图标代替它。
我发现这个 post 几乎可以回答我的问题(最终我可能不得不使用该解决方案):。问题是那里提供的答案建议使用自定义复选框图像,而我想使用内置的 OS-specific 扩展器,比如 QTreeView 中的扩展器,它在我的 PC 上看起来像这样:
这可能吗?扩展器是否被视为程式化的复选框或完全不同的东西?我是 Qt 的新手,所以我不太熟悉样式的处理方式。这是我可以查询的东西吗?如果可以,它是否与 QCheckBox 样式兼容?除了 enabling/disabling 它们之外,我在 QTreeView 文档页面上找不到太多关于扩展器的信息,我目前正在尝试挖掘 QTreeView.cpp 源代码以弄清楚它们是如何工作的。
更新
从 eyllanesc 的回答开始,我离解决方案越来越近了,但是我遇到了一些覆盖 QProxyStyle 方法的问题。我正在尝试执行以下操作:
class GroupBoxExpanderStyle(QtWidgets.QProxyStyle):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._replaceCheckboxWithExpander = False
def drawComplexControl(self, control, option, painter, widget):
try:
if control == QtWidgets.QStyle.CC_GroupBox and widget.isCheckable():
self._replaceCheckboxWithExpander = True
super().drawComplexControl(control, option, painter, widget)
finally:
self._replaceCheckboxWithExpander = False
def drawPrimitive(self, element, option, painter, widget):
if element == QtWidgets.QStyle.PE_IndicatorCheckBox and self._replaceCheckboxWithExpander:
indicatorBranchOption = ... # Set up options for drawing PE_IndicatorBranch
super().drawPrimitive(QtWidgets.QStyle.PE_IndicatorBranch, indicatorBranchOption, painter, widget)
else:
super().drawPrimitive(element, option, painter, widget)
在 this code. The issue I'm running into is that my overridden drawPrimitive()
function isn't being called at all for QGroupBox widgets... but it is called for other widgets with that same proxy style applied! Looking in the drawComplexControl()
function of qcommonstyle.cpp 中似乎已经完成了类似的事情,CC_GroupBox
案例正在调用
proxy()->drawPrimitive(PE_IndicatorCheckBox, &box, p, widget);
绘制复选框,所以我不明白为什么我的重写函数不是 运行。
我不太确定如何进行调试,因为我无法进入 C++ 代码以查看实际调用的内容。谁能提供任何建议来帮助我弄清楚为什么我的覆盖 drawPrimitive()
没有被调用?
更新二:
我解开了为什么我的覆盖 drawPrimitive()
没有被调用的谜团 - 这是因为我的应用程序使用根级样式 sheet,这导致 QStyleSheetStyle
用作小部件的活动样式。 QStyleSheetStyle
直接为 CC_GroupBox
调用它自己的 drawPrimitive()
方法,而不是调用 proxy()->drawPrimitive()
- 这对我来说似乎是一个错误,事实上 this Qt bug 指出样式 sheets 不能与 QProxyStyle
很好地混合。我将尝试摆脱使用样式 sheets.
eyllanesc 的技术适用于 Fusion 风格,所以我接受了他的回答,但它与其他风格不兼容。
一个可能的解决方案是实现 QProxyStyle:
from PySide2 import QtCore, QtGui, QtWidgets
class GroupBoxProxyStyle(QtWidgets.QProxyStyle):
def subControlRect(self, control, option, subControl, widget):
ret = super(GroupBoxProxyStyle, self).subControlRect(
control, option, subControl, widget
)
if (
control == QtWidgets.QStyle.CC_GroupBox
and subControl == QtWidgets.QStyle.SC_GroupBoxLabel
and widget.isCheckable()
):
r = self.subControlRect(
QtWidgets.QStyle.CC_GroupBox,
option,
QtWidgets.QStyle.SC_GroupBoxCheckBox,
widget,
)
ret.adjust(r.width(), 0, 0, 0)
return ret
def drawComplexControl(self, control, option, painter, widget):
is_group_box = False
if control == QtWidgets.QStyle.CC_GroupBox and widget.isCheckable():
option.subControls &= ~QtWidgets.QStyle.SC_GroupBoxCheckBox
is_group_box = True
super(GroupBoxProxyStyle, self).drawComplexControl(
control, option, painter, widget
)
if is_group_box and widget.isCheckable():
opt = QtWidgets.QStyleOptionViewItem()
opt.rect = self.proxy().subControlRect(
QtWidgets.QStyle.CC_GroupBox,
option,
QtWidgets.QStyle.SC_GroupBoxCheckBox,
widget,
)
opt.state = QtWidgets.QStyle.State_Children
opt.state |= (
QtWidgets.QStyle.State_Open
if widget.isChecked()
else QtWidgets.QStyle.State_None
)
self.drawPrimitive(
QtWidgets.QStyle.PE_IndicatorBranch, opt, painter, widget
)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
style = GroupBoxProxyStyle(app.style())
app.setStyle(style)
w = QtWidgets.QGroupBox(title="Exclusive Radio Buttons")
w.setCheckable(True)
vbox = QtWidgets.QVBoxLayout()
for text in ("Radio button 1", "Radio button 2", "Radio button 3"):
radiobutton = QtWidgets.QRadioButton(text)
vbox.addWidget(radiobutton)
vbox.addStretch(1)
w.setLayout(vbox)
w.resize(320, 240)
w.show()
sys.exit(app.exec_())
更新:
OP提供的code到Python的转换如下:
from PySide2 import QtCore, QtGui, QtWidgets
class GroupBoxProxyStyle(QtWidgets.QProxyStyle):
def drawPrimitive(self, element, option, painter, widget):
if element == QtWidgets.QStyle.PE_IndicatorCheckBox and isinstance(
widget, QtWidgets.QGroupBox
):
super().drawPrimitive(
QtWidgets.QStyle.PE_IndicatorArrowDown
if widget.isChecked()
else QtWidgets.QStyle.PE_IndicatorArrowRight,
option,
painter,
widget,
)
else:
super().drawPrimitive(element, option, painter, widget)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
style = GroupBoxProxyStyle(app.style())
app.setStyle(style)
w = QtWidgets.QGroupBox(title="Exclusive Radio Buttons")
w.setCheckable(True)
vbox = QtWidgets.QVBoxLayout()
for text in ("Radio button 1", "Radio button 2", "Radio button 3"):
radiobutton = QtWidgets.QRadioButton(text)
vbox.addWidget(radiobutton)
vbox.addStretch(1)
w.setLayout(vbox)
w.resize(320, 240)
w.show()
sys.exit(app.exec_())
我已经将 QGroupBox 的复选框的行为修改为 hide/show 组的子项,有效地充当扩展器。它运行良好,但唯一的问题是默认复选框图标看起来像是 enabling/disabling 组的,而不是扩展它。我想用扩展器样式的图标代替它。
我发现这个 post 几乎可以回答我的问题(最终我可能不得不使用该解决方案):
这可能吗?扩展器是否被视为程式化的复选框或完全不同的东西?我是 Qt 的新手,所以我不太熟悉样式的处理方式。这是我可以查询的东西吗?如果可以,它是否与 QCheckBox 样式兼容?除了 enabling/disabling 它们之外,我在 QTreeView 文档页面上找不到太多关于扩展器的信息,我目前正在尝试挖掘 QTreeView.cpp 源代码以弄清楚它们是如何工作的。
更新
从 eyllanesc 的回答开始,我离解决方案越来越近了,但是我遇到了一些覆盖 QProxyStyle 方法的问题。我正在尝试执行以下操作:
class GroupBoxExpanderStyle(QtWidgets.QProxyStyle):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._replaceCheckboxWithExpander = False
def drawComplexControl(self, control, option, painter, widget):
try:
if control == QtWidgets.QStyle.CC_GroupBox and widget.isCheckable():
self._replaceCheckboxWithExpander = True
super().drawComplexControl(control, option, painter, widget)
finally:
self._replaceCheckboxWithExpander = False
def drawPrimitive(self, element, option, painter, widget):
if element == QtWidgets.QStyle.PE_IndicatorCheckBox and self._replaceCheckboxWithExpander:
indicatorBranchOption = ... # Set up options for drawing PE_IndicatorBranch
super().drawPrimitive(QtWidgets.QStyle.PE_IndicatorBranch, indicatorBranchOption, painter, widget)
else:
super().drawPrimitive(element, option, painter, widget)
在 this code. The issue I'm running into is that my overridden drawPrimitive()
function isn't being called at all for QGroupBox widgets... but it is called for other widgets with that same proxy style applied! Looking in the drawComplexControl()
function of qcommonstyle.cpp 中似乎已经完成了类似的事情,CC_GroupBox
案例正在调用
proxy()->drawPrimitive(PE_IndicatorCheckBox, &box, p, widget);
绘制复选框,所以我不明白为什么我的重写函数不是 运行。
我不太确定如何进行调试,因为我无法进入 C++ 代码以查看实际调用的内容。谁能提供任何建议来帮助我弄清楚为什么我的覆盖 drawPrimitive()
没有被调用?
更新二:
我解开了为什么我的覆盖 drawPrimitive()
没有被调用的谜团 - 这是因为我的应用程序使用根级样式 sheet,这导致 QStyleSheetStyle
用作小部件的活动样式。 QStyleSheetStyle
直接为 CC_GroupBox
调用它自己的 drawPrimitive()
方法,而不是调用 proxy()->drawPrimitive()
- 这对我来说似乎是一个错误,事实上 this Qt bug 指出样式 sheets 不能与 QProxyStyle
很好地混合。我将尝试摆脱使用样式 sheets.
eyllanesc 的技术适用于 Fusion 风格,所以我接受了他的回答,但它与其他风格不兼容。
一个可能的解决方案是实现 QProxyStyle:
from PySide2 import QtCore, QtGui, QtWidgets
class GroupBoxProxyStyle(QtWidgets.QProxyStyle):
def subControlRect(self, control, option, subControl, widget):
ret = super(GroupBoxProxyStyle, self).subControlRect(
control, option, subControl, widget
)
if (
control == QtWidgets.QStyle.CC_GroupBox
and subControl == QtWidgets.QStyle.SC_GroupBoxLabel
and widget.isCheckable()
):
r = self.subControlRect(
QtWidgets.QStyle.CC_GroupBox,
option,
QtWidgets.QStyle.SC_GroupBoxCheckBox,
widget,
)
ret.adjust(r.width(), 0, 0, 0)
return ret
def drawComplexControl(self, control, option, painter, widget):
is_group_box = False
if control == QtWidgets.QStyle.CC_GroupBox and widget.isCheckable():
option.subControls &= ~QtWidgets.QStyle.SC_GroupBoxCheckBox
is_group_box = True
super(GroupBoxProxyStyle, self).drawComplexControl(
control, option, painter, widget
)
if is_group_box and widget.isCheckable():
opt = QtWidgets.QStyleOptionViewItem()
opt.rect = self.proxy().subControlRect(
QtWidgets.QStyle.CC_GroupBox,
option,
QtWidgets.QStyle.SC_GroupBoxCheckBox,
widget,
)
opt.state = QtWidgets.QStyle.State_Children
opt.state |= (
QtWidgets.QStyle.State_Open
if widget.isChecked()
else QtWidgets.QStyle.State_None
)
self.drawPrimitive(
QtWidgets.QStyle.PE_IndicatorBranch, opt, painter, widget
)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
style = GroupBoxProxyStyle(app.style())
app.setStyle(style)
w = QtWidgets.QGroupBox(title="Exclusive Radio Buttons")
w.setCheckable(True)
vbox = QtWidgets.QVBoxLayout()
for text in ("Radio button 1", "Radio button 2", "Radio button 3"):
radiobutton = QtWidgets.QRadioButton(text)
vbox.addWidget(radiobutton)
vbox.addStretch(1)
w.setLayout(vbox)
w.resize(320, 240)
w.show()
sys.exit(app.exec_())
更新:
OP提供的code到Python的转换如下:
from PySide2 import QtCore, QtGui, QtWidgets
class GroupBoxProxyStyle(QtWidgets.QProxyStyle):
def drawPrimitive(self, element, option, painter, widget):
if element == QtWidgets.QStyle.PE_IndicatorCheckBox and isinstance(
widget, QtWidgets.QGroupBox
):
super().drawPrimitive(
QtWidgets.QStyle.PE_IndicatorArrowDown
if widget.isChecked()
else QtWidgets.QStyle.PE_IndicatorArrowRight,
option,
painter,
widget,
)
else:
super().drawPrimitive(element, option, painter, widget)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
style = GroupBoxProxyStyle(app.style())
app.setStyle(style)
w = QtWidgets.QGroupBox(title="Exclusive Radio Buttons")
w.setCheckable(True)
vbox = QtWidgets.QVBoxLayout()
for text in ("Radio button 1", "Radio button 2", "Radio button 3"):
radiobutton = QtWidgets.QRadioButton(text)
vbox.addWidget(radiobutton)
vbox.addStretch(1)
w.setLayout(vbox)
w.resize(320, 240)
w.show()
sys.exit(app.exec_())