事件(event)是由系统或Qt本身在不同的时刻发出的。比如,当用户按下鼠标,敲下键盘,或窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件是在对用户操作做出响应的时候发出,如键盘事件等;另一些事件则是由系统自动发出,如定时器事件(在我们之前写的游戏壳子中用的就是这类事件)。
Qt的事件和信号槽很容易混淆,事件其实也就是所谓的事件驱动,signal由具体对象发出,然后会马上交给connect函数连接的slot进行处理。而对于事件,Qt使用一个**事件队列(windowSystemEventQueue)**对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部,前一个事件完成后,取出后面的事件进行处理。但是,必要的时候,Qt的事件也是可以不进入事件队列,而是直接处理的。
如果我们使用组件,我们关心的是信号槽;如果我们自定义组件,我们关心的是事件。
一般我们重写某个组件的事件,需要自定义类,继承对应的组件类,重写感兴趣的事件。
新建子类:
然后在代码创建成功后将父类改为QLable。
想知道有哪些事件,我们需要转到父类中,模糊搜索event,事件多为虚函数,供我们重写,定义自己的实现规则。
我们关注鼠标相关的事件:分别将mousePressEvent,mouseMoveEvent,mouseReleaseEvent三个虚函数(在qt中表现为斜体)重写。
可以在函数的声明后面加上宏Q_DECL_OVERRIDE或关键字override进行校验当前的虚函数是否为重写父类的,如果不是则会报错。
在重写的三个虚函数中,我们需要跟踪鼠标左键在Lable组件的状态。
声明:
public:
void mousePressEvent(QMouseEvent *ev) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent *ev) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *ev) Q_DECL_OVERRIDE;
定义:
void MyLabel::mousePressEvent(QMouseEvent *ev){
if(ev->button() == Qt::LeftButton){ //如果按下的键是鼠标左键
this->setText(QString("鼠标左键按下:%1,%2").arg(ev->x()).arg(ev->y()));
}
}
//button:触发当前事件的按钮
//buttons:触发当前事件时,有哪些按键是按下的
void MyLabel::mouseMoveEvent(QMouseEvent *ev){
if(ev->buttons() == Qt::LeftButton|Qt::RightButton){ //鼠标左键和右键同时按下且移动
this->setText(QString("鼠标左键合右键同时按下且移动"));
}else if(ev->buttons() == Qt::NoButton){ //鼠标移动,没有按下任何的按键
this->setText(QString("鼠标移动:%1,%2").arg(ev->x()).arg(ev->y()));
}
}
void MyLabel::mouseReleaseEvent(QMouseEvent *ev){
if(ev->button() == Qt::RightButton){ //鼠标右键抬起
this->setText(QString("鼠标右键抬起:%1,%2").arg(ev->x()).arg(ev->y()));
}
}
自定义的MyLabel类与主窗口上的Label组件绑定。
然后我们测试,经过测试我们发现基本都能通过,唯有一个当鼠标只移动没按下时没有反应,这是因为我们在他未按下时没有对他进行追踪,所以我们在构造函数中去添加一个函数。
这样就可以了
事件对象创建完毕后,Qt将这个事件传递给QObject::event()函数,event()函数主要用于事件的分发,一般情况下并不直接处理事件,而是将这些事件按照他们不同的类型,分发给不同的事件处理器(event handler)。
如果想在事件分发之前做一些额外的操作或屏蔽掉某些事件,我们也可以重写event()函数。通过event->type()来确定事件的类型。
举例:在窗口添加组件line edit,我们约定其为电话号码,并且只能输入数字最多可输入11位。
这个需求不敢说用信号槽一定不能做,但是一定会很麻烦,所以我们可以在事件分发中去做
我们首先还是要去自定义类,去继承对应的组件类
现在类建完了,那么就要去找需要重写的函数了
我们去父类中去通过模糊搜索event事件,找到我们需要的相关事件
将这个事件粘贴到子类中,然后我们还需要找到键盘释放的事件,但我们发现父类中没有,所以我们去爷爷类中寻找
我们找到了键盘释放函数,并且还找到了一个事件分发函数,将这两个函数也放到子类中去重写
然后将这三个函数在源文件中去定义
事件分发是在事件处理器之前发生的,用来集中接收并分发各种类型的事件
在事件分发函数中去实现拦截非法字符
//事件分发,集中接收并分发各种类型的事件
bool MyLineEdit::event(QEvent *event){
if(event->type() == QEvent::KeyPress){ //判断事件类型,如果是键盘按下的事件
QKeyEvent * pkey=(QKeyEvent *)event; //已经做判断了,所以可以强转为具体的事件了
if(Qt::Key_0 <= pkey->key() && pkey->key() <= Qt::Key_9 || pkey->key() == Qt::Key_Backspace){ //0~9分发,
return QLineEdit::event(event); //调用父类的分发
}else{ //不分发
qDebug()<<"event 拦截"<<pkey->key();
//return true; //代表的是:当前事件已经被处理,不需要分发了
return false; //代表:当前事件处理不了,一般会转到父窗口处理
}
}
return QLineEdit::event(event); //其他类型的事件,仍要继续分发
}
然后要将line edit提升为MyLineEdit
然后在键盘按下事件中实现将接收到的数字转为字符串,并显示
此时我们在输入电话号时就只能输入数字了,如果输入字母就会被拦截(英文模式下)
将电话号码存放到类成员属性中
我们再加一个限定条件,让输入的字符串只能小于11位
但是还有一个问题,如果我们输入的电话号码有误,那我们不管点什么都会被拦截,所以要再事件分发函数拦截条件中释放一个删除的口子
然后在键盘按下函数中做一个判断,如果按下的是退格键,就在结尾删除一位
到现在还没有结束,我们的需求又增加了,为了提高私密性,我们想将中间的四位数字以*的形式显示
所以还要在判断中加上三组判断,如果size小于3就正常输出,如果大于3小于7就显示*,最后大于七位就将前三位正常截取,然后拼接四个 *,在拼接上最后四位,最后再去显示
当然在之前实现尾删除时,最终显示也要显示带*的号码
键盘按下事件的完整实现:
void MyLineEdit::keyPressEvent(QKeyEvent * pkey){
if(pkey->key() == Qt::Key_Backspace){ //结尾删除一位
m_tel= m_tel.left(m_tel.size()-1); //保留前面,去除最后一位
//this->setText(m_tel);
this->setText(this->text().left(this->text().size()-1));
}
else if(m_tel.size()<11){
m_tel += QString::number(pkey->key()-Qt::Key_0);
this->setText(m_tel); //将接收到的数字转换为字符串,并显示
if(m_tel.size()<=3){
this->setText(m_tel);
}else if(3<m_tel.size() && m_tel.size() <=7){
QString tel = m_tel.left(3);
for(int i=3;i<m_tel.size();i++){
tel+="*";
}
this->setText(tel);
}else{
QString tel = m_tel.left(3)+"****"+ m_tel.right(m_tel.size()-7);
this->setText(tel);
}
}
}
最后还剩一点,我们此时的电话号码是带*加密的状态,那么我们想把键盘释放事件也利用上,在键盘释放中实现将完整电话号码显示出来,我们约定在回车键抬起时弹出一个弹出框将完整电话号码显示出来
void MyLineEdit::keyReleaseEvent(QKeyEvent *event){
//Qt::Key_Return 字母区的回车 Qt::Key_Enter 数字小键盘的回车
if(event->key() == Qt::Key_Return){ //如果是回车抬起
QMessageBox::information(this,"电话号码",m_tel);
}
}
最后,我们还记得之前在输入字母时会默认是中文,如果是中文输入法那么就不会对字母拦截,所以我们想把中文输入法禁用
在构造函数初始时调用一个禁用中文输入法的函数
MyLineEdit::MyLineEdit(QWidget *parent) : QLineEdit(parent)
{
this->setAttribute(Qt::WA_InputMethodEnabled,false); //禁用中文输入法
}
最终窗口显示