Qt / Qml 中捕获(中文)输入法事件(按下 & 提交)

【写在前面】

        最近工作中遇到一个奇怪的问题:

        本来想在 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(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 
#include 
#include 

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() 即可。

        效果如下所示:

Qt / Qml 中捕获(中文)输入法事件(按下 & 提交)_第1张图片


 【结语】

        最后,实际上 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

你可能感兴趣的:(Qt,开发之旅,qt,输入法事件,输入法按键捕获)