Qt应用启动之前会根据环境变量QT_IM_MODULE的值加载匹配的输入法插件,此过程可简单理解为:
- Qt扫描QT_INSTALL/plugins/platforminputcontexts/路径下的.dll文件,并尝试加载
- 得到插件中实现了QPlatformInputContextPlugin类的实例
- 调用步骤2中实例的create方法,create的传入参数key为QT_IM_MODULE的值,如果该值与此插件所定义的值相匹配则生成QPlatformInputContext对象
QPlatformInputContext对象即为输入法上下文对象,相当于输入法的入口。不过具体到实际开发中,我们是定义一个QPlatformInputContext的子类来实现自定义的输入法。
Qt输入法的工作原理可描述为 - QWidget得到焦点
- Qt框架将该事件告知输入法上下文对象
- 输入法上下文对象查看有焦点对象的类型,如果需要弹出输入窗口,则将自身设置为有焦点对象的事件过滤器并根据有焦点对象的位置显示出输入法面板
- 根据需要,拦截发送给有焦点对象的相关按键事件,根据键值从输入法引擎中取得结果字符串。
- 在适当时间点(比如检测到按回车键)将结果字符串发送给有焦点对象实现。
如果焦点切换到其他非可输入Widget,会触发输入法上下文对象的QPlatformInputContext的hideInputPanel方法,自定义输入法类只需重载该方法即可知道何时隐藏输入法面板
Qt 工程示例
.pro
QT += widgets gui-private
TARGET = demoinputcontextplugin
TEMPLATE = lib
PLUGIN_TYPE = platforminputcontexts
PLUGIN_CLASS_NAME = DemoPlatformInputContextPlugin
HEADERS += \
PlatformInputContext.h \
main.h
SOURCES += \
PlatformInputContext.cpp \
main.cpp
DISTFILES += \
demoim.json
输入法上下文子类
/**
* \file PlatformInputContext.h
*/
#ifndef PLATFORMINPUTCONTEXT_H
#define PLATFORMINPUTCONTEXT_H
#include <qpa/qplatforminputcontext.h>
#include <QPointer>
class PlatformInputContext : public QPlatformInputContext
{
Q_OBJECT
public:
explicit PlatformInputContext();
~PlatformInputContext();
virtual bool isValid() const;
virtual void setFocusObject(QObject *object);
virtual bool eventFilter(QObject * watched, QEvent *event);
virtual void showInputPanel();
virtual void hideInputPanel();
private:
QPointer<QObject> m_focusedObject;
};
#endif // PLATFORMINPUTCONTEXT_H
/**
* \file PlatformInputContext.cpp
*
* \brief 输入法上下文实现类
*/
#include "PlatformInputContext.h"
#include <QDebug>
PlatformInputContext::PlatformInputContext()
{
}
PlatformInputContext::~PlatformInputContext()
{
}
bool PlatformInputContext::isValid() const
{
qDebug() << "isValid";
return true;
}
void PlatformInputContext::setFocusObject(QObject *object)
{
Q_UNUSED(object);
if(object)
qDebug() << "setFocusObject" << object->objectName();
if(m_focusedObject){
m_focusedObject->removeEventFilter(this);
}
m_focusedObject = object;
if(m_focusedObject){
// 查询是否有焦点控件是否需要输入法
QInputMethodQueryEvent imEvent(Qt::ImQueryInput | Qt::ImEnabled);
QGuiApplication::sendEvent(m_focusedObject, &imEvent);
bool isImEnabled = imEvent.value(Qt::ImEnabled).toBool();
if(isImEnabled){
qDebug() << "Some gay need input method";
m_focusedObject->installEventFilter(this);
}
}
}
bool PlatformInputContext::eventFilter(QObject *watched, QEvent *event)
{
Q_UNUSED(watched);
Q_UNUSED(event);
}
void PlatformInputContext::showInputPanel()
{
qDebug() << "showInputPanel";
}
void PlatformInputContext::hideInputPanel()
{
qDebug() << "hideInputPanel";
}
生成输入法实例的插件接口(相当于该.dll的“main”)
/**
* \file main.cpp
*
* \brief 插件实现接口,实例化QPlatformInputContext
*/
#include <qpa/qplatforminputcontextplugin_p.h>
#include "PlatformInputContext.h"
#include <QDebug>
static const char pluginName[] = "demoim";
class DemoPlatformInputContextPlugin : public QPlatformInputContextPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID QPlatformInputContextFactoryInterface_iid FILE "demoim.json")
public:
QStringList keys() const;
QPlatformInputContext * create(const QString &key, const QStringList ¶mList);
};
QStringList DemoPlatformInputContextPlugin::keys() const
{
return QStringList(QLatin1String(pluginName));
}
QPlatformInputContext *DemoPlatformInputContextPlugin::create(const QString &key, const QStringList ¶mList)
{
Q_UNUSED(paramList);
if(key.compare(key, QLatin1String(pluginName), Qt::CaseInsensitive) == 0){
qDebug() << "Create Demo platform input context";
PlatformInputContext * pic = new PlatformInputContext();
return pic;
}
return 0;
}
#include "main.moc" /// 这一行没有深究,少了可能无法成功加载输入法
main.cpp中最有一行
#include "main.moc"
还未深究,以俟夫观大牛者得焉
将上述工程构建生成的demoinputcontextplugin.dll复制到QT_INSTALL/plugins/platforminputcontexts/路径下。
新建QApplication工程。在QApplication a(arc, arvc)之前设置环境变量
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
qputenv("QT_IM_MODULE", "demoim");
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}