在 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_())

小部件必须接受焦点,因此必须设置适当的焦点策略(StrongFocusWheelFocus),但由于它是一个容器,您还应该将 focus proxy 设置为旋转,以便它处理它的焦点。

这样,通过使用箭头或 Tab 聚焦旋转框,菜单的键盘导航也将正常工作。

该动作还必须设置为菜单的活动动作,以防止菜单接受可能触发其他动作的事件,以及实现此目的的方法是在自旋上安装一个事件过滤器并检查 应该 使小部件操作成为活动的事件,例如 FocusInEnter 事件。

请注意,从用户体验的角度来看,这会导致一些不直观的结果:默认情况下,悬停 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().