我想让 QComboBox 在程序启动时自动打开其下拉菜单,无需单击鼠标,并且菜单应无限期保持打开状态,并突出显示一个单元格。
我想这样做是因为我正在编写一个小部件来自定义我正在编写的应用程序的每个 GUI 组件的样式表,并且仅样式表就有数百行。我想让一个虚拟 QComboBox 保持打开状态,以便让用户预览样式结果。并且应该禁用虚拟 QComboBox。
我已经搜索了以编程方式打开下拉菜单的方法,但没有找到适用于 PyQt6 的方法。我自己也尝试过一些方法,但没有效果。
最小可重现示例:
import sys
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
app = QApplication([])
app.setStyle("Fusion")
class ComboBox(QComboBox):
def __init__(self, texts):
super().__init__()
self.addItems(texts)
self.setContentsMargins(3, 3, 3, 3)
self.setFixedWidth(100)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.centralwidget = QWidget(self)
self.setCentralWidget(self.centralwidget)
self.vbox = QVBoxLayout(self.centralwidget)
box = ComboBox(["Lorem", "Ipsum", "Dolor"])
self.vbox.addWidget(box)
box.showPopup()
box.DroppedDown = True
box.setProperty("DroppedDown", True)
box.raise_()
self.setStyleSheet("""
QMainWindow {
background: #201a33;
}
QComboBox {
border: 3px outset #552b80;
border-radius: 8px;
background: #422e80;
color: #c000ff;
selection-background-color: #7800d7;
selection-color: #ffb2ff;
}
QComboBox::drop-down {
border: 0px;
padding: 0px 0px 0px 0px;
}
QComboBox::down-arrow {
image: url(D:/Asceteria/downarrow.png);
width: 10px;
height: 10px;
}
QComboBox QAbstractItemView {
border: 2px groove #8000c0;
border-radius: 6px;
background: #422e80;
}""")
window = Window()
window.show()
sys.exit(app.exec())
组合框仅在单击时打开其下拉菜单,我希望它自动打开而无需单击它。我该怎么做?
我完全有能力作弊并使用 QGroupBox 和 QLabels 的组合来制作模型,但我想知道是否有正确的方法来做到这一点。
在组合框初始化时调用
showPopup()
是不合适的,特别是考虑到组合框正在添加到尚未显示的 container 中。
即使忽略这一点,该尝试还存在另外两个问题:
QPoint(0, 0)
) 的默认几何形状,并且根据其窗口级别设置大小:如果它有父级,则它是 100x30
,否则它被认为是顶层窗口带有 640x480
;由于 QComboBox 使用其 current 可见大小来调整弹出窗口几何形状,这意味着当组合尚不可见(并且位于窗口和屏幕坐标中)时显示的弹出窗口将具有不一致的几何形状;Popup
窗口标志;该标志会影响整个系统的键盘和鼠标管理,因此所有这些事件都会被它接收,从而防止与任何其他小部件交互,包括属于其他程序甚至操作系统的小部件;hidePopup()
,通常在需要隐藏视图时从组合中调用。不幸的是,这并不能解决上述所有问题。例如,当单击窗口外部或失去焦点时(取决于操作系统),标记为 Popup
的窗口可能会被关闭。
虽然可以通过使用正确设置的窗口标志和属性来防止这些问题,但除非非常小心,否则这样做可能不是很可靠,并且不同的操作系统版本/功能仍然可能会产生意外的结果,因此覆盖所有这些情况将除了不可靠之外,还很痛苦。
幸运的是,有一个更简单的选择:使用图形视图框架。
该框架允许使用“代理”图形项将标准 QWidget“嵌入”到 QGraphicsScene 中。当 QComboBox 添加到场景中时,其弹出视图也会显示在场景中。
这对于这种情况非常有用,因为我们可以完全忽略与操作系统焦点/输入事件相关的问题并简化一切:我们只需要正确地忽略所有会导致弹出窗口隐藏的事件和事件处理程序。这是通过以下方式实现的:
hidePopup()
电话;QSS = '''
QComboBox {
border: 3px outset #552b80;
border-radius: 8px;
background: #422e80;
color: #c000ff;
selection-background-color: #7800d7;
selection-color: #ffb2ff;
}
QComboBox::drop-down {
border: 0px;
padding: 0px 0px 0px 0px;
}
QComboBox::down-arrow {
image: url(D:/Asceteria/downarrow.png);
width: 10px;
height: 10px;
}
QComboBox QAbstractItemView {
border: 2px groove #8000c0;
border-radius: 6px;
background: #422e80;
}
'''.strip()
class ComboWithFixedPopup(QComboBox):
def __init__(self):
super().__init__()
self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.addItems(('Item 1', 'Item 2', 'Item 3'))
self.setCurrentIndex(1)
def eventFilter(self, obj, event):
eventType = event.type()
if eventType in (
event.Type.KeyPress, event.Type.KeyRelease,
event.Type.MouseButtonPress, event.Type.MouseMove,
event.Type.MouseButtonRelease,
event.Type.Wheel, event.Type.Hide
):
if eventType == event.Type.Hide:
obj.show()
return True
elif eventType == event.Type.FocusOut:
QTimer.singleShot(0, obj.setFocus)
return True
return super().eventFilter(obj, event)
def hidePopup(self):
pass
def keyPressEvent(self, event):
pass
def showPopup(self):
super().showPopup()
view = self.view()
container = view.parent()
proxy = container.graphicsProxyWidget()
if not proxy:
return
view.installEventFilter(self)
view.viewport().installEventFilter(self)
proxy.installEventFilter(self)
QApplication.processEvents()
#
# # Some styles show the popup *over* the combo, but some implementations
# # may override this behavior. If you want to follow the current style,
# # ignore the following, which instead always shows the popup above or
# # below the combobox in order to always show it.
## return
# comboRect = self.rect().translated(self.mapTo(self.window(), QPoint()))
# windowProxy = self.window().graphicsProxyWidget()
# comboRect = QRectF(comboRect).translated(windowProxy.scenePos())
# viewRect = container.graphicsProxyWidget().geometry()
# if viewRect.intersects(comboRect):
# if comboRect.y() > viewRect.y():
# print('cippo', comboRect.y(), viewRect.y())
# viewRect.moveBottom(comboRect.y())
# else:
# viewRect.moveTop(comboRect.bottom())
# proxy.setGeometry(viewRect)
def wheelEvent(self, event):
pass
class GraphicsPreview(QGraphicsView):
def __init__(self):
scene = QGraphicsScene()
super().__init__(scene)
self.combo = ComboWithFixedPopup()
self.container = QWidget()
self.container.resize(400, 400)
layout = QVBoxLayout(self.container)
layout.addWidget(self.combo)
scene.addWidget(self.container)
self.setSceneRect(scene.sceneRect())
def showEvent(self, event):
super().showEvent(event)
self.combo.setCurrentIndex(1)
self.combo.showPopup()
def updateQSS(self, qss):
self.container.setStyleSheet(qss)
class QSSEditor(QPlainTextEdit):
_shown = False
def __init__(self):
super().__init__()
font = QFont()
font.setFamily('monospace')
self.document().setDefaultFont(font)
def sizeHint(self):
hint = super().sizeHint()
if self.toPlainText().strip():
fm = QFontMetrics(self.document().defaultFont())
width = fm.size(0, self.toPlainText()).width()
hint.setWidth(int(
width + self.document().documentMargin() * 2
+ self.frameWidth() * 2
+ self.verticalScrollBar().sizeHint().width()
))
return hint
class QSSPreview(QWidget):
def __init__(self):
super().__init__()
self.editor = QSSEditor()
self.graphicsPreview = GraphicsPreview()
layout = QHBoxLayout(self)
layout.addWidget(self.editor)
layout.addWidget(self.graphicsPreview)
self.editor.textChanged.connect(lambda:
self.graphicsPreview.updateQSS(self.editor.toPlainText()))
self.editor.setPlainText(QSS)
if __name__ == '__main__':
app = QApplication(sys.argv)
# app.setStyle('windows')
window = QSSPreview()
window.show()
sys.exit(app.exec())
请注意,某些样式可能会在组合上显示
弹出窗口,从而使其不可见。仍然可以通过检查场景坐标的几何图形并最终显示组合上方或下方的弹出窗口来解决此问题。