QML快捷键(Shortcut、Keys)

(Qt 的 按键输入都是在应用激活时才能触发的,所以要注册系统全局快捷键请用系统 API)

0.前言

一般情况下,QML 中一个按键事件的流程是这样的:

  • Qt接收按键动作并生成一个按键事件 KeyEvent
  • 如果 QQuickWindow 是 active 的,则将按键事件传递给它
  • 按键事件由  scene 传递到 activeFocus 的 Item,如果没有 activeFocus Item,则事件被忽略
  • 如果按键事件被接受,即 accepted=true,则传递结束,否则将一直往父节点传递,知道根节点或者被接受
  • 如果到达根节点,则忽略该 KeyEvent ,并继续常规的按键处理
  • (事实上,顺序为 QEvent::ShortcutOverride ->KeyPress->KeyRelease ,如果触发了快捷键,即 ShortcutOverride 被接受,那么后面的 press 和 release 就只有当前 focus Item 才知道,不会像一般情况那样 Keys.event 没 accepted 就一直往上传递)

但有时我们需要注册快捷键,来完成一些便捷操作,如按空格播放、暂停视频;如按上下加减数值等。QML 提供了两种类型用于处理按键事件,一是 Shortcut、Action 这种,可以注册应用或 Window 全局的快捷键;二是 Keys、KeyNavigation 这种对焦点 Item 或者 FocusScope 按键事件进行处理(类似 ListView 之类的部分组件本身也提供了简单的按键导航功能,ListView 的导航可以设置 keyNavigationEnabled=false 屏蔽)。

《青春》  ----  席慕容

所有的结局都已写好
所有的泪水也都已启程
却忽然忘了是怎麽样的一个开始
在那个古老的不再回来的夏日

无论我如何地去追索
年轻的你只如云影掠过
而你微笑的面容极浅极淡
逐渐隐没在日落後的群岚

遂翻开那发黄的扉页
命运将它装订得极为拙劣
含著泪 我一读再读
却不得不承认
青春是一本太仓促的书

1.Shortcut { }

Shortcut 可以设置当前 Window 或者整个应用的全局快捷键,先看下它的属性和信号:

属性:
1.autoRepeat : bool
    重复触发,如长按的情况,默认 true
2.context : enumeration
    处理当前Window还是整个应用的快捷键
    可以是 Qt.WindowShortcut (默认)或者 Qt.ApplicationShortcut
3.enabled : bool
    是否启用
4.nativeText : string
5.portableText : string
    sequence快捷设置对应的字符串,
    如设置 "delete" 则 nativeText 和 portableText 显示为 Del
6.sequence : keysequence
    单个快捷键或序列,可以是 QKeySequence::StandardKey,也可以是最多四个键组合的字符串
    如:"Ctrl+E,Ctrl+W" 先按 Ctrl+E,再按 Ctrl+W 触发
    如:StandardKey.Delete 删除键,具体平台映射按键值见文档
7.sequences : list<keysequence>
    可以触发的快捷键列表,每一个都可以触发
    如:["return","enter","space"]

信号:
1.activated()
    快捷键触发了
2.activatedAmbiguously()
    快捷键触发了,但是被多个 Shortcut 对象捕获,此时不会触发 activated(),
    多个 Shortcut 轮流触发 activatedAmbiguously()

使用也很简单

    Shortcut {
        //过滤整个应用
        context: Qt.ApplicationShortcut
        //快捷键
        //sequence: "left,right" //按了left再按right触发
        //sequences: ["left","right"] //按left或者right触发
        //sequences: ["return","enter"] //大小键盘回车
        sequences: ["+","delete","return","enter","space"]
        //只有一个Shortcut关注该快捷键则只触发onActivated
        //否则只触发onActivatedAmbiguously
        onActivated: {
            console.log('Shortcut 1 onActivated.')
        }
        onActivatedAmbiguously: {
            console.log('Shortcut 1 onActivatedAmbiguously.')
        }
    }

此外,Action 也可以设置快捷键( shortcut ),同一快捷键多处绑定后,也会轮流着触发  triggered() 。

2.Keys  

Keys 是作为附加属性来使用的,相当于过滤当前 Item 的按键事件。只要  Keys.press 和 Keys.release 没有被 accepted 或者被 Shortcut 处理,可以一直往上级传递,多个层级都可以获知该按键事件。流程为先触发 ShortcutOverride,该事件不会冒泡,如果 ShortcutOverride 没被 Shortcut 接收,那么后面的按键事件会往父级冒泡;要是 ShortcutOverride 被 Shortcut 处理了,那么后面的按键事件就不会触发 Keys.press 信号了。

看下它的属性和信号:

属性:
1.enabled : bool
    启用开关
2.forwardTo : list<Object>
    可以将按键转发给别的对象,一旦被列表中某个Item accepted,将不再转发给列表中往后的Item。
3.priority : enumeration
    设置 forwardTo 的优先级,
    Keys.BeforeItem(默认值)-在一般的按键处理之前处理该事件
    Keys.AfterItem-在一般的按键处理之后处理该事件
    要注意的是,event.accepted 就不再继续传递了

信号:
1.shortcutOverride(KeyEvent event)
    快捷键事件,如果想拦截某个按键的快捷键不被全局捕获,可以 accepted
    如:event.accepted=(event.key===Qt.Key_Space);
2.pressed(KeyEvent event)
    某个按键按下,accpted 可以拦截不往上传递事件
3.released(KeyEvent event)
    某个按键释放,accpted 可以拦截不往上传递事件
4.一堆具体按键的 pressed 信号

对于优先级参照文档:https://doc.qt.io/qt-5/qml-qtquick-keys.html (Key Handling Priorities)

If priority is Keys.BeforeItem (default) the order of key event processing is:

Items specified in forwardTo. 给fortwardTo 指定的 Item
specific key handlers, e.g. onReturnPressed. 特定的按键处理,如onReturnPressed
onPressed, onReleased handlers. 通用的按键处理,如 pressed、released
Item specific key handling, e.g. TextInput key handling. 组件特有的案件处理,如编辑输入
parent item. 往父节点传递

If priority is Keys.AfterItem the order of key event processing is:

Item specific key handling, e.g. TextInput key handling. 组件特有的案件处理,如编辑输入
Items specified in forwardTo. 给fortwardTo 指定的 Item
specific key handlers, e.g. onReturnPressed. 特定的按键处理,如onReturnPressed
onPressed, onReleased handlers. 通用的按键处理,如 pressed、released
parent item. 往父节点传递


If the event is accepted during any of the above steps, key propagation stops.
任意步骤 event.accepted = true, 则传递结束

简单的使用

            FocusScope {
                id: keys_scope
                anchors.fill: parent
                Keys.enabled: true //activeFocus的时候就能触发了
                Keys.onShortcutOverride: {
                    event.accepted=(event.key===Qt.Key_Space);
                } 
                Keys.onPressed: {
                    console.log('Keys onPressed.')
                }
                Keys.onReleased: {
                    console.log('Keys onReleased.')
                }
            }

3.实例:屏蔽 Button 空格触发 clicked

    Button {
        width: 100
        height: 30
        focus: true
        //被Shortcut捕获了就不会触发Keys.press
        //被Shortcut或者Keys.press接受了,5.15不会触发click,5.12、5.13会
        //5.12、5.13及以下需要接受Keys.release才不会触发click
        Keys.onSpacePressed: {
            console.log('Keys onSpacePressed')
            //5.15 accepted了press就不会触发点击,5.12、5.13需要设置release的accepted
            event.accepted=false;
        }
        Keys.onReleased: {
            console.log('Keys onReleased')
            event.accepted=(event.key===Qt.Key_Space); //低版本需要
        }
        //通过onShortcutOverride accepted可以拦截快捷键
        //Control组件的空格是在事件函数里处理的,所以不需要这个
        //Keys.onShortcutOverride: { }
        onClicked: {
            console.log('Button onClicked')
        }

        background: Rectangle {
            border.width: parent.activeFocus?2:1
            border.color: parent.focus?"red":"gray"
        }
    }

4.实例:屏蔽全局快捷键,使用当前 FocusScope 的快捷键处理

    Shortcut {
        sequence: "space"
        onActivated: {
            console.log('Shortcut onActivated')
        }
    }

    FocusScope {
        focus: true
        anchors.fill: parent
        Keys.onPressed: {
            console.log('Keys onPressed')
        }
        Keys.onReleased: {
            console.log('Keys onReleased')
        }
        //通过onShortcutOverride accepted可以拦截快捷键
        Keys.onShortcutOverride: {
            console.log('Keys onShortcutOverride')
            //accepted接受需要屏蔽的按键后就不再触发全局快捷键
            event.accepted=(event.key===Qt.Key_Space);
        }

        Item {

        }
    }

5.实例:eventFilter 过滤快捷键

虽然 Keys 可以对当前 activeFocus Item 过滤快捷键,但是组件比较多时,想要对某个焦点域进行快捷键操作,但又需要屏蔽全局快捷键怎么办呢?总不能每个组件都设置一边吧。我的办法就是使用 eventFilter 对 qApp 实例进行事件过滤,大致代码如下:

class KeysFilter : public QObject
{
    Q_OBJECT
public:
    //过滤类型
    enum FilterType {
        None = 0x00
        ,ShortcutOverride = 0x01
        ,KeyPress = 0x02
        ,KeyRelease = 0x04
        ,All = 0xFFFF
    };
    Q_ENUM(FilterType)
public:
    explicit KeysFilter(QObject *parent = nullptr);
    
    void setEnabled(bool enable)
    {
        if(enabled != enable){
            if(enable){
                qApp->installEventFilter(this);
            }else{
                qApp->removeEventFilter(this);
            }
    
            enabled = enable;
            emit enabledChanged();
        }
    }
    
    bool eventFilter(QObject *watched, QEvent *event) override
    {
        Q_UNUSED(watched)
        const int &&e_type = event->type();
        switch(e_type)
        {
        case QEvent::ShortcutOverride:
            if(filterType & FilterType::ShortcutOverride) goto Tab_KeyProcess;
            break;
        case QEvent::KeyPress:
            if(filterType & FilterType::KeyPress) goto Tab_KeyProcess;
            break;
        case QEvent::KeyRelease:
            if(filterType & FilterType::KeyRelease) goto Tab_KeyProcess;
            break;
        }
        return false;
    
        Tab_KeyProcess:
        {
            QKeyEvent *key_event = static_cast<QKeyEvent*>(event);
            if(key_event && filterKeys.contains(key_event->key())){
                //accepted之后,shortcut就不再处理了
                key_event->setAccepted(true);
    
                //下面的逻辑用不到,目前只把快捷键过滤
                if(acceptAutoRepeat || !key_event->isAutoRepeat()){
                    if(e_type == QEvent::KeyPress){
                        emit pressed(key_event->key());
                    }else if(e_type == QEvent::KeyRelease){
                        emit released(key_event->key());
                    }
                }
                return true;
            }
        }
        return false;
    }
    
private:
    //使能-eventFilter
    bool enabled = true;
    //使能-长按重复触发
    bool acceptAutoRepeat = false;
    //过滤类型
    FilterType filterType = FilterType::ShortcutOverride;
    //过滤的按键列表
    QList<int> filterKeys;
};

在焦点域激活时 install 过滤器,失去焦点时 remove 过滤器,减小对程序的效率影响。 

6.参考

文档:https://doc.qt.io/qt-5/qtquick-input-focus.html

文档:https://doc.qt.io/qt-5/qml-qtquick-keys.html 

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值