事件与信号/事件过滤器
- 事件
- 一、事件的产生
- 二、事件的派发
- 三、事件类和事件类型
- 四、事件的处理
- 事件与信号
- 事件过滤器
事件
Qt系统的图形化窗口是由事件驱动的。,点击鼠标、按键,改变窗口大小、最小化窗口、关闭窗口等都会产生相应的事件。
QWidget类的所有界面组件类的基类,QWdiget类定义了大量的处理事件相关的数据类型和接口函数。
一、事件的产生
当应用程序发生变化,如点击鼠标、按下键盘等,就会产生事件。
事件是对象,是QEvent类或其派生类的实例。
Qt的主事件循环(QCoreApplication::exec())从事件队列中获取本地窗口系统事件,将它们转换为QEvent,并将转换后的事件发送给QObject。QObject类调用函数QObject::event()
来处理事件。
事件可以划分为3类:
- 自主事件:是由窗口系统产生的事件。例如,QKeyEvent事件、QMouseEvent事件。自主事件会进入系统事件队列,然后被应用程序的事件循环逐个处理
- 发布事件:是由Qt或应用程序产生的事件。例如,QTime定时器发送定时溢出时,Qt会自动QTimeEvent事件。应用程序使用静态的
QCoreApplication::postEvent()
产生发布事件。发布事件会进入Qt事件队列,然后被应用程序的事件循环逐个处理 - 发送事件:是由Qt或应用程序定向发送给某个对象的事件。应用程序使用静态的
QCoreApplication::sendEvent()
产生发送事件,由对象的event()函数直接处理
自主事件和发布事件是异步
的,也就是事件进入队列后系统去处理,程序不会在产生事件的地方停止进行等待
QCoreApplication::postEvent
(
QObject *receiver
,QEvent *event
,int priority = Qt::NromalEventPriority
)
- receiver:事件的接收者
event:事件对象
priority :事件的优先级 - 发布事件之后,该函数会立即退出,不会等待事件处理完,发布事件是异步的
QCoreApplication::sendEvent
(
QObject *receiver
,QEvent *event
)
- receiver:事件的接收者
event:事件对象 - 发送事件之后,需要等待事件处理完,发送事件是同步的
二、事件的派发
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();//事件循环
}
函数QApplication::exec()
的主要功能就是不断的检查系统队列和Qt事件队列中是否有未处理的自主事件和发布事件。
如果有事件就派发给接收事件的对象去处理,事件循环还可以把相同的事件进行合并发送给接收事件的对象去处理。
注意,应用程序的事件循环只会处理自主事件和发布事件,而不会处理发送事件,因为发送事件是以同步模式运行的
学习两个派发事件的函数:
在事件队列未能及时被处理,导致用户界面出现卡顿无响应,
可以使用这个函数来派发事件队列中为处理的事件
static void QCoreApplication::processEvents
(
QEventLoop::ProcessEventFlags flags=QEventLoop::AllEvents
)
- QEventLoop::AllEvents:处理所有事件
- QEventLoop::ExcludeUserInputEvents:排除用户输入事件
- QEventLoop::ExcludeSockstNoitEvents:排除网络socket的通知事件
- QEventLoop::WaitForMoreEvents:如果没有未处理的事件,等待更多事件
static void QCoreApplication::sendPostEvents
(
QObject *receiver=nullptr //接收者对象
,int event_type=0 //事件类型
)
把QCoreApplication::postEvent()发送到Qt事件队列里的事件立刻派发出去。如果没指定事件类型,就派发所有给这个接收者的事件。如果接收者和事件类型都不指定,就派发给所有使用QCoreApplication::postEvent()发布的事件
三、事件类和事件类型
事件是QEvent或其派生类的实例。QEvent是所有事件类的基类,但是它不是抽象函数。它可以自己创建事件,其主要的几个函数如下:
函数 | 描述 |
---|---|
void accept() | 接收事件 |
void ignore() | 忽略事件 |
bool isAccepted() | 是否接收事件 |
bool isInputEvent() | 事件对象是不是QInputEvent或其派生类实例 |
bool isPointerEvent() | 事件对象是不是QPointerEvent或其派生类实例 |
bool isSinglePointEvent() | 事件对象是不是QSinglePointEvent或其派生类实例 |
void spontaneous() | 是不是自主事件,也就是窗口系统事件 |
QEvent::Type type() | 事件类型 |
调用ignore(),事件会派发给其父组件。
每一个事件都有唯一的的事件类型,也有对应的事件类,有的事件类可以处理多种类型的事件。
四、事件的处理
有对象接收到应用程序派发过来的事件后,会调用函数event()处理事件,event()是QObject类中定义的一个虚函数,原型如下:
bool QObject::event(QEvent *e);
参数e就是事件对象,通过e->type()来判断事件的类型,调用对应的处理函数。
例如:事件类是QEvent::Paint(需要重新绘制事件类型),就调用paintEvent()处理函数。我们只需要重写函数paintEvent()就能达到自己的目标。
QWidget类是所有组件类的基类,它重新实现了event(),并针对一写典型类型的事件定义了专门的事件处理函数。
如果需要处理的事件在QWidget中没有对应的事件处理函数,需要重写event(),判断事件类型后调用自己定义的事件处理函数。
事件与信号
事件和信号的区别在于,事件通常是窗口系统和应用程序产生的。信号则是由Qt定义或用户自定义的。Qt为界面组件定义的信号通常都是对事件的封装。
现在需要编写一个例子:设计一个标签类TMyLabel,该类继承QLabel,重写event()函数
,并且对QEvent::HoverEnter和QEvent::HoverLeave类型的事件进行处理。当光标移动到组件上,组件上文字的颜色为红色,离开组件上的文字为黑色。
tmylabel.h
#include <QWidget>
#include <QLabel>
#include <QEvent>
class TMyLabel : public QLabel
{
Q_OBJECT
public:
TMyLabel(QWidget *parten=nullptr);
private:
//重写event
bool event(QEvent *e);
//重写鼠标双事件处理的函数
void mouseDoubleClickEvent(QMouseEvent *event);
signals:
//自定义信号
void doubleClicked();
};
tmyLabel.cpp
#include "tmylabel.h"
TMyLabel::TMyLabel(QWidget *parten):QLabel(parten)
{
this->setAttribute(Qt::WA_Hover,true);
}
bool TMyLabel::event(QEvent *e)
{
QPalette plet=this->palette();
if(e->type()==QEvent::HoverEnter)
{
this->setText("鼠标进入");
plet.setColor(QPalette::WindowText,Qt::red);
this->setPalette(plet);
}
else if(e->type()==QEvent::HoverLeave)
{
this->setText("鼠标离开");
plet.setColor(QPalette::WindowText,Qt::black);
this->setPalette(plet);
}
//其他事件类型继续交给父类处理
return QLabel::event(e);
}
void TMyLabel::mouseDoubleClickEvent(QMouseEvent *event)
{
Q_UNUSED(event);
emit doubleClicked();
}
事件过滤器
从事件与信号章节得知,我们需要重新处理事件需要重新定义一个新类,在新类中去重新函数event()或对应的事件处理函数。
QObject还提供了另一种方式来处理事件:事件过滤器。它可以将一个对象的事件委托给另一个对象来监视并处理。
例如:一个窗口可以作为其界面上的QLabel组件的事件过滤器,派发给QLabel组件的事件由窗口去处理,这样就不需要重新定义一个标签类啦。
实现事件过滤器需要用到两个函数:
安装事件过滤器:将自己注册给监视对象
void QObject::installEventFilter(QObject *filterObj);
参数 filterObj:监视对象
监视对象需要重新实现eventFilter(),对监视到的事件进行处理
bool QObject::eventFilter(QObject *watched, QEvent *event);
参数watched:被监视对象
参数event:事件对象
返回值:
true(事件就不会在传播给其他对象)
false(事件会继续传播给事件接收者做进一步处理)
代码例子:
widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
bool eventFilter(QObject *watched,QEvent *event);
private:
Ui::Widget *ui;
};
widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->label->installEventFilter(this);
ui->label_2->installEventFilter(this);
}
Widget::~Widget()
{
delete ui;
}
bool Widget::eventFilter(QObject *watched, QEvent *event)
{
if(watched==ui->label)
{
if(event->type()==QEvent::Enter)
{
ui->label->setStyleSheet("background-color:rgb(170,255,255)");
ui->label->setText("走开,我不喜欢");
}
else if(event->type()==QEvent::Leave)
{
ui->label->setText("靠近我呀");
}
}
else if(watched==ui->label_2)
{
if(event->type()==QEvent::Enter)
{
ui->label_2->setStyleSheet("background-color:rgb(85,255,127)");
ui->label_2->setText("走开,我不喜欢");
}
else if(event->type()==QEvent::Leave)
{
ui->label_2->setText("靠近我呀");
}
}
我们只处理了少数事件,其他的事情传播给父类处理(如QEvent::Paint事件没有处理),
所以需要返回父类的eventFilter()
如果直接返回true,界面上就不会显示文字啦
return QWidget::eventFilter(watched,event);
}