QML 列表视图和密钥导航 - 处理单个密钥事件

QML ListView and key navigation - handling of single key events

本文关键字:密钥 处理 单个 事件 导航 视图 QML 列表      更新时间:2023-10-16

我正在使用Qt 5.6.2(Windows 10),并且有一个QObject派生的映射器,可以将QKeyEvent发送到当前关注的QML项目。我的基类有一个虚拟mapToKey()函数,然后在我想使用的各种映射器中覆盖该函数。例如,下面的映射函数是我LeftArrowMapper的一部分,并发送左箭头键事件:

void LeftArrowMapper::mapToKey()
{
// Retrieve root of QML context
QObject* root = engine->rootObjects()[0];
// Get currently focused item
QQuickItem *item = (root->property("activeFocusItem")).value<QQuickItem *>();
if (item == NULL) qDebug() << "No item";
// Create a key and a key event
Qt::Key key = Qt::Key_Left;
QKeyEvent* event = new QKeyEvent(QKeyEvent::KeyPress,
key,
Qt::NoModifier,
KeySequence(key).toString());
// Send it to the focused QML item
QCoreApplication::postEvent(item,event);
}

如果给定项目支持关键事件,它将做出相应的反应。否则,接收到的密钥事件将不会触发任何响应。我已经使用TextField对其进行了测试,这是一个基于Text的自定义组件(它只是使用通过 key 事件发送的文本更新text属性)等,我没有遇到任何问题。直到我开始与ListView合作...

我正在尝试使用键盘的左右箭头键导航ListView键,关键事件是使用我的映射器的mapToKey()功能人工创建的(这里的重点是:将硬件事件(如按真实按钮映射到Qt事件)。在Qt 5.6中,将focus属性设置为true可以使用键进行导航(在5.8和可能还有5.7中,keyNavigationEnabled属性必须设置为true;Qt 5.6中没有这样的属性)。

我的ListView如下所示:

ListView {
id: list
orientation: Qt.Horizontal
width: rootWindow.width
height: 50
Keys.onPressed: {
console.log("list: " + event.key + " : " + event.text)
if (event.key == Qt.Key_Left) console.log("Moving to the left");
else if (event.key == Qt.Key_Right) console.log("Moving to the right");
}
model: ListModel {
id: items
}
delegate: Rectangle {
width: 50
height: 50
Text {
anchors.centerIn: parent
text: "[" + name + "]"
}
}
Component.onCompleted: function() {
for(var i = 1; i <= 100; i++) {
items.append({"name" : i});
}
}
onFocusChanged: {
console.log("List focus has changed");
}
}

我发现的问题是这个QML组件处理关键事件的方式很奇怪。似乎它试图模仿基于动力学的滑动(考虑加速度),您可以在滚动浏览其中的项目时用鼠标进行。这意味着以下内容:

  • 如果长时间按住箭头键(约2 秒),则会触发滚动视图 - 启用后,它不会滚动单个项目,而是滚动多个项目
  • 触发上述滚动后,用户可以继续按箭头键继续快速滚动。此时,也可以为每个键事件滚动单个项目,但是...
  • 更改方向会导致上述行为的重置,因此用户需要执行另一个"触发滚动"事件才能向相反方向滚动
  • 触发
  • 多个单独的键事件也会触发滚动,但这次一次一个项目。此外,我上面提到的时间间隔似乎不适用于这里。用户需要按给定的箭头键 7 次(至少这是我计算的次数),然后才能选择列表中的下一个/上一个项目

似乎在内部,映射需要一定数量的按下和释放按住 X 秒键事件才能实际触发ListView中的某些响应。

现在我的一位同事为我们正在使用ListView创建了一个自定义键导航,它工作正常。但是,我想知道是否没有一些秘密设置(或者我们俩都错过了文档中的备忘录)来处理这个问题。我可以看到这种与键交互的应用(即模拟加速),但对于我正在处理的场景,我真的不需要它,或者至少现在不需要。另外,如果我想在某个时候添加伪加速部分,了解事情是如何完成的将非常有用。

当您按左/右箭头键时,基于动力学的轻扫ListView是来自ListView.highlight的动画。为了使其可见,我们可以将一些代码添加到您的列表中:

ListView {
id: list
Keys.onPressed: {
console.log(list.currentIndex);
//...
}
delegate: Item { // so we can see highlight in background
width: 50
height: 50
Text {
anchors.centerIn: parent
text: "[" + name + "]"
}
}
highlight: Rectangle {
width: 50
height: 50
color: "pink"
}
//...
}

按向右箭头键时,currentIndex会更改,突出显示将移动到该索引。如果像这样显式更改currentIndex,则可以看到相同的动画:

Button {
text: "go to 45"
onClicked: list.currentIndex = 45;
}

若要自定义动画,可以更改ListView.highlightMoveDurationListView.highlightMoveVelocity属性,或将ListView.highlightFollowsCurrentItem设置为 false 以将其关闭。您还可以按照ListView示例代码制作自己的高光动画。

似乎在内部映射需要一定数量的按下并释放或按住 X 秒键事件才能实际触发ListView中的某些响应。

在处理关键事件时,ListView没有魔力,它只是不断变化currentIndex,QML中的动画可以完美地处理它。