事件(event)是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
例如,在执行主窗口的exec()函数
之后,程序将进入事件循环
来监听
应用程序的事件。当事件发生时,Qt 将创建一个事件对象
。Qt 中所有事件类都继承于QEvent。在事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数
。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler)
,相当于用一个switch判断事件类型,根据不同的事件类型给出不同的处理函数。
P.S:
可以把事件看作是一个个软件中断。
事件处理函数都是虚函数,需要我们自己去实现。
事件处理函数可以在帮助文档中QWidget-protected function中查找。
下面我们来用事件实现点击按钮弹出窗口的效果:
//MyButton.h
#ifndef MYBUTTON_H
#define MYBUTTON_H
#pragma execution_character_set("utf-8")
#include
#include
class MyButton : public QPushButton
{
Q_OBJECT
public:
explicit MyButton(QPushButton *parent = nullptr);
signals:
public slots:
protected:
void MyButton::mousePressEvent(QMouseEvent *ev);
};
#endif // MYBUTTON_H
说明:先在项目中新建一个叫MyButton的C++类,继承自QPushButton类(在创建C++文件时如果没有这个选项,可以先随便选一个,然后在代码中修改成我们需要的基类)
新建一个MyButton对象,作为主窗口的私有成员数据。
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#pragma execution_character_set("utf-8")
#include
#include "MyButton.h"
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
protected:
private:
MyButton m_button;
};
#endif // WIDGET_H
在MyButton.cpp文件中,实现在头文件中声明的事件虚函数。
#include "MyButton.h"
#include
#include
#include
#include
MyButton::MyButton(QPushButton *parent) : QPushButton(parent)
{
}
void MyButton::mousePressEvent(QMouseEvent *ev){
qDebug()<<ev->button();
QDialog *p_dialog=new QDialog(this);
p_dialog->setWindowTitle("我是生成的窗口");
p_dialog->resize(350,350);
p_dialog->exec();
}
P.S:函数中返回的参数ev可以获得当前事件对象的一些信息,比如可以获取当前鼠标事件点击的是左键还是右键,可以获取当前键盘事件按下的是哪个键等等。
在Widget.cpp文件中,将MyButton对象作为子部件添加进来。
#include "Widget.h"
#include
#include
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
m_button.move(100,100);
m_button.setText("点击生成窗口");
m_button.setParent(this);
}
Widget::~Widget()
{
}
P.S:即使MyButton对象作为主窗口的私有成员数据,还是需要为其指定父元素,否则按钮不会在主窗口中显示出来。
实现效果:
同时,也打印出鼠标点击事件的信息:按下的是左键
P.S:要先引入相应的事件头文件才能调用事件对象的方法。
计时器在软件开发中很常用,它会以一定的时间间隔自动触发。
下面我们在刚才的界面中实现一个计时器:
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#pragma execution_character_set("utf-8")
#include
#include "MyButton.h"
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
protected:
void Widget::timerEvent(QTimerEvent *event); //声明要实现的计时器函数
private:
MyButton m_button;
};
#endif // WIDGET_H
//Widget.cpp
#include "Widget.h"
#include
#include
#include
#include
#include
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
m_button.move(100,100);
m_button.setText("点击生成窗口");
m_button.setParent(this);
startTimer(Qt::1000); //启动一个计时器,参数是每隔多少毫秒触发一次定时器
}
Widget::~Widget()
{
}
void Widget::timerEvent(QTimerEvent *event){
static int count=0;
count++;
qDebug()<<QString("%1 秒钟过去了").arg(count);
}
P.S:
Qt中格式化字符串函数:QString("xxx %1 %2").arg(a).arg(b);
结果是:%1位置会被变量a的值替换,%2位置会被变量b的值替换
启动一个计时器需要调用方法startTimer()
实现效果:
P.S:startTimer()函数返回一个int型的值,这个值就是计时器的ID,当我们想要关闭某个计时器时,只需要获取它的ID,然后调用killTimer(),将计时器ID作为参数传入即可。
#include "Widget.h"
#include
#include
#include
#include
#include
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
m_button.move(100,100);
m_button.setText("点击生成窗口");
m_button.setParent(this);
timerId=startTimer(1000); //用timerId接收计时器ID
qDebug()<<this;
}
Widget::~Widget()
{
}
void Widget::timerEvent(QTimerEvent *event){
static int total=0;
total++;
qDebug()<<QString("%1 秒钟过去了").arg(total);
if(total==3) killTimer(timerId); //关闭计时器
}
这样,经过3秒钟就将计时器关闭了。
注意:这里的timerId是类的私有成员数据,如果作为类的构造函数中的局部变量,在timerEvent()中是访问不到的。
事件的接收是有一定顺序的,如果在事件传播的过程中,事件被某个对象接收了,就不会继续往下传递了,只有当优先级高的对象忽略了这个事件,事件才会继续往后传递,优先级低的对象才有机会接收事件。
以下是若非特殊事件,则转发给基类去处理的情况:
void MyButton::event(QEvent *event)
{ //如果左键被按下,打印文字
if (event->button() == Qt::LeftButton) {
qDebug()<<"左键被按下";
}
//否则,转发给基类处理
else{
return QWidget::event(event);
}
}
以下是既要处理特殊事件,同时也要转发给基类去处理的情况:
void MyButton::event(QEvent *event)
{ //如果左键被按下,打印文字
if (event->button() == Qt::LeftButton) {
qDebug()<<"左键被按下";
}
//无论是否为特殊事件,都要转发给基类去处理
return QWidget::event(event);
}
下面是一个实验:
//MyButton.cpp
#include "MyButton.h"
#include
#include
#include
#include
MyButton::MyButton(QPushButton *parent) : QPushButton(parent)
{
}
void MyButton::mousePressEvent(QMouseEvent *ev){
if(ev->button()==Qt::LeftButton){
qDebug()<<"Mybutton鼠标左键被按下";
}else{
return QWidget::mousePressEvent(ev);
}
}
#include "Widget.h"
#include
#include
#include
#include
#include
#include
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
m_button.move(100,100);
m_button.setText("点击生成窗口");
m_button.setParent(this);
timerId=startTimer(1000);
qDebug()<<this;
}
Widget::~Widget()
{
}
void Widget::mousePressEvent(QMouseEvent *ev){
qDebug()<<ev->button();
qDebug()<<"Widget鼠标右键被按下";
}
P.S:注意到MyButton类和Widget类都实现了mousePressedEvent(),若点击鼠标左键,会打印“MyButton鼠标左键被按下”
,若点击鼠标右键,则将事件分发给基类,此时基类捕获到这个事件,会调用Widget类中的mousePressEvent()函数,打印“Widget鼠标被按下”
event()函数主要用于根据事件对象类型进行消息分发。利用这个特性,我们可以拦截某些特定的事件。
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#pragma execution_character_set("utf-8")
#include
#include "MyButton.h"
#include
#include
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
protected:
void Widget::timerEvent(QTimerEvent *event);
bool Widget::event(QEvent *event); //声明重载函数
private:
MyButton m_button;
int timerId;
};
#endif // WIDGET_H
//Widget.cpp
#include "Widget.h"
#include
#include
#include
#include
#include
#include
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
m_button.move(100,100);
m_button.setText("点击生成窗口");
m_button.setParent(this);
timerId=startTimer(1000);
qDebug()<<this;
}
Widget::~Widget()
{
}
void Widget::timerEvent(QTimerEvent *event){
static int total=0;
total++;
qDebug()<<QString("%1 秒钟过去了").arg(total);
if(total==3) killTimer(timerId);
}
bool Widget::event(QEvent *event){
switch(event->type()){
case QEvent::KeyPress:
qDebug()<<"键盘被按下";
break;
case QEvent::MouseButtonPress:
qDebug()<<"鼠标被按下";
break;
case QEvent::Resize:
qDebug()<<"窗口大小被改变";
break;
default:
break;
}
return true;
}
实现效果:
P.S:重写的event函数返回类型是bool型,当返回值为true,Qt会认为事件已经处理完毕,不再继续分发给其它对象,而是处理事件队列中的下一个事件。当返回值为false,事件将会由系统处理。
event()函数中实际是通过事件处理器来响应一个具体的事件。这相当于event()函数将具体事件的处理委托给具体的事件处理器。而这些事件处理器是一些虚函数,需要我们自己去实现。
有时候,对象需要查看、甚至要拦截发送到另外对象的事件。例如,对话框可能想要拦截按键事件,不让别的组件接收到;或者要修改回车键的默认处理,这时候就需要使用到事件过滤器。
所谓事件过滤器,可以理解成一种过滤代码。事件过滤器会检查接收到的事件。如果这个事件是我们感兴趣的类型,就进行我们自己的处理;如果不是,就继续转发。这个函数返回一个 bool 类型,如果你想将参数 event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回 false。
事件过滤器eventFilter()解决了当组件很多时需要重写很多个event()函数的问题。
eventFilter()
的参数有两个,分别是watched和event,其中watched指的是触发事件的目标控件,event指的是触发的事件。
eventFilter()
相当于创建过滤器,而installEventFilter()
则是安装过滤器。对象只有调用installEventFilter()
安装过滤器后,对应的eventFilter()
才有效。
- installEventFilter()函数的声明为:
void QObject::installEventFilter(QObject *filterObj);
其中,filterObj参数表示实现事件过滤器的部件。- 请注意,如果我们在一个部件安装了事件过滤器,一般在其父控件上实现事件过滤器函数。
- 事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。
- 【转自:cicero-Qt evenFilter()与installEvenFilter()-博客园】
#include "Widget.h"
#include
#include
#include
#include
#include
#include
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
m_button.move(100,100);
m_button.setText("点击生成窗口");
m_button.setParent(this);
m_button.installEventFilter(this);
this->installEventFilter(this);
}
Widget::~Widget()
{
}
bool Widget::eventFilter(QObject *obj,QEvent *ev){
// MyButton *p_button=static_cast(obj);
if( obj == &m_button){
if(ev->type()==QEvent::MouseButtonPress){
qDebug()<<"m_button需要被过滤";
return true;
}
}
if(obj==this){
if(ev->type()==QEvent::MouseButtonPress){
qDebug()<<"Widget需要被过滤";
return true;
}
}
return QWidget::eventFilter(obj,ev);
}
可以看到,点击m_button后,m_button的鼠标点击事件被它的事件过滤器捕获了,点击旁边的空白区域,widget的鼠标点击事件被它的事件过滤器捕获了。
P.S:如有错误,欢迎指正~