事件处理器
事件
众所周知Qt是一个基于C++的框架,主要用来开发带窗口的应用程序(不带窗口的也行,但不是主流)。我们使用的基于窗口的应用程序都是基于事件,其目的主要是用来实现回调(因为只有这样程序的效率才是最高的)。所以在Qt框架内部为我们提供了一些列的事件处理机制,当窗口事件产生之后,事件会经过:事件派发 -> 事件过滤->事件分发->事件处理
几个阶段。Qt窗口中对于产生的一系列事件都有默认的处理动作,如果我们有特殊需求就需要在合适的阶段重写事件的处理动作。
事件(event) 是由系统或者 Qt 本身在不同的场景下发出的。当用户按下/移动鼠标、敲下键盘,或者是窗口关闭/大小发生变化/隐藏或显示都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如鼠标/键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
每一个Qt应用程序都对应一个唯一的 QApplication
应用程序对象,然后调用这个对象的exec()
函数,这样Qt框架内部的事件检测就开始了(程序将进入事件循环来监听应用程序的事件
)。
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow* w = new MainWindow;
w.show();
return a.exec();
}
事件在Qt中产生之后,的分发过程是这样的:
当事件产生之后,Qt使用用应用程序对象调用
notify()
函数将事件发送到指定的窗口:cpp[override virtual] bool QApplication::notify(QObject *receiver, QEvent *e);
事件在发送过程中可以通过事件过滤器进行过滤,默认不对任何产生的事件进行过滤。
cpp// 需要先给窗口安装过滤器, 该事件才会触发 [virtual] bool QObject::eventFilter(QObject *watched, QEvent *event)
当事件发送到指定窗口之后,窗口的事件分发器会对收到的事件进行分类:
cpp[override virtual protected] bool QWidget::event(QEvent *event);
事件分发器会将分类之后的事件(鼠标事件、键盘事件、绘图事件。。。)分发给对应的事件处理器函数进行处理,每个事件处理器函数都有默认的处理动作(我们也可以重写这些事件处理器函数),比如:鼠标事件:
cpp// 鼠标按下 [virtual protected] void QWidget::mousePressEvent(QMouseEvent *event); // 鼠标释放 [virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event); // 鼠标移动 [virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event);
事件和信号的区别
事件和信号都是 QT 框架中的重要特性,它们都可以用于处理用户的交互操作,但是它们之间有一些区别。
从发生角度来看,事件是外部引起的,由外向内传递。而信号是内部发出的,由内向外传递。比如:
- 事件:当用户点击窗口的关闭按钮时,会产生一个关闭事件,这个事件会从窗口传递到应用程序,然后应用程序会处理这个事件,关闭窗口。
- 信号:当用户点击窗口的关闭按钮时,会发出一个关闭信号,这个信号会从窗口传递到应用程序,然后应用程序会处理这个信号,关闭窗口。
从发生顺序来看,事件是先产生后处理,而信号是后产生再处理(这个后产生是相对于事件来说的)。因为只有发生某个事件了,才会有对应的处理动作,这个处理动作可以是直接处理事件,也可以产生一个信号交给其他对象处理。
处理事件的方法
- 重写控件的事件处理器函数,如鼠标事件QWidget::mousePressEvent()、键盘事件QWidget::keyPressEvent()等。
- 重写事件的 event 方法,也就是改变事件的分发方式
- 在控件的父窗口上安装事件过滤器
- 重写 notify 方法
前三种方式是比较常用的,第四种方式比较特殊,一般不建议使用。
事件处理器函数
通过上面的描述可以得知:Qt的事件处理器函数处于食物链的最末端,每个事件处理器函数都对应一个唯一的事件,这为我们重新定义事件的处理动作提供了便利。另外,Qt提供的这些事件处理器函数都是回调函数,也就是说作为使用者我们只需要指定函数的处理动作,关于函数的调用是不需要操心的,当某个事件被触发,Qt框架会调用对应的事件处理器函数。
QWidget类是Qt中所有窗口类的基类,在这个类里边定义了很多事件处理器函数,它们都是受保护的虚函数。我们可以在Qt的任意一个窗口类中重写这些虚函数来重定义它们的行为。下面介绍一些常用的事件处理器函数:
鼠标事件
鼠标按下事件
当鼠标左键、鼠标右键、鼠标中键被按下,该函数被自动调用,通过参数可以得到当前按下的是哪个鼠标键
cpp[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event);
鼠标释放事件
当鼠标左键、鼠标右键、鼠标中键被释放,该函数被自动调用,通过参数可以得到当前释放的是哪个鼠标键
cpp[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event);
鼠标移动事件
当鼠标移动(也可以按住一个或多个鼠标键移动),该函数被自动调用,通过参数可以得到在移动过程中哪些鼠标键被按下了。
cpp[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event);
鼠标双击事件
当鼠标双击该函数被调用,通过参数可以得到是通过哪个鼠标键进行了双击操作。
cpp[virtual protected] void QWidget::mouseDoubleClickEvent(QMouseEvent *event);
鼠标进入事件
当鼠标进入窗口的一瞬间,触发该事件,注意:只在进入的瞬间触发一次该事件
cpp[virtual protected] void QWidget::enterEvent(QEvent *event);
鼠标离开事件
当鼠标离开窗口的一瞬间,触发该事件,注意:只在离开的瞬间触发一次该事件
cpp[virtual protected] void QWidget::leaveEvent(QEvent *event);
键盘事件
键盘按下事件
当键盘上的按键被按下了,该函数被自动调用,通过参数可以得知按下的是哪个键。
cpp[virtual protected] void QWidget::keyPressEvent(QKeyEvent *event);
键盘释放事件
当键盘上的按键被释放了,该函数被自动调用,通过参数可以得知释放的是哪个键。
cpp[virtual protected] void QWidget::keyReleaseEvent(QKeyEvent *event);
窗口重绘事件
当窗口需要刷新的时候,该函数就会自动被调用。窗口需要刷新的情景很多,比如:窗口大小发生变化,窗口显示等,另外我们还可以通过该函数给窗口绘制背景图,总之这是一个需要经常被重写的一个事件处理器函数。
[virtual protected] void QWidget::paintEvent(QPaintEvent *event);
窗口关闭事件
当窗口标题栏的关闭按钮被按下并且在窗口关闭之前该函数被调用,可以通过该函数控制窗口是否被关闭。
[virtual protected] void QWidget::closeEvent(QCloseEvent *event);
重置窗口大小事件
当窗口的大小发生变化,该函数被调用。
[virtual protected] void QWidget::resizeEvent(QResizeEvent *event);
除此之外,关于Qt窗口提供的其他事件处理器函数还有很多,感兴趣的话可以仔细阅读Qt的帮助文档,窗口的事件处理器函数非常好找,规律是这样的:
受保护的虚函数
函数名分为两部分: 事件描述+Event
函数带一个事件类型的参数
重写事件处理器函数
由于事件处理器函数都是虚函数,因此我们就可以添加一个标准窗口类的派生类,这样不仅使子类继承了父类的属性,还可以在这个子类中重写父类的虚函数,总起来说整个操作过程还是 so easy 的。
下面
创建一个Qt项目,添加一个窗口类(让其从某个标准窗口类派生)
在子类中重写从父类继承的虚函数(也就是事件处理器函数)
头文件
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
// 重写事件处理器函数
void closeEvent(QCloseEvent* ev);
void resizeEvent(QResizeEvent* ev);
private:
Ui::MainWindow *ui;
};
源文件
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QCloseEvent>
#include <QMessageBox>
#include <QResizeEvent>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::closeEvent(QCloseEvent *ev)
{
QMessageBox::Button btn = QMessageBox::question(this, "关闭窗口", "您确定要关闭窗口吗?");
if(btn == QMessageBox::Yes)
{
// 接收并处理这个事件
ev->accept();
}
else
{
// 忽略这个事件
ev->ignore();
}
}
void MainWindow::resizeEvent(QResizeEvent *ev)
{
qDebug() << "oldSize: " << ev->oldSize()
<< "currentSize: " << ev->size();
}
QCloseEvent
类是QEvent
类的子类,程序中使用的accept()
或者ignore()
的作用请参考 QEvent类
效果
在上面重写的closeEvent
事件中添加了关闭窗口的判断,这样就可以避免误操作导致窗口被关闭了,效果如下:
如果想要时时检测窗口大小,就可以重写窗口的resizeEvent
事件,这样就可以得到窗口的最新尺寸信息了: