参考:
Qt 事件(event)_w3cschool
https://www.w3cschool.cn/learnroadqt/xvme1j4c.html
本地环境:
win10专业版,64位
事件的概念
将事件抽象为一个对象,当用户发起一个行为,就把对应的事件加入事件队列,对于系统来说,每次只要处理事件队列里未处理的事件就可以了;如果没用事件,程序就阻塞,不执行任何代码。
必要时,Qt的事件也可以不进入事件队列,直接处理。
与信号的区别:
- 信号一旦发出,对应的槽函数一定会被执行,但是事件可以使用事件过滤器进行过滤。
- 如果使用组件,那么要关心信号槽;如果自定义组件,那么要关心事件。(比如如果自定义一个QPushButton,那么需要重写它的鼠标点击事件和键盘处理时间,并在恰当的时候发出clicked()信号)
如果要使用事件,只要让类继承QWidget类及其子类(里面定义了很多protected virtual函数),然后再重写事件回调函数即可。
简单实例:自定义一个Label,重写鼠标移动、按压和释放事件
初始状态:
在label内移动:
鼠标按压:
鼠标释放:
代码:
// eventlabel.h
#ifndef EVENTLABEL_H
#define EVENTLABEL_H#include <QLabel>class QLabel;class EventLabel : public QLabel {
protected:void mouseMoveEvent(QMouseEvent *event);void mousePressEvent(QMouseEvent *event);void mouseReleaseEvent(QMouseEvent *event);
};#endif // EVENTLABEL_H// eventlabel.cpp
#include <QWidget>
#include <QMouseEvent>#include "eventlabel.h"void EventLabel::mouseMoveEvent(QMouseEvent *event) {this->setText(QString("<center><h1>Move: (%1, %2)</h1></center>").arg(QString::number(event->x()),QString::number(event->y())));
}void EventLabel::mousePressEvent(QMouseEvent *event) {this->setText(QString("<center><h1>Press: (%1, %2)</h1></center>").arg(QString::number(event->x()),QString::number(event->y())));
}void EventLabel::mouseReleaseEvent(QMouseEvent *event) {// 支持用c格式化字符串的方式写QStringQString msg;msg.sprintf("<center><h1>Release: (%d, %d)</h1></center>",event->x(), event->y());this->setText(msg);
}// main.cpp
#include <QApplication>
#include "eventlabel.h"int main(int argc, char *argv[])
{QApplication a(argc, argv);EventLabel *label = new EventLabel;label->setWindowTitle("DIY Label");label->resize(400, 200);// width, heightlabel->show();return a.exec();
}
事件的接收与忽略
Qt的事件对象都有一个accept()和一个ignore()。对于一个事件,如果子类没有处理这个事件的函数或者忽略了,那么会向上传递事件给父类;否则会接收事件,不再传递。在事件处理函数中可以用isAccepted()查询接收状态。
不过accept()和ignore()是很少用的,如果希望忽略一个事件,只要调用父类的响应函数即可。(如果不传给父类,直接忽略,可能应该执行的操作没有执行,会有危险)。用到它们的情况,例如在窗口关闭时需要询问是否要关闭:
点击Yes之后才能正常关闭。
// eventlabel.h
protected:void closeEvent(QCloseEvent *event);
private:bool continueToClose();// eventlabel.cpp
#include <QMessageBox>
...
void EventLabel::closeEvent(QCloseEvent *event){if(continueToClose()) {event->accept();}else {event->ignore();}
}bool EventLabel::continueToClose() {if(QMessageBox::question(this, "Quit", "Are you sure?",QMessageBox::Yes | QMessageBox::No,QMessageBox::No) == QMessageBox::Yes){return true;}else {return false;}
}
event()函数
event()是QObject的,它不是直接处理事件的,而是分发给不同的事件处理器(event handler)。如果希望在事件分发之前做一些操作,比如区分一些事件,某些事件在处理之前需要做一些处理。下面的例子是,当在窗口中,按下tab键时,做一些处理,那么应该继承QWidget,然后重写它的event()函数。同时,如果需要自定义事件,同样也要重写event()。
event()函数的返回值是bool型,返回true表示被传入的事件已经被识别并且得到了处理,此时QApplication会认为这个事件已经处理了,然后继续处理事件队列的下一个事件;返回false表示未被处理,QApplication会尝试寻找这个事件的下一个处理函数。
需要注意的是,前面提到的accept()和ignore()是不同事件处理器之间的沟通,event()的返回值是通知QApplication的notify()函数是否处理下一个事件。
效果:当按下tab键后,会弹出about提示框。
// eventlabel.h
protected:bool event(QEvent *event);// eventlabel.cpp
bool EventLabel::event(QEvent *event) {if(event->type() == QEvent::KeyPress) {QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);if (keyEvent->key() == Qt::Key_Tab) {// 处理QMessageBox::about(this, "", "press Tab");return true;}}return QWidget::event(event);
}
事件过滤器
事件过滤器的作用是筛选哪些事件需要被响应,就是判断哪些事件需要调用event(),进行识别和分发。此时需要重写eventFilter()函数,在有事件过滤器的情况下,这个函数会被优先调用,然后才处理事件。
这个函数的返回值也是bool,返回true表示停止响应。
比如为EventLabel安装一个eventFilter,希望它在面对键盘事件的时候,先发出一条提示信息。
// eventlabel.h
public:EventLabel();
protected:bool eventFilter(QObject *obj, QEvent *event);// eventlabel.cpp
EventLabel::EventLabel() {// 安装事件过滤器this->installEventFilter(this);
}bool EventLabel::eventFilter(QObject *obj, QEvent *event) {if(obj == this) {if(event->type() == QEvent::KeyPress) {QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);//qDebug() << "you press: " << keyEvent->key();QMessageBox::about(this, "", QString("%1").arg(keyEvent->key()));return true;} else {return false;}} else {// pass to parentreturn QLabel::eventFilter(obj, event);}
}
这是安装在Label上的。如果假设有个主窗口MainWindow,MainWindow有个组件textEdit(一个QObject对象),假设不希望让textEdit处理键盘事件,那么需要:
- 在MainWindow上实现eventFilter(),需要判断obj == textEdit,如果是的话,再判断是不是KeyPress,如果是的话返回true,这样不会被父级的evenFilter处理
- 执行textEdit.installEventFilter(MainWindow),表示如果有事件发送到textEdit,那么先调用MainWindow->eventFilter(),然后才调用textEdit.event()
注意:
- 事件过滤器也可以安装在QApplication上,这样可以过滤所有事件,但是也会降低事件分发的效率。
- 如果一个组件安装了多个过滤器,那么最后一个安装的会先调用,类似stack。
- 如果在事件过滤器中delete了某个接收组件,一定要将返回值设为true,不然Qt仍然会分发给这个组件,但实际是找不到的,那么程序会崩溃
- 事件过滤器和被安装的组件必须在同一线程,否则过滤器不起作用;如果安装之后,两个组件到了不同的线程,那么只有当二者重回到同一线程才气笑
- 事件的调用最终都会调用QCoreApplication.notify()。因此Qt的事件处理,控制权由低到高依次是:重定义事件处理函数,重定义event()函数,为单个组件安装事件过滤器,为QApplication安装事件过滤器,重定义QCoreApplication.notify()
自定义事件
自定义事件需要继承QEvent,同时需要提供一个QEvent::TYPE类型(enum)的参数,作为自定义事件的类型值。Qt保留了0-999的值,所以TYPE要大于999,同时需要在QEvent::User和QEvent::MaxUser之间(1000-65535)。由于很难记住这个黍子,所以使用一个函数:
static int QEvent::registerEventType( int hint = -1 );
这个函数接受一个int,如果合法,直接返回,如果不合法,返回一个系统分配的合法值。这个函数是线程安全的,不必添加另外的同步操作。
发送事件有两种方式:(自定义的事件也可以)
- send
事件被QCoreApplication的notify()函数直接发给receiver对象,返回值是事件处理函数的返回值,使用这个函数必须在栈上创建对象QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0); QApplication::sendEvent(mainWindow, &event);
- post
事件被追加到事件列表的最后,等待处理。线程安全,要在堆上创建对象。
这个对象不用手动delete,是Qt自动的。postEvent()还有一个带优先级参数的版本。通过调用sendPostedEvent()可以让已提交的事件立即得到处理。QApplication::postEvent(object, new MyEvent(QEvent::registerEvenType(2048)));
处理自定义事件也有两种方式:
- 重写void customEvent(QEvent *event),类似重写event()
- 直接重写event
bool CustomWidget::event(QEvent *event) {if (event->type() == CustomEventType) {CustomeEvent *myEvent = static_cast<CustomeEvent *>(event);// processingreturn true;}return QWidget::event(event); }
这一块等用到了再补实例。暂时只作理解。