開發環境
- windows
- QtCreator 4.10.2(Community)
- C++和QML混合編輯
應用場景
應用程序分為登錄界面和主界面2個窗口。要求在主界面有全局鍵盤監控的功能,比如按ESC時,確認后退回到登錄窗口。
QML中的按鍵事件處理
三要素:
- focus :true //組件必須獲得焦點,只有在獲得焦點時,該組件的Key事件才有效
- Key.enabled:true //使能Key功能
- Key.onPress:{} //重寫Key按下的處理時間
舉個例子:我們假想這個Rect是主界面最外部的包圍框(root),即所有的界面元素都在這個Rect中。

Rectangle{ id: root width: 512 height: 512 color: "gray" focus: true Keys.enabled: true Keys.onPressed: { switch (event.key){ case Qt.Key_Left: //todo break; case Qt.Key_Right: //todo break; case Qt.Key_Up: //todo break; case Qt.Key_Down: //todo break; default: return; } event.accepted = true;//表示這些列出的按鍵事件已經處理, //不再往下傳遞 } }
按這個方法,假如主界面一開始加載完成時,獲得焦點的是這個Rect,那么當然可以實現我們的需求。但是用戶一旦在界面上操作一些獲取焦點的組件,使得這個Rect失去了焦點。那么這個全局監控鍵盤得到功能就無法實現了。如果想反復設置Rect的focus屬性,顯然不是一個好辦法。
Qt的事件過濾器
QML不方便完成的事情,我們自然想到了C++了。原則上講:QML負責界面邏輯,C++負責數據處理和功能實現。這邊博客給了我基本的思路:https://blog.csdn.net/weixin_34354945/article/details/92973701。
思路:實現一個過濾器類。這個類只要是繼承自QObject或其子類,它就有一個虛函數eventFilter。我們只要實現它,在其中捕獲按鈕事件,進行處理。其他事件放行。捕獲到按鍵事件時,C++發出信號,QML處理信號。只要把這個過濾器類安裝我們的主窗口就可以實現需求。
過濾器類
filterevent.h

#ifndef FILTEREVENT_H #define FILTEREVENT_H #include <QObject> #include <QDebug> class FilterEvent : public QObject { Q_OBJECT public: explicit FilterEvent(QObject *parent = nullptr); protected: protected: bool eventFilter(QObject *obj, QEvent *ev) override; signals: void myExited(); public slots: }; #endif // FILTEREVENT_H
filterevent.cpp

#include "filterevent.h" #include <qevent.h> FilterEvent::FilterEvent(QObject *parent) : QObject(parent) { } bool FilterEvent::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); switch (keyEvent->key()) { case Qt::Key_Escape: qDebug()<<"pressed Key_Escape"; emit myExited(); return true; case Qt::Key_Enter: qDebug()<<"pressed Key_Enter"; return true; } qDebug() << "Ate key press" << keyEvent->key(); } return false; }
安裝過濾器
在這里,我遇到了一個問題:這個過濾器的實例對象應該放在哪里?放在 main.cpp 的main函數中,我們是可以通過調用 installEventFilter 安裝到主窗口上,但是按鍵事件處理中的信號該怎么處理呢,信號的處理地點是要放在QML中的。
參照其它博客的寫法試了試,最后QML會報錯過濾器實例對象can not find(雖然名字是變藍色了...)。最后結合自己的經歷,想了另一個辦法。
- filterEvent實現在QML端,采用on+信號的形式處理按鍵事件中發出的信號
- main.cpp 中通過查找objectName的方式,得到主窗口和filterEvent 的指針
- 主窗口調用installEventFilter 函數安裝上filterEvent
首先要將過濾器類注冊到QML的元象樹系統中。第一個參數是自己定義的包名,要改成自己的,用到的時候QML端import。
qmlRegisterType<FilterEvent>("an.qt.UserDefine", 1, 0, "FilterEvent");
然后安裝
//安裝事件過濾器 QObject* mainRootItem = engine.rootObjects().at(1); QObject* filterEvent = mainRootItem->findChild<QObject*>("filterEvent"); mainRootItem->installEventFilter(filterEvent);
QML端
//事件過濾器 FilterEvent{ objectName: "filterEvent" //objectName用以元象樹查找元素 onMyExited: { console.log("filterEvent trigged"); //todo } }
補充說明
這里要補充說明的是:QML的對象是以樹的形式進行管理的。可以這樣理解:最外圍的最大的組件可以看成是根節點,它包含的組件呢,是根節點的分支。子節點再包含一些組件,就再分支。(個人對元象樹的理解,錯誤請指出)。然后我們可以通過 findChild<QObject*>("objectName") 的方式得到我們想要找的對象。比如可以進行一些信號與槽函數綁定。
安裝過濾器中的 mainRootItem 也是這樣獲得的。之所以是 at(1) 是因為我第一步加載的是登錄界面(0)。
信號與槽函數綁定舉例
QObject* logInRootItem = engine.rootObjects().at(0); QObject* logInButton = logInRootItem->findChild<QObject*>("logInButton");//根據qml中的objectName找到這個對象,獲取到它的指針 if(logInButton) { //連接QML元素中的信號到C++的槽函數 QObject::connect(logInButton,SIGNAL(logIn(QString)),&control,SLOT(showWindow(QString))); }