什么是事件
在 Qt 中,是以事件驱动 UI 工具集,包括信号和槽都依赖于 Qt 的事件处理机制。
通常事件是由窗口系统或者 Qt 自身产生的,用以响应所发生的各类事情,比如按下并释放了键盘或者鼠标、窗口因缩放而需要重绘、定时器到期而应有所动作。
如下图可以看到事件比信号更原始,一个下压按钮首先感受到的是鼠标事件,再进行必要的处理以产生按钮下沉而弹起的视觉效果之后,才会发射 clicked()
信号。
如何处理事件
在 Qt 中,事件被封装成对象,所有的事件对象类型都继承自抽象类 QEvent
。
当事件发生时,首先被调用的是 QObject
类中的虚函数 event()
,其参数 QEvent
标识了具体的事件类型。
在桌面应用(Qt Widgets Application)开发中,QWidget
类覆盖了其基类中的 event()
虚函数,并根据具体事件调用具体事件处理函数:
void QWidget::mousePressEvent(QMouseEvent* e); // 鼠标按下事件
void QWidget::mouseReleaseEvent(QMouseEvent* e); // 鼠标抬起事件
void QWidget::mouseMoveEvent(QMouseEvent* e); // 鼠标移动事件
void QWidget::paintEvent(QPaintEvent* e); // 绘图事件
所有事件处理函数都是虚函数,可以被 QWidget
的子类覆盖,以提供针对不同窗口控件类型的事件处理,控件的使用者所关心的往往是定义什么样的槽处理什么样的信号,而控件的实现者则更关心覆盖哪些事件处理函数。
如果程序员希望在窗口自定义的处理事件,可以继承 QWidget
或者其子类,比如 QDialog
、QMainWindow
,在自定义的窗口子类中重写事件处理函数,当相应事件被处罚时,会利用多态的语法机制,所执行到的事件处理函数将是子类中重写的版本,从而实现程序员想要的事件处理效果。
绘图事件
通过绘图事件,可以实现自定义的图像绘制,当有下列情况之一发生时,将触发窗口的绘制事件,即 QWidget
类的 paintEvent()
虚函数会被调用。
- 窗口被创建以后第一次显示出来
- 窗口由隐藏状态转变为可见状态
- 窗口由最小化状态转变为正常或最大化状态
- 窗口因尺寸大小的变化需要呈现更多的内容
QWidget
类的update()
/repaint()
成员函数被调用
如果希望在自己的窗口中显示某个图像,在 QWidget
的窗口子类中可以重写绘图事件函数 paintEvent()
,在其中可用 QPainter
(Qt 二维图形引擎)实现指定的图像绘制、渲染等操作。
案例——图片浏览器
-
文件目录
image-viewer |-- image/ |-- 0.png |-- 1.png |-- 2.png |-- 3.png |-- image-viewer.pro |-- image.qrc # 资源文件 |-- dialog.ui |-- dialog.h |-- dialog.cpp |-- main.cpp
-
image-viewer.pro
QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 SOURCES += \ main.cpp \ dialog.cpp HEADERS += \ dialog.h FORMS += \ dialog.ui RESOURCES += \ image.qrc
-
image.qrc
<RCC> <qresource prefix="/"> <file>image/0.jpg</file> <file>image/1.jpg</file> <file>image/2.jpg</file> <file>image/3.jpg</file> </qresource> </RCC>
-
dialog.ui
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>Dialog</class> <widget class="QDialog" name="Dialog"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>538</width> <height>355</height> </rect> </property> <property name="font"> <font> <pointsize>16</pointsize> </font> </property> <property name="windowTitle"> <string>Dialog</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QFrame" name="frame"> <property name="minimumSize"> <size> <width>480</width> <height>270</height> </size> </property> <property name="maximumSize"> <size> <width>480</width> <height>270</height> </size> </property> <property name="frameShape"> <enum>QFrame::StyledPanel</enum> </property> <property name="frameShadow"> <enum>QFrame::Raised</enum> </property> </widget> </item> <item> <spacer name="horizontalSpacer_2"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> </layout> </item> <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>0</height> </size> </property> </spacer> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <spacer name="horizontalSpacer_3"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="prevButton"> <property name="text"> <string>上一张</string> </property> </widget> </item> <item> <widget class="QPushButton" name="nextButton"> <property name="text"> <string>下一张</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_4"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> </layout> </item> </layout> </widget> <resources/> <connections/> </ui>
-
dialog.h
#ifndef DIALOG_H #define DIALOG_H #include <QDialog> #include <QPainter> QT_BEGIN_NAMESPACE namespace Ui { class Dialog; } QT_END_NAMESPACE class Dialog : public QDialog { Q_OBJECT public: Dialog(QWidget* parent = nullptr); ~Dialog(); private slots: // 上一张按钮点击槽函数 void on_prevButton_clicked(); // 下一张按钮点击槽函数 void on_nextButton_clicked(); private: // 绘图事件 void paintEvent(QPaintEvent*); private: Ui::Dialog* ui; int index; }; #endif // DIALOG_H
-
dialog.cpp
#include "dialog.h" #include "ui_dialog.h" Dialog::Dialog(QWidget* parent) : QDialog(parent), ui(new Ui::Dialog) { ui->setupUi(this); index = 0; } Dialog::~Dialog() { delete ui; } // 上一张按钮点击槽函数 void Dialog::on_prevButton_clicked() { if (--index < 0) { index = 3; } update(); } // 下一张按钮点击槽函数 void Dialog::on_nextButton_clicked() { if (++index > 3) { index = 0; } update(); } // 绘图事件 void Dialog::paintEvent(QPaintEvent*) { // 1. 创建画家对象 QPainter painter(this); // 2. 获取绘图所在矩形区域 QRect rect = ui->frame->frameRect(); // 坐标值平移(让 rect 和 painter 使用相同的坐标系) rect.translate(ui->frame->pos()); // 3. 构建要绘制的图像对象 QImage image(":/image/" + QString::number(index) + ".jpg"); // 4. 使用 painter 将 image 绘制到 rect painter.drawImage(rect, image); }
定时器事件
Qt 通过两套机制为应用程序提供定时功能:
-
定时器事件,由
QObject
提供int QObject::startTimer(int interval); // 启动定时器 void QObject::timerEvent(QTimerEvent* e); // 定时器事件处理函数 void QObject::killTimer(int id); // 关闭定时器
-
定时器信号,由
QTimer
提供
案例——摇奖机
-
文件目录
ernie/ |-- image/ |-- ernie.pro |-- dialog.ui |-- dialog.h |-- dialog.cpp |-- main.cpp
-
dialog.ui
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>Dialog</class> <widget class="QDialog" name="Dialog"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>538</width> <height>351</height> </rect> </property> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="font"> <font> <family>Inconsolata</family> </font> </property> <property name="windowTitle"> <string>Dialog</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <spacer name="horizontalSpacer_3"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QFrame" name="frame"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="minimumSize"> <size> <width>480</width> <height>270</height> </size> </property> <property name="maximumSize"> <size> <width>480</width> <height>270</height> </size> </property> <property name="frameShape"> <enum>QFrame::StyledPanel</enum> </property> <property name="frameShadow"> <enum>QFrame::Raised</enum> </property> </widget> </item> <item> <spacer name="horizontalSpacer_4"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> </layout> </item> <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>0</height> </size> </property> </spacer> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="pushButton"> <property name="text"> <string>开始</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_2"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> </layout> </item> </layout> </widget> <resources/> <connections/> </ui>
-
dialog.h
#ifndef DIALOG_H #define DIALOG_H #include <QDialog> #include <QTimer> #include <QTime> #include <QPainter> #include <QImage> #include <QDir> #include <QVector> #include <QRandomGenerator> QT_BEGIN_NAMESPACE namespace Ui { class Dialog; } QT_END_NAMESPACE class Dialog : public QDialog { Q_OBJECT public: Dialog(QWidget* parent = nullptr); ~Dialog(); private slots: // 按钮点击槽函数 void on_pushButton_clicked(); private: // 加载图片到容器 void loadImages(const QString&); // 定时器事件 void timerEvent(QTimerEvent*); // 绘图事件 void paintEvent(QPaintEvent*); private: Ui::Dialog* ui; QVector<QImage> vector; // 保存图片的容器 int index; // 当前图片在容器中的索引 int timerId; // 定时器 bool isStarted; // 标记摇奖机状态 }; #endif // DIALOG_H
-
dialog.cpp
#include "dialog.h" #include "ui_dialog.h" Dialog::Dialog(QWidget* parent) : QDialog(parent), ui(new Ui::Dialog) { ui->setupUi(this); index = 0; isStarted = false; loadImages("../../../image"); qDebug() << "图片数量 = " << vector.size(); } Dialog::~Dialog() { delete ui; } // 按钮点击槽函数 void Dialog::on_pushButton_clicked() { if (!isStarted) { // 摇奖开始 isStarted = true; timerId = startTimer(50); ui->pushButton->setText("停止"); } else { // 摇奖结束 isStarted = false; killTimer(timerId); ui->pushButton->setText("开始"); } } // 加载图片到容器 void Dialog::loadImages(const QString& path) { QDir dir(path); // 遍历当前目录所有图片 QStringList list1 = dir.entryList(QDir::Files); for (int i = 0; i < list1.size(); i++) { QImage image(path + "/" + list1.at(i)); vector << image; } // 递归遍历子目录中的图片 QStringList list2 = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (int i = 0; i < list2.size(); i++) { loadImages(path + "/" + list2.at(i)); } } // 定时器事件 void Dialog::paintEvent(QPaintEvent*) { QPainter painter(this); QRect rect = ui->frame->frameRect(); rect.translate(ui->frame->pos()); painter.drawImage(rect, vector[index]); } // 绘图事件 void Dialog::timerEvent(QTimerEvent*) { index = QRandomGenerator::global()->generate() % vector.size(); update(); }
鼠标键盘事件
void mousePressEvent(QMouseEvent* e); // 鼠标按下
void mouseReleaseEvent(QMouseEvent* e); // 鼠标抬起
void mouseDoubleClicked(QMouseEvent* e); // 鼠标双击
void mouseMoveEvent(QMouseEvent* e); // 鼠标移动
void keyPressEvent(QKeyEvent* e); // 按键按下
void keyReleaseEvent(QKeyEvent* e); // 按键抬起
案例——移动标签
-
dialog.ui
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>Dialog</class> <widget class="QDialog" name="Dialog"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>600</width> <height>400</height> </rect> </property> <property name="windowTitle"> <string>Dialog</string> </property> <widget class="QLabel" name="label"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>50</width> <height>50</height> </rect> </property> <property name="styleSheet"> <string notr="true">background-color:blue;</string> </property> <property name="text"> <string/> </property> </widget> </widget> <resources/> <connections/> </ui>
-
dialog.h
#ifndef DIALOG_H #define DIALOG_H #include <QDialog> #include <QMouseEvent> #include <QKeyEvent> QT_BEGIN_NAMESPACE namespace Ui { class Dialog; } QT_END_NAMESPACE class Dialog : public QDialog { Q_OBJECT public: Dialog(QWidget* parent = nullptr); ~Dialog(); private: void mousePressEvent(QMouseEvent*); void mouseReleaseEvent(QMouseEvent*); void mouseMoveEvent(QMouseEvent*); void keyPressEvent(QKeyEvent* e); private: Ui::Dialog* ui; bool draggable; QPoint point; }; #endif // DIALOG_H
-
dialog.cpp
#include "dialog.h" #include "ui_dialog.h" Dialog::Dialog(QWidget* parent) : QDialog(parent), ui(new Ui::Dialog) { ui->setupUi(this); } Dialog::~Dialog() { delete ui; } void Dialog::mousePressEvent(QMouseEvent* e) { if (e->button() == Qt::LeftButton) { // 后去 Label 所在矩形区域 QRect rect = ui->label->frameRect(); rect.translate(ui->label->pos()); if (rect.contains(e->pos())) { draggable = true; point = ui->label->pos() - e->pos(); } } } void Dialog::mouseReleaseEvent(QMouseEvent* e) { if (e->button() == Qt::LeftButton) { draggable = false; } } void Dialog::mouseMoveEvent(QMouseEvent* e) { if (draggable) { // 计算 Label 要移动的位置 QPoint newPoint = e->pos() + point; QSize parentSize = size(); QSize labelSize = ui->label->size(); // x 范围 0 ~ parentWidht-labelWidth if (newPoint.x() < 0) { newPoint.setX(0); } else if (newPoint.x() > parentSize.width() - labelSize.width()) { newPoint.setX(parentSize.width() - labelSize.width()); } // y 范围 0 ~ parentHeight-labelHeight if (newPoint.y() < 0) { newPoint.setY(0); } else if (newPoint.y() > parentSize.height() - labelSize.height()) { newPoint.setY(parentSize.height() - labelSize.height()); } // 移动 Label 到新位置 ui->label->move(newPoint); } } void Dialog::keyPressEvent(QKeyEvent* e) { int x = ui->label->pos().x(); int y = ui->label->pos().y(); int step = 10; if (e->key() == Qt::Key_Up) { ui->label->move(x, y - step); } else if (e->key() == Qt::Key_Down) { ui->label->move(x, y + step); } else if (e->key() == Qt::Key_Left) { ui->label->move(x - step, y); } else { ui->label->move(x + step, y); } }