1. 程式人生 > 實用技巧 >Qt 6中的輸入事件

Qt 6中的輸入事件

Qt Quick中滑鼠和觸控事件的傳遞非常複雜,幾年前我們就清楚了我們需要重構事件繼承層次結構,為各種事件型別提供一些通用的API,以便可以將更多傳遞程式碼共享。在Qt 5.8中,我們添加了QQuickPointerEvent和關聯的型別,作為對可能的樣子進行原型製作的一種方法。它們是QObjects。從那時起,QQuickWindow一直在交付這些包裝器事件,這些包裝器事件將原始事件承載在內部。現在終於在Qt 6中,我們已經能夠完成QEvent重構,因此QQuickWindow不再需要包裝器。除此之外,我們還能夠新增一些功能並修復了一些錯誤。Qt 5中似乎很難解決的許多其他錯誤至少應在以後修復。

向QPointerEvent和QEventPoint問好

現在,繼承層次結構如下所示:


QPointerEvent是來自指標裝置(滑鼠,觸控式螢幕,平板電腦手寫筆)的所有事件的新抽象型別。它具有通用API,能夠以與裝置無關的方式處理所有這些問題。由於QTouchEvent可以在一個事件中攜帶多個接觸點,因此我們對這一概念進行了標準化:每個QPointerEvent都可能代表QEventPoint例項的叢集(即使大多數事件僅攜帶一個點),因此具有適當的API:points(),point(i)和pointCount()。
每個QInputEvent(包括QPointerEvent)都帶有指向它來自的QInputDevice的指標。

即使處理合成滑鼠事件,事件處理程式碼也可以以裝置特定的方式響應。

每個QEventPoint都有速度。Qt Quick在Qt 5中使用的卡爾曼濾波器已移至QtGui,因此無論事件傳遞到何處,都可以使用最近幾次移動的平均速度。這樣,無論速度來自哪個裝置,都可以實現對速度敏感的行為(例如,將快速拖動與快速拖動區別開來,或響應特定的運動方向)。對於這樣的目的,瞬時速度通常太不穩定,但是如果需要,可以將其計算為(globalPosition() - globalLastPosition()) / (timestamp() - lastTimestamp())

QSinglePointEvent是另一種抽象型別,用於標準化位置訪問器,該位置訪問器以前在QMouseEvent,QTabletEvent,QHoverEvent,QWheelEvent等中單獨實現且不一致。

它們現在都是浮點數。position()替換pos()和posF(),scenePosition()替換windowPos(),而globalPosition()替換screenPos()。舊的訪問器現在仍然存在,但是已被棄用:例如,僅由於處理QMouseEvent,Qt 5應用程式就不會遇到SC中斷。QEventPoint代替了QTouchEvent :: TouchPoint,但是出於源相容性的考慮,有一個“使用中”的宣告。

我已經分手了,並添加了新的qevent-accessors檢查,這可能為您省去一些麻煩:它可以自動應用“ fixits”來擺脫由於事件訪問器重新命名而引起的棄用警告。(是的,我們應該在這一變化的上游。)

C ++中與裝置無關的事件處理

現在,在Qt的各個地方,我們可以通過迭代QEventPoints或僅響應第一點來響應滑鼠,觸控和平板電腦事件(例如檢測單擊或拖動)。這是一個QQuickItem子類如何做到這一點的人為示例:

bool MyItem :: event(QEvent * ev)覆蓋{
如果(ev-> isPointerEvent()){
QPointerEvent * pev = static_cast <QPointerEvent *>(ev);
for(QEventPoint&point:pev-> points()){
開關(point.state()){
case QEventPoint :: State :: Pressed:
if(reactToPress(point.position()))
pev-> setExclusiveGrabber(point,this );
打破;
case QEventPoint :: State :: Updated:
...
}
}
返回true;
}
return QQuickItem :: event(ev);
}

例如,QQuickFlickable :: childMouseEventFilter()以這種方式工作。這產生了一個有趣的結果:

可滑動的手柄現在觸控!

Qt 5的Flickable只能處理實際的滑鼠事件和合成的滑鼠事件,這涉及到許多開放的錯誤。Qt只支援一隻滑鼠,一個滑鼠位置,一個游標(到目前為止,但是我們正在努力...),因此您無法用兩根手指輕拂兩個Flickable。如果您在Flickable中觸摸了能夠處理觸控事件的某個元件,但隨後在允許的方向上在Flickable上拖動手指,則它將使用childMouseEventFilter()來竊取該元件的抓取;但是這涉及從實際觸控事件切換到合成滑鼠事件,並且還記得將以下更新作為合成滑鼠事件傳遞給Flickable。各種事情出了問題。好吧...那些日子已經過去了,因為Flickable ::childMouseEventFilter()不再關心QPointerEvents來自哪個裝置。如果設定了pressDelay,則可以保留實際的觸控按鍵,然後在計時器到期時將其重播到其中的專案。是的,您現在也可以用多個手指拖動多個Flickable。


但是,多點觸控仍不能與其餘的僅滑鼠項(如MouseArea)一起使用,因為它們仍依賴於合成滑鼠事件。但這是可以避免的。通常,請嘗試使用事件處理程式而不是MouseArea,因為(顧名思義)它實際上不打算支援除滑鼠互動以外的任何功能。

QTabletEvents(來自Wacom手寫筆,Samsung S-pen,Apple Pencil等)也是具有更多屬性的指標事件,並且可以由任何處理滑鼠和觸控事件的與裝置無關的程式碼來處理。但是,我們將繼續努力改善這些體驗。我們沒有在QQuickItem中為其新增任何新的虛擬函式,但是它們很快就會被傳遞到QQuickItem :: event()。

我們仍在努力的另一件事是使Flickable在膝上型電腦觸控板上的效能更好。一個修復程式即將推出。

留給自己的裝置

平臺外掛現在有責任發現和註冊輸入裝置,以兌現每個QInputEvent告訴您確切來源的承諾。其中一些工作已經在某些平臺上完成。(事實證明,這是有問題的。)您可以從QInputDevice :: devices()獲取所有已註冊裝置的列表。qtdiag實用程式還會顯示此列表。

QInputDevice是一個QObject,並且如果存在自然的層次結構,則它的parent()可以是另一個裝置:例如,X11具有主裝置和從裝置,而數位板手寫筆“屬於”特定的數位板裝置。在其他情況下,父物件只是平臺外掛中的一個物件,該物件擁有該裝置以用於記憶體管理。

在未完成裝置發現工作的平臺上,QInputEvent :: device()永遠不會為null,而可以是取自QInputDevice :: primaryKeyboard()或QPointingDevice :: primaryMouse()的通用例項。觸控式螢幕裝置是獨一無二的。我們已經在Qt 5中做到了這一點。

QInputDevice :: seatName()對應於“座位”的Wayland概念:一個使用者正在使用的一組裝置。到目前為止,幾乎沒有多座位支援,但是隨著時間的推移它將得到改善。如果配置多指標X伺服器,則可以在不同裝置上看到不同的席位名稱,但是這些名稱是根據xcb外掛中的xinput ID自動生成的。在Wayway合成器(如Sway)上,可以給座椅任意命名;我們計劃Qt最終將與之合作。

$ xinput list
虛擬核心指標id = 2 [主指標(3)]
虛擬核心XTEST指標id = 4 [從屬指標(2)]
ZSA Technology Labs ErgoDox EZ滑鼠id = 11 [從屬指標(2)]
ZSA Technology Labs ErgoDox EZ使用者控制id = 13 [從屬指標(2)]
Logitech MX Master 2S id = 15 [從屬指標(2)]
虛擬核心鍵盤id = 3 [主鍵盤(2)]
虛擬核心XTEST鍵盤id = 5 [從屬鍵盤(3)
電源按鈕id = 6 [從屬鍵盤(3)]
電源按鈕id = 7 [從屬鍵盤(3)]
睡眠按鈕id = 8 [從屬鍵盤(3)]
UVC攝像機(046d:0992)id = 9 [從屬鍵盤(3)]
ZSA Technology Labs ErgoDox EZ id = 10 [從屬鍵盤(3)]
ZSA Technology Labs ErgoDox EZ系統控制元件id = 12 [從屬鍵盤(3)]
ZSA Technology Labs ErgoDox EZ鍵盤id = 14 [從屬鍵盤(3)]
ZSA Technology Labs ErgoDox EZ Consumer Control id = 16 [從屬鍵盤(3)]
Logitech MX Master 2S id = 17 [從屬鍵盤(3)]
輔助指標id = 22 [主指標(23)]
Microsoft Microsoft光學滑鼠,由Starck id = 19 [從屬指標(22)]
輔助XTEST指標id = 24 [從屬指標(22)]
輔助鍵盤id = 23 [主鍵盤(22)]
Apple,Inc.蘋果鍵盤id = 20 [從屬鍵盤(23)]
蘋果公司Inc.蘋果鍵盤id = 21 [從屬鍵盤(23)]
aux XTEST鍵盤id = 25 [從屬鍵盤(23)]

$ qtdiag
在“ xcb”
作業系統上的Qt 6.0.0(x86_64-little_endian-lp64共享(動態)除錯版本;由GCC 10.2.0構建):Arch Linux [linux版本5.9.11-arch2-1]
...
輸入裝置:23
QInputDevice :: DeviceType :: Mouse“虛擬核心指標”,座位:“ 30002”功能:位置滾動懸停
QInputDevice :: DeviceType :: Keyboard“虛擬核心鍵盤”,座位:“ 30002”功能:
QInputDevice :: DeviceType :: Mouse“輔助指標”,座:“ 170016”功能:位置滾動懸停
QInputDevice :: DeviceType :: Keyboard“輔助鍵盤”,座:“ 170016”功能:
QInputDevice :: DeviceType :: Mouse“虛擬核心XTEST指標”,座: 30002“功能:位置滾動懸停
QInputDevice :: DeviceType :: Keyboard“虛擬核心XTEST鍵盤”,座位:“ 30002”功能:
QInputDevice :: DeviceType ::鍵盤“電源按鈕”,座位:“ 30002”功能:
QInputDevice :: DeviceType ::鍵盤“電源按鈕”,座位:“ 30002”功能:
QInputDevice :: DeviceType ::鍵盤“睡眠按鈕”,席位:“ 30002”功能:
QInputDevice :: DeviceType ::鍵盤“ UVC攝像頭(046d:0992)”,席位:“ 30002”功能:
QInputDevice :: DeviceType ::鍵盤“ ZSA Technology Labs ErgoDox EZ”,席位:“ 30002功能:
QInputDevice :: DeviceType :: Mouse“ ZSA Technology Labs ErgoDox EZ滑鼠”,座椅:“ 30002”功能:位置滾動懸停
QInputDevice :: DeviceType :: Keyboard“ ZSA Technology Labs ErgoDox EZ System Control”,座椅:“ 30002 ”功能:
QInputDevice :: DeviceType :: Mouse“ ZSA Technology Labs ErgoDox EZ消費者控制”,座位:“ 30002”功能:位置滾動懸停
QInputDevice :: DeviceType :: Keyboard“ ZSA Technology Labs ErgoDox EZ鍵盤”,座位:“ 30002”功能:
QInputDevice :: DeviceType ::鍵盤“ ZSA Technology Labs ErgoDox EZ消費者控制”,位置:“ 30002”功能:
QInputDevice :: DeviceType ::滑鼠“ Logitech MX Master 2S”,位置:“ 30002”功能:位置滾動懸停
QInputDevice: :裝置型別::鍵盤“ Logitech MX Master 2S”,位置:“ 30002”功能:
QInputDevice ::裝置型別::滑鼠“ Starck的Microsoft Microsoft光學滑鼠”,位置:“ 170016”功能:位置滾動懸停
QInputDevice :: DeviceType :: Keyboard“ Apple,Inc蘋果鍵盤”,座位:“ 170016”功能:
QInputDevice :: DeviceType ::鍵盤“ Apple,Inc蘋果鍵盤”,座:“ 170016”功能:
QInputDevice :: DeviceType ::滑鼠“ aux XTEST指標”,座:“ 170016”功能:位置滾動懸停
QInputDevice :: DeviceType: :鍵盤“ aux XTEST鍵盤”,座位:“ 170016”功能:

QInputDevice :: availableVirtualGeometry()旨在告訴您該裝置可以訪問虛擬桌面的哪個區域。例如,您可能正在使用帶有外部顯示器的觸控式螢幕膝上型電腦:那麼觸控式螢幕的QPointingDevice :: availableVirtualGeometry()應該與螢幕的QScreen :: geometry()相同。Wacom數位板可以對映到小於整個螢幕的區域,以提高繪圖精度(使用xinput或特定於作業系統的控制面板)。但是同樣,這項工作尚未在所有受支援的平臺上完成。

合成滑鼠事件

合成滑鼠事件仍然存在,即使我們現在嘗試減少對它們的依賴。

QEvent :: spontaneous()是我們必須區分OS生成的事件和合成事件的最古老的方法,但是它不適用於區分合成滑鼠事件。在Qt 5中,添加了QMouseEvent :: source()來幫助您區分由其他裝置,作業系統,Qt或應用程式與其他裝置合成的滑鼠事件。但隨後我們發現,假設此類事件是從接觸點合成的,這既誘人又是錯誤的。(例如,它可以從QTabletEvent合成。)因此,我們建議現在使用event->device()->type()和/或pointerDevice()->pointerType()區分它們。當QMouseEvent由其他型別的事件合成時,裝置例項保持不變,因此您可以知道它的真正來源。

分類日誌

自從幾年前新增分類日誌記錄以來,我們一直在Qt中新增越來越多的內部日誌記錄。git grep Q_LOGGING_CATEGORY在qtbase和qtdeclarative中將找到許多用於除錯Qt Quick應用程式的有用的工具。)解決滑鼠和觸控互動問題的最有用的類別是qt.pointer.grab,因為抓取轉換是這些問題的徵兆或原因。但是還有更多:在交付專案和/或處理程式期間,您可以在QPA級別,QtGui級別,QQuickWindow中記錄事件。您可以在各種專案和處理程式中記錄互動的各個方面。

我可能會在以後的文章中寫有關捕獲和接受事件的更多技術細節。

祝您好運,並與Qt 6玩得開心!

https://www.qt.io/blog/input-events-in-qt-6