如何在 QML 应用程序中生成 KeyEvents?

How to generate KeyEvents in QML application?

我正在使用 PyQt5 和 QML 一起创建一个应用程序。我需要能够从 PyQT 到我的 QML 对象或从 QML 本身模拟键盘板事件。

我正在使用 QQmlApplicationEngine 加载我的 QML,我正在使用 Python 中的 "back end" QObject 连接到 QML 中的信号和槽。

app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
backend = Backend()
engine.rootContext().setContextProperty("backend", backend)
engine.load('./qml/main.qml')
app.setEngine(engine)

稍后我尝试发送一个按键事件:

app.sendEvent(engine.rootObjects()[0].focusObject(), QKeyEvent(QEvent.KeyRelease, Qt.Key_Down, Qt.NoModifier))

在我的 QML 中,我有一个具有焦点的列表视图。如果我按键盘上的上下键,列表中的焦点项目会按预期更改。但是,当我尝试使用上面的代码发送按键事件时,列表视图没有反应。

打印时engine.rootObjects()[0]是一个QObject。

QML 片段:

ApplicationWindow {
    // list model here
    // list delegate here
    ListView {
        id: menuView
        anchors.fill: parent
        focus: true
        model: menuModel
        delegate: menuDelegate
        keyNavigationEnabled: true
        keyNavigationWraps: true
   }
}

或者,我想知道是否可以通过与 ApplicationWindow 对象的 activeFocusItem 交互从 QML 本身生成一个按键事件?我也没能让这个工作。

您有 2 个错误:

  • focusObject() 方法 returns 具有焦点的 QObject,在您的特定情况下,它是 ListView 委托的项目之一,尽管接收鼠标事件不会改变所选项目,因为只有 ListView 可以。

  • 如果要发送鼠标事件,必须先发送KeyPress,否则永远不会触发keyRelease方法,虽然很多时候只需要第一个事件

考虑到以上,解决方法是除了发送QEvent::KeyPress外,直接将事件发送给ListView,或者发送给另一个转发给ListView的对象,比如window。在以下使用计时器的示例中,事件将分别从 python 和 QML 发送到 window 或对象:

from functools import partial
from PyQt5 import QtCore, QtGui, QtQml


class KeyboardBackend(QtCore.QObject):
    @QtCore.pyqtSlot(QtCore.QObject)
    def moveUp(self, obj):
        print("up")
        event = QtGui.QKeyEvent(
            QtCore.QEvent.KeyPress, QtCore.Qt.Key_Up, QtCore.Qt.NoModifier
        )
        QtCore.QCoreApplication.sendEvent(obj, event)

    @QtCore.pyqtSlot(QtCore.QObject)
    def moveDown(self, obj):
        print("down")
        event = QtGui.QKeyEvent(
            QtCore.QEvent.KeyPress, QtCore.Qt.Key_Down, QtCore.Qt.NoModifier
        )
        QtCore.QCoreApplication.sendEvent(obj, event)


if __name__ == "__main__":
    import os
    import sys

    app = QtGui.QGuiApplication(sys.argv)
    engine = QtQml.QQmlApplicationEngine()

    keyboard_backed = KeyboardBackend()
    engine.rootContext().setContextProperty("keyboard_backed", keyboard_backed)
    file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "main.qml")
    engine.load(QtCore.QUrl.fromLocalFile(file))

    if not engine.rootObjects():
        sys.exit(-1)
    root = engine.rootObjects()[0]
    timer = QtCore.QTimer(timeout=partial(keyboard_backed.moveUp, root), interval=1000)
    QtCore.QTimer.singleShot(500, timer.start)
    sys.exit(app.exec())
import QtQuick 2.12
import QtQuick.Controls 2.12

ApplicationWindow {
    id: root
    visible: true
    width: 640
    height: 480
    ListModel {
        id: menuModel
        Component.onCompleted:{
            ['A', 'B', 'C', 'D'].forEach(function(letter) {
                menuModel.append({"name": letter})
            });
        }
    }
    Component{
        id: menuDelegate
        Text {
            text: name
        }
    }
    Timer{
        interval: 1000; running: true; repeat: true
        onTriggered: keyboard_backed.moveDown(lv)
    }
    ListView {
        id: lv
        anchors.top: parent.top
        focus: true
        model: menuModel
        delegate: menuDelegate
        keyNavigationEnabled: true
        keyNavigationWraps: true
        highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
        height: 100
    }
}

在查看 QtGamepad class 如何生成按键事件后,这最终成功了:

QGuiApplication.sendEvent(app.focusWindow(), QKeyEvent(QEvent.KeyPress, Qt.Key_Down, Qt.NoModifier))

现在我的 QML 应用程序的响应方式与用户按下某个键的方式相同。