个人认为,事件机制是Qt最难以理解且最为精妙的一部分。事件主要分为两种:
在发生事件时(比如说上面说的按下鼠标),就会产生一个QEvent对象(这里是QMouseEvent,为QEvent的子类),这个QEvent对象会传给当前组件的event函数。如果当前组件没有安装事件过滤器(这个后面会提到),则会被event函数发放到相应的xxxEvent函数中(这里是mousePressEvent函数)。
需要区分的是:事件与信号并不相同。
比如:鼠标单击按钮,鼠标事件(QMouseEvent),而按钮本身发射clicked()信号。一般而言我们只需要关注单击信号,不用考虑鼠标事件。但是当我们要对该按钮做额外操作,不想通过信号处理,此时事件就是一个很好的选择。关闭事件(QCloseEvent)是一个常用的事件。
处理事件有5种常用的方法:
实际编程中最常用的是方法(1),其次是方法(5)。方法2要继承QApplication类,方法3需要全局的事件过滤器,减缓事件的传递。
鼠标事件:
常用的鼠标事件:(本篇处理事件用的是方法一:重写鼠标事件)
鼠标事件使用的时候,加头文件: #include
重写事件框架:
1️⃣鼠标按下事件
void Widget::mousePressEvent(QMouseEvent *event)
{
// 如果是鼠标左键按下
if(event->button() == Qt::LeftButton){
···
}
// 如果是鼠标右键按下
else if(event->button() == Qt::RightButton){
···
}
}
2️⃣鼠标移动事件
void Widget::mouseMoveEvent(QMouseEvent *event)
{
// 这里必须使用buttons()
if(event->buttons() & Qt::LeftButton){ //进行的按位与
···
}
}
默认情况下,触发事件需要点击一下,才能触发。可设置为自动触发:setMouseTracking(true);
3️⃣鼠标释放事件
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
···
}
4️⃣鼠标双击事件
void Widget::mouseDoubleClickEvent(QMouseEvent *event)
{
// 如果是鼠标左键按下
if(event->button() == Qt::LeftButton){
···
}
}
5️⃣滚轮事件
void Widget::wheelEvent(QWheelEvent *event)
{
// 当滚轮远离使用者时
if(event->delta() > 0){
···
}else{//当滚轮向使用者方向旋转时
···
}
}
实例演示(在label控件中,移动鼠标获取实时位置,并显示在界面上)
这里用了类似自定义控件的方法,对Mylabel类进行封装。设置基类QLabel 是为了在ui界面中提升label控件(即将label控件和Mylabel关联,提升时候必须二者基类相同)
#pragma once
#include
class mylabel : public QLabel
{
public:
mylabel(QWidget* parent = 0);
~mylabel();
public:
//鼠标移动事件
void mouseMoveEvent(QMouseEvent* event);
//鼠标按下事件
void mousePressEvent(QMouseEvent* event);
//鼠标释放事件
void mouseReleaseEvent(QMouseEvent* event);
};
#include "mylabel.h"
#include"QMouseEvent"
mylabel::mylabel(QWidget* parent) :QLabel(parent)
{
}
mylabel::~mylabel()
{
}
//鼠标移动显示坐标
void mylabel::mouseMoveEvent(QMouseEvent* event)
{
if (event->buttons() & Qt::LeftButton) //进行的按位与(只有左键点击移动才满足)
{
QString str = QString("Move:(X:%1,Y:%2)").arg(event->x()).arg(event->y());
this->setText(str);
}
}
//鼠标按下显示“ok,mouse is press”
void mylabel::mousePressEvent(QMouseEvent* event)
{
setText("Ok, mouse is press");
}
//鼠标释放清除显示
void mylabel::mouseReleaseEvent(QMouseEvent* event)
{
setText(" ");
}
#include "qtest.h"
QTest::QTest(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
//声明mylabel类的控件
mylabel* label1 = new mylabel(this);
label1->setGeometry(QRect(130, 100, 271, 161));
//设置边框
label1->setFrameShape(QFrame::Panel);
}
另外,当调用setMouseTracking(true);时(即设置鼠标状态为自动触发),需要将鼠标移动事件的if语句去掉(因为不需要点击触发了)
修改maylabel.cpp事件:
#include "mylabel.h"
#include"QMouseEvent"
mylabel::mylabel(QWidget* parent) :QLabel(parent)
{
//设置鼠标状态(自动触发)
setMouseTracking(true);
}
mylabel::~mylabel()
{
}
//鼠标移动显示坐标
void mylabel::mouseMoveEvent(QMouseEvent* event)
{
QString str = QString("Move:(X:%1,Y:%2)").arg(event->x()).arg(event->y());
this->setText(str);
}
//鼠标按下显示“ok,mouse is press”
void mylabel::mousePressEvent(QMouseEvent* event)
{
setText("Ok, mouse is press");
}
//鼠标释放清除显示
void mylabel::mouseReleaseEvent(QMouseEvent* event)
{
setText(" ");
}
效果展示:
这里用的是代码创建label控件,那么能不能用ui界面编辑然后在对label控件提升呢?
答案是可以的,但是需要注意的是:此处不能选择全局包含
否则会出现:
我想其中的原因主要是因为:
本实例是新建了一个mylabel类,而不是像QT常用控件(三)——自定义控件封装 - 唯有自己强大 - 博客园 (cnblogs.com)这篇博文中直接新添加了一个设计师界面类(即包含ui .h .cpp)。当选择全局包含时,就包含了主类。
其实也有解决的办法:需要在提升界面的头文件处,将工程目录下自定义控件的地址放于此处(本篇地址:C:/Users/WFD/Desktop/QTest/QTest/mylabel.h)
上面提到的xxxEvent函数,称为事件处理器(event handler)。而event函数的作用就在于事件的分发。如果想在事件的分发之前就进行一些操作,比如监听(阻塞)鼠标按下事件。
如果希望在事件分发之前做一些操作,就可以重写这个 event()函数了。比如我们希望阻塞鼠标按下事件,那么我们就在新建的Mylabel类中重写event()函数(该类的父类是QLabel)
#include"qlabel.h"
class Mylabel : public QLabel
{
public:
explicit Mylabel(QWidget* parent = 0);
//鼠标按下事件
void mousePressEvent(QMouseEvent* event);
//鼠标释放事件
void mouseReleaseEvent(QMouseEvent* event);
//声明event事件
bool event(QEvent* e);
};
#include "Mylabel.h"
#include"QMouseEvent"
Mylabel::Mylabel(QWidget* parent) :QLabel(parent)
{
}
//重写鼠标按下事件
void Mylabel::mousePressEvent(QMouseEvent* event)
{
this->setText(QString("mouse is press x:%1,y:%2").arg(event->x()).arg(event->y()));
}
//重写鼠标释放事件
void Mylabel::mouseReleaseEvent(QMouseEvent* event)
{
this->setText("mouse is release ");
}
//重写event事件
bool Mylabel::event(QEvent* e)
{
//如果鼠标按下,再事件分发中做拦截
if (e->type()==QEvent::MouseButtonPress)
{
//静态转换(将QEvent的对象转换为QMouseEvent对象)
QMouseEvent* event = static_cast(e);
this->setText(QString("event mouse is press x:%1,y:%2").arg(event->x()).arg(event->y()));
return true;//返回ture,说明用户自己处理事件,不往下分发(即拦截上面的按下事件)
}
return QLabel::event(e);
}
点击鼠标可以看到,触发的是event的事件(即阻塞了mousePressEvent的事件)。特别需要注意的是:在将不需要阻塞分发的时候,需要分发给父类的event函数处理。即(return QLable::event(e);)
某些应用场景下,需要拦截某个组件发生的事件,让这个事件不再向其他组件进行传播,这时候可以为这个组件或其父组件安装一个事件过滤器,该过滤器在event分发之前进行拦截。
事件的过滤有两个步骤:
1️⃣对QObject组件安装过滤器(调用installEvenFilter函数)
void QObject::installEventFilter ( QObject * filterObj );
参数filterobj 是指谁为组件安装过滤器(一般是父类)
2️⃣事件过滤器的重写(evenFilter函数)
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );
可以看到,函数有两个参数,一个为具体发生事件的组件,一个为发生的事件(产生的QEvent对象)。当事件是我们感兴趣的类型,可以就地进行处理,并令其不再转发给其他组件。函数的返回值也是bool类型,作用跟even函数类似,返回true为不再转发,false则让其继续被处理。
#include "qtest.h"
#include"qmouseevent"
QTest::QTest(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
//第一步:给label添加过滤器
ui.label->installEventFilter(this);
}
//第二步:重写过滤事件
bool QTest::eventFilter(QObject* obj, QEvent* e)
{
if (obj == ui.label)
{
//如果鼠标按下,再事件分发中做拦截
if (e->type() == QEvent::MouseButtonPress)
{
QMouseEvent* event = static_cast(e);
ui.label->setText(QString("eventfilter mouse is press x:%1,y:%2").arg(event->x()).arg(event->y()));
return true;//返回ture,说明用户自己处理事件,不往下分发(即拦截上面的按下事件)
}
}
return QWidget::eventFilter(obj, e);
}
//重写鼠标按下事件
void QTest::mousePressEvent(QMouseEvent* event)
{
ui.label->setText(QString("mouse is press x:%1,y:%2").arg(event->x()).arg(event->y()));
}
//重写事件分发
bool QTest::event(QEvent* e)
{
//如果鼠标按下,再事件分发中做拦截
if (e->type() == QEvent::MouseButtonPress)
{
QMouseEvent* event = static_cast(e);
ui.label->setText(QString("event mouse is press x:%1,y:%2").arg(event->x()).arg(event->y()));
return true;//返回ture,说明用户自己处理事件,不往下分发(即拦截上面的按下事件)
}
return QWidget::event(e);
}
运行结果:
可以看到在过滤器事件中就监听了鼠标按压(即阻塞了后面的事件分发和鼠标按压)