【QT】事件处理机制案例——图片浏览器、摇奖机、移动标签

什么是事件

在 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或者其子类,比如 QDialogQMainWindow,在自定义的窗口子类中重写事件处理函数,当相应事件被处罚时,会利用多态的语法机制,所执行到的事件处理函数将是子类中重写的版本,从而实现程序员想要的事件处理效果。

绘图事件

通过绘图事件,可以实现自定义的图像绘制,当有下列情况之一发生时,将触发窗口的绘制事件,即 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);
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值