前言
由于需要在触摸屏上实现输入操作,但是触摸屏的qt版本为qt4,后期也有可能会换为qt5,所以为了之后移植程序能够直接移植,所以重新搭建了一套输入法架构
实现的原理
基于qt的焦点切换事件来实现,所有继承子QWidget的控件可以获得焦点。另外焦点控件理论来说有且只会有一个,所以我们在程序里只需要连接QApplication的focusChange信号就可以了。当有新的焦点切换时,判断焦点是否需要使用输入法,然后显示键盘,在键盘输入完成后移除当前焦点控件的焦点,让键盘消失。
关键代码
输入法类
首先是输入法类,这个类是单例类,因为要监控全局。主要完成焦点控件获取和对键盘控件的管理
class InputMethodPrivate;
class AbstractKeyBoard;
class QCP_LIB_DECL InputMethod : public QObject
{
Q_OBJECT
public:
~InputMethod();
//初始化,会初始化默认键盘Numeric_Keyboard和Pinyin_Keyboard
void initSorftKeyBoard();
//外部注册键盘,可覆盖原有键盘
void registerKeyBoard(const QString &keyBoardName, AbstractKeyBoard *keyBoard);
//移除键盘
void removeKeyBoard(const QString &keyBoardName);
signals:
void signal_comfirm(QWidget *focus,const QVariant &value);
private:
explicit InputMethod(QObject *parent = 0);
private:
InputMethodPrivate *d_ptr;
Q_DECLARE_PRIVATE(InputMethod)
Q_DISABLE_COPY(InputMethod)
friend class Singleton<InputMethod>;
};
#define InputMethodInstance Singleton<InputMethod>::instance()
头文件很简单,只提供给外部调用的函数就行了。外部需要获取键盘输入的值时可以连接信号signal_comfirm,拿到当前焦点控件和键盘确认输入的值。
InputMethodPrivate::InputMethodPrivate():
m_curKeyboard(NULL),
m_focusWidget(NULL),
q_ptr(NULL)
{
connect(qApp,SIGNAL(focusChanged(QWidget*,QWidget*)),this,SLOT(slot_focusChanged(QWidget*,QWidget*)));
}
在私有类的构造中连接焦点切换信号
void InputMethodPrivate::slot_focusChanged(QWidget *old, QWidget *now)
{
Q_UNUSED(old)
m_focusWidget = now;
if(!checkIsKeyBoard(now))//判断是否是键盘上的控件,不能是键盘的控件
{
if(m_curKeyboard)//判断当前是否有键盘在使用,如果使用则关闭键盘
{
m_curKeyboard->cancel();
m_curKeyboard = NULL;
}
//判断是否是可输入控件,并返回输入控件指定的键盘名称
QString keyboardName = isInputWidget(now);
if(!keyboardName.isEmpty())
{
showKeyBoard(keyboardName,now);//显示指定键盘
}
}
}
焦点切换的槽函数实现
键盘抽象类
键盘可以有多个,对于只能输入数字的使用数字键盘,需要输入中文可能就要使用拼音键盘,所以需要对键盘类进行抽象,把键盘的公用特性拿出来即可。具体的键盘只要继承这个抽象类即可。
#include <QWidget>
#include "inputmethod.h"
class AbstractKeyBoardPrivate;
class QCP_LIB_DECL AbstractKeyBoard : public QWidget
{
Q_OBJECT
public:
enum Position{
CONTROL = 0, // 显示位置在控件边上
TOP, //屏幕顶部
BOTTOM, //屏幕底部
Embedded //同主程序显示位置对齐
};
explicit AbstractKeyBoard(QWidget *parent = 0);
//显示键盘
virtual void showKeyboard(QWidget *focusWidget);
//键盘确认输入
virtual void confirm();
//键盘取消
virtual void cancel();
//给键盘输入当前数据
virtual void setCurData(const QVariant &_data) = 0;
//当前数据
virtual QVariant curData() = 0;
//焦点控件
virtual void setFocusWidget(QWidget *focusWidget) = 0;
virtual QWidget *focusWidget() = 0;
//刷新键盘显示位置
virtual void updatePos(Position type,QWidget *focusWidget);
//提交数据,会设置控件数据
virtual void commitData(const QVariant &_data);
//动画使能
void setAnimationEnable(bool enabled);
bool animationEnable();
signals:
//当键盘确认输入后,发送此信号,需要在实现类值调用此信号
void signal_confirm(const QVariant &value);
//在confirm和cancel默认发送
void signal_hide();
private:
QSharedPointer<AbstractKeyBoardPrivate> d_ptr;
Q_DECLARE_PRIVATE(AbstractKeyBoard)
Q_DISABLE_COPY(AbstractKeyBoard)
};
键盘的基本操作就是显示、输入值、关闭。基本也就围绕着这些来进行扩展。我这边因为项目中键盘不能直接输入到控件中,所有会对控件数据进行获取和提交,具体的数据展示由焦点控件实现。
AbstractKeyBoard::AbstractKeyBoard(QWidget *parent) : QWidget(parent),d_ptr(new AbstractKeyBoardPrivate)
{
d_ptr->q_ptr = this;
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
| Qt::WindowDoesNotAcceptFocus
#endif
);
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
setAttribute(Qt::WA_X11DoNotAcceptFocus);//qt5中设置此标志会导致在清除焦点后控件又会重新获取焦点
#endif
d_func()->initAnimation();
}
FramelessWindowHint :去除外边框
WindowStaysOnTopHint :保证键盘一定在最上方,避免键盘被盖住
WindowDoesNotAcceptFocus :qt5以上保证键盘不会获取焦点,避免焦点控件消失
WA_X11DoNotAcceptFocus:qt4下取消焦点获取,这个参数qt5上也有,但是经过测试发现关闭键盘后取消焦点控件后,这个焦点控件会重新获取焦点,导致键盘又会显示出来,但是在嵌入式linux和Ubuntu系统下却没有(qt4版本,暂时未试qt5版本)
void AbstractKeyBoard::commitData(const QVariant &_data)
{
QWidget *_focusWidget = this->focusWidget();
if(_focusWidget == NULL)return;
const QMetaObject *metaObj = _focusWidget->metaObject();
if(metaObj == NULL)return;
QVariant keyboardData = _focusWidget->property(KeyBoard::KEY_KeyBoardData.toLatin1().data());
if(keyboardData.isValid())//自定义数据提交方法
{
metaObj->invokeMethod(_focusWidget,
"setKeyBoardData",
QGenericArgument(keyboardData.typeName(),_data.data()));
}
else if(_focusWidget->inherits("QLineEdit") ||
_focusWidget->inherits("QTextEdit"))//默认使用控件的text属性
{
QVariant text = _focusWidget->property("text");
metaObj->invokeMethod(_focusWidget,
"setText",
QGenericArgument(text.typeName(),_data.data()));
}
else if(_focusWidget->inherits("QPlainTextEdit"))
{
QVariant plainText = _focusWidget->property("plainText");
metaObj->invokeMethod(_focusWidget,
"setPlainText",
QGenericArgument(plainText.typeName(),_data.data()));
}
}
给焦点控件提交数据,这边是通过元对象QMetaObject 来实现方法调用,这样的好处是不需要知道具体的焦点控件类是什么。其中setKeyBoardData时自定义的数据提交方法,只需要在任何需要键盘输入的控件类中实现setKeyBoardData函数就可以获取键盘提交的数据,来保证通用性。