Qt使用QQuickWidget的输入法问题(Qt5.12及以前)
最近有网友表示,在使用QQuickWidget嵌入到QWidget时,QML内部的输入法会有问题。
主要表现是,当焦点从QWidget(比如QLineEdit)切换到QQuickWidget内(比如TextInput),不能切换输入法;而当切换到其他应用程序,在切换回Qt程序时,输入法状态可正常切换。
我个人只有 5.6.3、5.12.2、5.15.2 三个版本环境,经过测试,5.15.2 已经修复了这个bug。而 Qt5.6 到 5.12,一些接口和机制有变化,所以解决方案不一样。本文主要讨论一下这个bug形成的具体原因。
Qt的焦点与输入法
Qt窗口内的子控件,有的可以唤起输入法(比如QLineEdit),有的不能(比如QPushButton)。当焦点从一个QWidget切换到另一个QWidget,Qt通过会向焦点控件发送QInputMethodQueryEvent事件,将系统感兴趣的信息返回给系统。
而QQuickWidget内部的不同元素,有自己的焦点,有不同的输入法状态。
当混合使用QWidget与QQuickWidget(继承自QWidget)时,对于上层QApplication而言,只会感知到QWidget的焦点变化,QML内元素的输入法状态会经过QQuickWidget这一层来返回给系统。
Bug原因
所以bug本质原因是,鼠标点击QML内输入框时,QQuickWidget会获得焦点,随即触发QInputMethodQueryEvent事件,QQuickWidget处理该事件。而此时,QML的焦点状态不对,或者Qt内部Bug,导致没有正确将事件发送给焦点QQuickItem。
而当窗口由非激活切换为激活状态,Qt内部能正确将事件发送给焦点对象,输入法正常。
解决办法
不同Qt版本可能机制和接口有变化,建议通过相关事件、信号、焦点状态来找到合适的解决办法。
下面时5.6.3和5.12.2版本的解决方案:
-
Qt.5.6.3
该版本里,QQuickWidget收到输入法事件时,将事件发送给了QQuickWindow,QQuickWindow虽然保存了当前QML的焦点元素,但实际源码什么都没做。
Qt 源码:
所以,可以对QQuickWidget注册事件过滤器或者重写event方法,通过QQuickWidget::quickWindow拿到关联的QQuickWindow,再由QQuickWindow::activeFocusItem获取到焦点元素,将事件发送给它。
bool Widget::eventFilter(QObject *watched, QEvent *event) { if(watched == quickWidget && event->type() == QEvent::InputMethodQuery) { // 重新发送该消息。 QApplication::sendEvent(quickWidget->quickWindow()->focusObject(), event); return true; } return false; }
(Qt 5.6.3的QQuickWindow会始终保存焦点元素,即便焦点切换到外部,内部仍然保持了自己的焦点。)
-
Qt.5.12.2
该版本里,Qt的逻辑有了变化,QQuickWidget 处理了输入法事件,向当前的焦点元素发送事件,但此时焦点元素不准确,实际焦点有些滞后。
Qt源码:
但相比Qt5.6.3,QML的焦点能准确变换。
所以,可以绑定QQuickWindow::focusObjectChanged信号,当QML的焦点变化时,更新输入法。
connect(quickWidget->quickWindow(), &QQuickWindow::focusObjectChanged, this, [this](QObject *){ // 限定一下,判断当前焦点 if(QApplication::focusWidget() == quickWidget) QGuiApplication::inputMethod()->update(Qt::ImQueryAll); });
( Qt 5.6.3里,QQuickWiget失去焦点并不会导致QQuickItem失去焦点,内部总是维持了焦点,所以再切回时,不会触发该信号…)
总结
输入法状态异常的解决办法,实际就是需要让正确的焦点对象处理 QInputMethodQueryEvent。如果上述两个解决办法不能解决其他版本问题,就需要看源码,从下面几个问题入手:
- QQuickWidget是怎么处理输入法事件
- 焦点切换时,QApplication::focusWidget、QQuiApplication::focusObject是什么
- QQuickWindow::focusObjectChanged有没有正确触发
- 窗口非激活到激活状态,是怎么触发输入法事件,焦点是谁