在 QMenu 中按回车键时触发错误操作
Wrong action triggered when pressing enter in QMenu
我有一个带有复选框和旋转框的上下文菜单。两者都按预期工作,除了当我在旋转框中输入数字时,我倾向于点击 enter 来关闭菜单,但这也会切换复选框。有谁知道如何防止这种情况发生?
最小示例:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QFrame, QMenu, QAction, QWidgetAction, QFormLayout, QSpinBox
from PyQt5.QtCore import Qt
t = True
v = 0
class SpinAction(QWidgetAction):
def __init__(self, parent):
super(SpinAction, self).__init__(parent)
w = QWidget()
layout = QFormLayout(w)
self.spin = QSpinBox()
layout.addRow('value', self.spin)
w.setLayout(layout)
self.setDefaultWidget(w)
def _menu(position):
menu = QMenu()
test_action = QAction(text="Test", parent=menu, checkable=True)
test_action.setChecked(t)
test_action.toggled.connect(toggle_test)
spin_action = SpinAction(menu)
spin_action.spin.setValue(v)
spin_action.spin.valueChanged.connect(spin_changed)
menu.addAction(test_action)
menu.addAction(spin_action)
action = menu.exec_(w.mapToGlobal(position))
def toggle_test(val):
global t
t = val
def spin_changed(val):
global v
v = val
app = QApplication(sys.argv)
w = QFrame()
w.setContextMenuPolicy(Qt.CustomContextMenu)
w.customContextMenuRequested.connect(_menu)
w.show()
sys.exit(app.exec_())
小部件必须接受焦点,因此必须设置适当的焦点策略(StrongFocus
或 WheelFocus
),但由于它是一个容器,您还应该将 focus proxy 设置为旋转,以便它处理它的焦点。
这样,通过使用箭头或 Tab 聚焦旋转框,菜单的键盘导航也将正常工作。
该动作还必须设置为菜单的活动动作,以防止菜单接受可能触发其他动作的事件,以及实现此目的的方法是在自旋上安装一个事件过滤器并检查 应该 使小部件操作成为活动的事件,例如 FocusIn
和 Enter
事件。
请注意,从用户体验的角度来看,这会导致一些不直观的结果:默认情况下,悬停 normal QAction 使其处于活动状态,但用户可能想移动鼠标远离旋转框,以便在没有鼠标 的情况下对其进行编辑,并且通过这样做可以激活另一个动作;与此同时,出于显而易见的原因,旋转仍应保持焦点。结果是当微调框具有焦点时,其他操作可能看起来在视觉上被选中。对此没有明显的解决方案,这就是为什么应始终谨慎选择用于 QWidgetActions 的小部件。
class SpinAction(QWidgetAction):
FocusEvents = QtCore.QEvent.FocusIn, QtCore.QEvent.Enter
ActivateEvents = QtCore.QEvent.KeyPress, QtCore.QEvent.Wheel)
WatchedEvents = FocusEvents + ActivateEvents
def __init__(self, parent):
# ...
w.setFocusPolicy(self.spin.focusPolicy())
w.setFocusProxy(self.spin)
self.spin.installEventFilter(self)
def eventFilter(self, obj, event):
if obj == self.spin and event.type() in self.WatchedEvents:
if isinstance(self.parent(), QtWidgets.QMenu):
self.parent().setActiveAction(self)
if event.type() in self.FocusEvents:
self.spin.setFocus()
return super().eventFilter(obj, event)
注意:obj == self.spin
条件是必需的,因为 QWidgetAction 已经安装了事件过滤器。一个更优雅的解决方案是创建一个仅用于该目的的 QObject 子类,并且仅覆盖 eventFilter()
.
我有一个带有复选框和旋转框的上下文菜单。两者都按预期工作,除了当我在旋转框中输入数字时,我倾向于点击 enter 来关闭菜单,但这也会切换复选框。有谁知道如何防止这种情况发生?
最小示例:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QFrame, QMenu, QAction, QWidgetAction, QFormLayout, QSpinBox
from PyQt5.QtCore import Qt
t = True
v = 0
class SpinAction(QWidgetAction):
def __init__(self, parent):
super(SpinAction, self).__init__(parent)
w = QWidget()
layout = QFormLayout(w)
self.spin = QSpinBox()
layout.addRow('value', self.spin)
w.setLayout(layout)
self.setDefaultWidget(w)
def _menu(position):
menu = QMenu()
test_action = QAction(text="Test", parent=menu, checkable=True)
test_action.setChecked(t)
test_action.toggled.connect(toggle_test)
spin_action = SpinAction(menu)
spin_action.spin.setValue(v)
spin_action.spin.valueChanged.connect(spin_changed)
menu.addAction(test_action)
menu.addAction(spin_action)
action = menu.exec_(w.mapToGlobal(position))
def toggle_test(val):
global t
t = val
def spin_changed(val):
global v
v = val
app = QApplication(sys.argv)
w = QFrame()
w.setContextMenuPolicy(Qt.CustomContextMenu)
w.customContextMenuRequested.connect(_menu)
w.show()
sys.exit(app.exec_())
小部件必须接受焦点,因此必须设置适当的焦点策略(StrongFocus
或 WheelFocus
),但由于它是一个容器,您还应该将 focus proxy 设置为旋转,以便它处理它的焦点。
这样,通过使用箭头或 Tab 聚焦旋转框,菜单的键盘导航也将正常工作。
该动作还必须设置为菜单的活动动作,以防止菜单接受可能触发其他动作的事件,以及实现此目的的方法是在自旋上安装一个事件过滤器并检查 应该 使小部件操作成为活动的事件,例如 FocusIn
和 Enter
事件。
请注意,从用户体验的角度来看,这会导致一些不直观的结果:默认情况下,悬停 normal QAction 使其处于活动状态,但用户可能想移动鼠标远离旋转框,以便在没有鼠标 的情况下对其进行编辑,并且通过这样做可以激活另一个动作;与此同时,出于显而易见的原因,旋转仍应保持焦点。结果是当微调框具有焦点时,其他操作可能看起来在视觉上被选中。对此没有明显的解决方案,这就是为什么应始终谨慎选择用于 QWidgetActions 的小部件。
class SpinAction(QWidgetAction):
FocusEvents = QtCore.QEvent.FocusIn, QtCore.QEvent.Enter
ActivateEvents = QtCore.QEvent.KeyPress, QtCore.QEvent.Wheel)
WatchedEvents = FocusEvents + ActivateEvents
def __init__(self, parent):
# ...
w.setFocusPolicy(self.spin.focusPolicy())
w.setFocusProxy(self.spin)
self.spin.installEventFilter(self)
def eventFilter(self, obj, event):
if obj == self.spin and event.type() in self.WatchedEvents:
if isinstance(self.parent(), QtWidgets.QMenu):
self.parent().setActiveAction(self)
if event.type() in self.FocusEvents:
self.spin.setFocus()
return super().eventFilter(obj, event)
注意:obj == self.spin
条件是必需的,因为 QWidgetAction 已经安装了事件过滤器。一个更优雅的解决方案是创建一个仅用于该目的的 QObject 子类,并且仅覆盖 eventFilter()
.