【写在前面】
最近工作中遇到一个奇怪的问题:
本来想在 TextEdit ( QTextEdit ) 中捕获一下键盘按键按下的事件。
然而,当输入法为英文时( 正常输入字符 ),可以捕获到按键事件,但当我切换到中文时,弹出输入法选框后,却无法再像英文那样捕获到事件。
经过查阅资料,发现在使用输入法时,不会发出按键事件,而是另外一种不太常见的事件类型:QEvent::InputMethod ,与之关联的事件为:QInputMethodEvent 。
为了正确处理这类事件,我简单封装了一个辅助类,效果相当不错。
【正文开始】
首先,因为要处理特殊事件,必须重新实现 bool QObject::event(QEvent *e),不过我们并不需要重写 TextEdit ( QTextEdit ) 这些类,只需借助事件过滤器即可:
使用 void QObject::installEventFilter(QObject *filterObj) 函数为指定对象安装事件过滤器。
这里的过滤器称为 InputMethodEventCatch :
class InputMethodEventCatch : public QObject
{
Q_OBJECT
Q_PROPERTY(QObject* target READ target WRITE setTarget NOTIFY targetChanged)
public:
explicit InputMethodEventCatch(QObject *parent = nullptr);
~InputMethodEventCatch();
QObject *target();
void setTarget(QObject *target);
bool eventFilter(QObject *obj, QEvent *event);
signals:
void targetChanged();
void inputMethodEventPressed(int code, const QString &key);
void inputMethodEventCommitted(const QString &commitString);
private:
QObject *m_target = nullptr;
};
它提供主要两个信号:
void inputMethodEventPressed(int code, const QString &key) 输入法按键按下时发出,code为 Qt::Key Qt 按键枚举,key 则为按键字符(串)。
void inputMethodEventCommitted(const QString &commitString) 输入法编辑完成时发出,代表文本从输入法提交到编辑器,其中 commitString 为提交的文本。
接下来是实现部分:
InputMethodEventCatch::InputMethodEventCatch(QObject *parent)
: QObject{parent}
{
}
InputMethodEventCatch::~InputMethodEventCatch()
{
if (m_target) m_target->removeEventFilter(this);
}
QObject *InputMethodEventCatch::target()
{
return m_target;
}
void InputMethodEventCatch::setTarget(QObject *target)
{
if (!target) return;
if (m_target != target) {
if (m_target) m_target->removeEventFilter(this);
target->installEventFilter(this);
m_target = target;
emit targetChanged();
}
}
bool InputMethodEventCatch::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::InputMethod) {
QInputMethodEvent *input = static_cast<QInputMethodEvent *>(event);
if (input->preeditString().isEmpty()) {
emit inputMethodEventCommitted(input->commitString());
} else {
QString key;
for (const auto &attr: input->attributes()) {
if (attr.type == QInputMethodEvent::AttributeType::Cursor && attr.start > 0) {
key = input->preeditString().mid(attr.start - 1, attr.length);
break;
}
}
if (key.size() == 1)
emit inputMethodEventPressed(key2code(*key.begin()), key);
}
}
return QObject::eventFilter(obj, event);
}
关键代码在 eventFilter() 中:
1、如果预编辑字符串为空,则认为编辑完成。
2、如果不为空,那么我们取出光标处的字符,然后使用 key2code() 转换为 Qt::Key。
其中 key2code() 实现如下:
static int key2code(const QChar &key)
{
switch (key.toLatin1())
{
case 'q': case 'Q': return Qt::Key_Q;
case 'w': case 'W': return Qt::Key_W;
case 'e': case 'E': return Qt::Key_E;
case 'r': case 'R': return Qt::Key_R;
case 't': case 'T': return Qt::Key_T;
case 'y': case 'Y': return Qt::Key_Y;
case 'u': case 'U': return Qt::Key_U;
case 'i': case 'I': return Qt::Key_I;
case 'o': case 'O': return Qt::Key_O;
case 'p': case 'P': return Qt::Key_P;
case 'a': case 'A': return Qt::Key_A;
case 's': case 'S': return Qt::Key_S;
case 'd': case 'D': return Qt::Key_D;
case 'f': case 'F': return Qt::Key_F;
case 'g': case 'G': return Qt::Key_G;
case 'h': case 'H': return Qt::Key_H;
case 'j': case 'J': return Qt::Key_J;
case 'k': case 'K': return Qt::Key_K;
case 'l': case 'L': return Qt::Key_L;
case 'z': case 'Z': return Qt::Key_Z;
case 'x': case 'X': return Qt::Key_X;
case 'c': case 'C': return Qt::Key_C;
case 'v': case 'V': return Qt::Key_V;
case 'b': case 'B': return Qt::Key_B;
case 'n': case 'N': return Qt::Key_N;
case 'm': case 'M': return Qt::Key_M;
}
return Qt::Key_unknown;
}
注意:该实现只转换了 26 个字母,其他的根据需要自己加上即可。
【如何使用】
使用起来相当简单:
#include "inputmethodeventcatch.h"
#include <QApplication>
#include <QDebug>
#include <QTextEdit>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QTextEdit edit;
InputMethodEventCatch editCatch;
QObject::connect(&editCatch, &InputMethodEventCatch::inputMethodEventPressed, &editCatch, [](int code, const QString &key){
qDebug() << "inputMethodEventPressed" << Qt::Key(code) << key;
});
QObject::connect(&editCatch, &InputMethodEventCatch::inputMethodEventCommitted, &editCatch, [](const QString &commitString){
qDebug() << "inputMethodEventCommitted" << commitString;
});
editCatch.setTarget(&edit);
edit.show();
return app.exec();
}
绑定相关信号并 setTarget() 即可。
效果如下所示:
【结语】
最后,实际上 Qml 中也一样可以使用,只需要将 InputMethodEventCatch 注册进 Qml 中即可,然后类似这样:
TextEdit {
id: textEdit
}
InputMethodEventCatch {
target: textEdit
onInputMethodEventPressed: { }
onInputMethodEventCommitted: { }
}
源码地址,Github的:GitHub - mengps/QtExamples: 分享各种 Qt 示例,,说不定用得上呢~分享各种 Qt 示例,,说不定用得上呢~. Contribute to mengps/QtExamples development by creating an account on GitHub.https://github.com/mengps/QtExamples
CSDN的:https://download.csdn.net/download/u011283226/87276813https://download.csdn.net/download/u011283226/87276813