QT消息机制和事件 - 鼠标事件、键盘按下事件、绘图事件、定时器事件处理

事件

  • 事件(event)是由系统或者Qt本身在不同时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等。另一些事件则由系统自动发出,如定时器事件。

Qt消息循环机制

  • Qt程序需要在main函数创建一个QApplication对象,然后调用它的exec函数,这个函数就是开始Qt的事件循环。在执行exec函数之后,程序将进入事件循环来监听应用程序的事件。
  • 当应用程序监听到事件触发后,先将请求交给事件过滤器,过滤后再交给事件分发器,由事件分发器交给具体的事件处理器进行处理。
  • 事件过滤器,事件分发器和事件处理器,默认都会对事件进行处理,如果我们想要自己处理事件,就重写对应的事件函数。然后在对应的事件函数中,进行相应的处理。
  • 一般来说,重写事件处理函数的场景比较多,事件过滤和事件分发,基本上交给系统处理就可以了,我们不用自己处理。为了说明事件过滤器和事件分发器的作用,因此下面也重写了事件过滤函数和事件分发函数。
  • 由于重写事件处理器函数比较常用,阅读时可以先查看事件处理,再查看事件过滤和事件分发。

事件过滤

事件过滤函数

  • bool eventFilter(QObject *watched, QEvent *event);
  • 返回值是bool类型,如果返回true,表示用户要处理这个事件,不向下分发事件。

示例

  • 头文件
c 复制代码
  #ifndef WIDGET_H
  #define WIDGET_H
  
  #include <QWidget>
  #include <QEvent>
  
  namespace Ui {
  class Widget;
  }
  
  class Widget : public QWidget
  {
      Q_OBJECT
  
  public:
      explicit Widget(QWidget *parent = 0);
      ~Widget();
  protected:
  
      // 键盘按下事件
      void keyPressEvent(QKeyEvent *event);
  
      // 事件分发
      bool event(QEvent* ev);
  
      // 重写事件过滤器函数
      bool eventFilter(QObject *watched, QEvent *event);
  private:
      Ui::Widget *ui;
  };
  
  #endif // WIDGET_H
  • 源文件
c 复制代码
  #include "widget.h"
  #include "ui_widget.h"
  #include <QMessageBox>
  #include <QDebug>
  #include <QMouseEvent>
  
  Widget::Widget(QWidget *parent) :
      QWidget(parent),
      ui(new Ui::Widget)
  {
      ui->setupUi(this);
  
      // 安装事件过滤器
      installEventFilter(this);
  }
  
  Widget::~Widget()
  {
      delete ui;
  }
  
  void Widget::keyPressEvent(QKeyEvent *event)
  {
      // 判断键盘按下事件
      if(event->type() == QEvent::KeyPress){
          QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
          // 判断按下的是否为tab键
          if(keyEvent->key() == Qt::Key_Tab){
              QMessageBox::question(this, "提示", "事件处理函数中Tab键按下", QMessageBox::Yes);
          }
      }
  }
  
  bool Widget::event(QEvent *ev)
  {
      // 判断键盘按下事件
      if(ev->type() == QEvent::KeyPress){
          QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev);
          // 判断按下的是否为tab键
          if(keyEvent->key() == Qt::Key_Tab){
              QMessageBox::question(this, "提示", "事件分发函数中Tab键按下", QMessageBox::Yes);
  
              // 返回true, 表示自己处理这个事件,不向下分发了,这样对应的事件处理函数就接收不到该事件了
              return true;
          }
      }
  
      // 其他事件交给父类,默认处理
      return QWidget::event(ev);
  }
  
  bool Widget::eventFilter(QObject *watched, QEvent *event)
  {
      // 判断是哪个窗口,这里是当前窗口
      if(watched == this){
  
          if(event->type() == QEvent::KeyPress){
              QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
              // 判断按下的是否为tab键
              if(keyEvent->key() == Qt::Key_Tab){
                  QMessageBox::question(this, "提示", "Tab键按下", QMessageBox::Yes);
  
                  // 返回true, 表示自己处理这个事件,不向下分发了
                  // 这样不仅事件处理函数接收不到该事件,事件分发函数也接受不到该事件
                  return true;
              }
          }
      }
  
  	// 其他事件交给系统处理
      return QWidget::eventFilter(watched, event);
  }
  • 如果在事件过滤器中处理了事件并且不往下传递,那么在事件分发器和事件处理器都不会接收到相应的事件。

事件分发

事件分发函数

  • bool event(QEvent* ev);
  • 返回值是bool类型,如果返回true,表示用户要处理这个事件,不向下分发事件。

示例

  • 下来通过一个例子演示下,在事件分发函数中,处理键盘的tab键按下事件,并且处理后不向下传递,这样对应的事件处理函数就接收不到该事件了。
  • 头文件
c 复制代码
  #ifndef WIDGET_H
  #define WIDGET_H
  
  #include <QWidget>
  #include <QEvent>
  
  namespace Ui {
  class Widget;
  }
  
  class Widget : public QWidget
  {
      Q_OBJECT
  
  public:
      explicit Widget(QWidget *parent = 0);
      ~Widget();
  protected:
  
      // 键盘按下事件
      void keyPressEvent(QKeyEvent *event);
  
      // 事件分发
      bool event(QEvent* ev);
  private:
      Ui::Widget *ui;
  };
  
  #endif // WIDGET_H
  • 源文件
c 复制代码
  #include "widget.h"
  #include "ui_widget.h"
  #include <QMessageBox>
  #include <QDebug>
  #include <QMouseEvent>
  
  Widget::Widget(QWidget *parent) :
      QWidget(parent),
      ui(new Ui::Widget)
  {
      ui->setupUi(this);
  }
  
  Widget::~Widget()
  {
      delete ui;
  }
  
  void Widget::keyPressEvent(QKeyEvent *event)
  {
      // 判断键盘按下事件
      if(event->type() == QEvent::KeyPress){
          QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
          // 判断按下的是否为tab键
          if(keyEvent->key() == Qt::Key_Tab){
              QMessageBox::question(this, "提示", "事件处理函数中Tab键按下", QMessageBox::Yes);
          }
      }
  }
  
  bool Widget::event(QEvent *ev)
  {
      // 判断键盘按下事件
      if(ev->type() == QEvent::KeyPress){
          QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev);
          // 判断按下的是否为tab键
          if(keyEvent->key() == Qt::Key_Tab){
              QMessageBox::question(this, "提示", "Tab键按下", QMessageBox::Yes);
  
              // 返回true, 表示自己处理这个事件,不向下分发了,这样对应的事件处理函数就接收不到该事件了
              return true;
          }
      }
  
  
      // 其他事件交给父类,默认处理
      return QWidget::event(ev);
  }
  • 演示
  • 通过示例可以看到,虽然在事件处理函数中对tab键按下进行了处理,但由于在事件分发函数中,并未向下传递tab键按下事件,因此事件处理函数并未被触发。

事件处理

  • 事件触发后,先经过事件过滤器进行过滤,再经过事件分发器,下发到具体的事件处理器中,默认事件处理器也会自动处理事件,如果想要自己处理,就可以重写事件处理函数。
  • QObject类 是所有Qt类的父类,因此所有Qt类都可以继承QObject类的事件处理函数,QObject类的事件函数主要是定时器事件函数,由系统自动触发
  • QWidget类 是所有Qt窗口类的事件函数,因此所有Qt窗口类都可以继承QWidget类的事件处理函数,QWidget类的事件函数比较多,比如键盘按下事件,鼠标移动事件等,QWidget类的事件函数,主要由用户触发

QObject类的事件处理函数

  • 定时器事件函数
  • virtual void timerEvent(QTimerEvent *event)

QWidget类的事件处理函数

  • 窗口关闭事件函数
  • virtual void closeEvent(QCloseEvent *event)
  • 窗口隐藏事件函数
  • virtual void hideEvent(QHideEvent *event)
  • 窗口显示事件函数
  • virtual void showEvent(QShowEvent *event)
  • 窗口重设大小事件函数
  • virtual void resizeEvent(QResizeEvent *event)
  • 键盘按下事件函数
  • virtual void keyPressEvent(QKeyEvent *event)
  • 键盘松开事件函数
  • virtual void keyReleaseEvent(QKeyEvent *event)
  • 鼠标双击事件函数
  • virtual void mouseDoubleClickEvent(QMouseEvent *event)
  • 鼠标移动事件函数
  • virtual void mouseMoveEvent(QMouseEvent *event)
  • 鼠标按下事件函数
  • virtual void mousePressEvent(QMouseEvent *event)
  • 鼠标松开事件函数
  • virtual void mouseReleaseEvent(QMouseEvent *event)
  • 绘图事件函数
  • virtual void paintEvent(QPaintEvent *event)

示例1:窗口事件处理

  • 接下来我们通过一个例子来演示下对窗口关闭窗口重设大小的事件如何处理
  • 创建一个类,继承于QWidget类
  • 头文件
c 复制代码
  #ifndef WIDGET_H
  #define WIDGET_H
  
  #include <QWidget>
  
  namespace Ui {
  class Widget;
  }
  
  class Widget : public QWidget
  {
      Q_OBJECT
  
  public:
      explicit Widget(QWidget *parent = 0);
      ~Widget();
  protected:
      // 重写窗口关闭事件函数
      void closeEvent(QCloseEvent *event);
      // 重写重绘窗口大小事件函数
      void resizeEvent(QResizeEvent *event);
  private:
      Ui::Widget *ui;
  };
  
  #endif // WIDGET_H
  • 源文件
c 复制代码
  #include "widget.h"
  #include "ui_widget.h"
  #include <QMessageBox>
  #include <QCloseEvent>
  #include <QResizeEvent>
  #include <QDebug>
  
  Widget::Widget(QWidget *parent) :
      QWidget(parent),
      ui(new Ui::Widget)
  {
      ui->setupUi(this);
  }
  
  Widget::~Widget()
  {
      delete ui;
  }
  
  void Widget::closeEvent(QCloseEvent *event)
  {
      int retStatus = QMessageBox::question(this, "提示", "确定关闭窗口吗?", QMessageBox::Yes | QMessageBox::No);
      if(retStatus == QMessageBox::Yes){
          // 处理该事件,也就是关闭窗口
          event->accept();
      }else{
          // 忽略该事件,也就是不关闭窗口
          event->ignore();
      }
  }
  
  void Widget::resizeEvent(QResizeEvent *event)
  {
      qDebug()<<"old size : "<<event->oldSize();
      qDebug()<<"new size : "<<event->size();
  }
  • 效果展示

示例2:鼠标事件处理

  • 再实现一个对鼠标按下鼠标松开鼠标移动的事件处理流程。
  • 头文件
c 复制代码
  #ifndef WIDGET_H
  #define WIDGET_H
  
  #include <QWidget>
  
  namespace Ui {
  class Widget;
  }
  
  class Widget : public QWidget
  {
      Q_OBJECT
  
  public:
      explicit Widget(QWidget *parent = 0);
      ~Widget();
  protected:
      // 重写鼠标按下事件
      void mousePressEvent(QMouseEvent* event);
      // 重写鼠标松开事件
      void mouseReleaseEvent(QMouseEvent* event);
      // 重写鼠标移动事件
      void mouseMoveEvent(QMouseEvent* event);
  
  private:
      Ui::Widget *ui;
  };
  
  #endif // WIDGET_H
  • 源文件
c 复制代码
  #include "widget.h"
  #include "ui_widget.h"
  #include <QMessageBox>
  #include <QDebug>
  #include <QMouseEvent>
  
  Widget::Widget(QWidget *parent) :
      QWidget(parent),
      ui(new Ui::Widget)
  {
      ui->setupUi(this);
  
      // 设置鼠标被追踪
      // 不设置该属性,鼠标按下移动时才能触发鼠标移动事件函数
      // 设置该属性后,不管鼠标是否按下,鼠标移动事件函数都会被触发
      // this->setMouseTracking(true);
  
      ui->label->setText("鼠标正常");
  }
  
  Widget::~Widget()
  {
      delete ui;
  }
  
  void Widget::mousePressEvent(QMouseEvent *event)
  {
      if(event->button() == Qt::LeftButton){
          ui->label->setText("鼠标左键按下");
      }else if(event->button() == Qt::RightButton){
          ui->label->setText("鼠标右键按下");
      }else{
          ui->label->setText("鼠标按下");
      }
  }
  
  void Widget::mouseReleaseEvent(QMouseEvent *event)
  {
      ui->label->setText("鼠标松开");
  }
  
  void Widget::mouseMoveEvent(QMouseEvent *event)
  {
      QString msg;
      msg = "鼠标移动(" + QString::number(event->x()) + "," + QString::number(event->y()) + ")";
      ui->label->setText(msg);
  }
  • 效果展示

示例3:绘图事件处理

  • 处理绘图事件算是很常用的一个功能,下面通过一个示例进行简单说明
  • 头文件
c 复制代码
  #ifndef WIDGET_H
  #define WIDGET_H
  
  #include <QWidget>
  #include <QEvent>
  #include <QPainter>
  
  namespace Ui {
  class Widget;
  }
  
  class Widget : public QWidget
  {
      Q_OBJECT
  
  public:
      explicit Widget(QWidget *parent = 0);
      ~Widget();
  protected:
  
      // 绘图事件
      void paintEvent(QPaintEvent* e);
  
  private:
      Ui::Widget *ui;
  };
  
  #endif // WIDGET_H
  • 源文件
c 复制代码
  #include "widget.h"
  #include "ui_widget.h"
  #include <QMessageBox>
  #include <QDebug>
  
  
  Widget::Widget(QWidget *parent) :
      QWidget(parent),
      ui(new Ui::Widget)
  {
      ui->setupUi(this);
  
  }
  
  Widget::~Widget()
  {
      delete ui;
  }
  
  void Widget::paintEvent(QPaintEvent *e)
  {
      // 实例化画家对象, this表示绘图设备是当前窗口
      QPainter painter(this);
  
      // 画线
      painter.drawLine(QPoint(0, 0), QPoint(100, 100));
  
      // 设置画笔(红色)
      QPen pen(QColor(255, 0, 0));
  
      // 设置画笔宽度
      pen.setWidth(3);
      // 设置画笔风格
      pen.setStyle(Qt::DotLine);
      painter.setPen(pen);
  
      // 画矩形
      painter.drawRect(QRect(0, 0, 100, 100));
  
      // 设置画刷
      QBrush brush(Qt::green);
      // 设置画刷风格
      brush.setStyle(Qt::Dense1Pattern);
      painter.setBrush(brush);
  
      // 画圆
      painter.drawEllipse(QPoint(150, 50), 50, 50);
  
      // 画文字
      painter.drawText(QRect(0, 150, 150, 150), "HELLO");
  
      // 画图片
      painter.drawPixmap(100, 100, QPixmap(":/gray.jpg"));
  }
  • 演示
  • 绘图事件比较特殊,默认是系统自动触发,但是还可以手动进行触发,那就是在函数中调用 update() 函数,调用之后也会自动触发绘事件,对应的绘图事件处理函数会被执行。比如可以在鼠标点击槽函数中调用 update() 函数,这里就不进行演示了。

示例4:定时器事件处理

  • 定时器事件由系统触发,可以重写处理函数,在其中进行相应操作,这也是非常常用的一个功能。
  • 头文件
c 复制代码
  #ifndef WIDGET_H
  #define WIDGET_H
  
  #include <QWidget>
  #include <QEvent>
  #include <QTimerEvent>
  
  namespace Ui {
  class Widget;
  }
  
  class Widget : public QWidget
  {
     Q_OBJECT
  
  public:
     explicit Widget(QWidget *parent = 0);
     ~Widget();
  protected:
  
     // 定时器事件处理函数
     void timerEvent(QTimerEvent *e);
  
  private:
     Ui::Widget *ui;
  
     // 定时器id
     int timerId;
  };
  
  #endif // WIDGET_H
  • 源文件
c 复制代码
  #include "widget.h"
  #include "ui_widget.h"
  #include <QMessageBox>
  #include <QDebug>
  
  
  Widget::Widget(QWidget *parent) :
      QWidget(parent),
      ui(new Ui::Widget)
  {
      ui->setupUi(this);
  
      // 启动定时器,定时事件为1秒
      timerId = startTimer(1000);
  }
  
  Widget::~Widget()
  {
      // 停止定时器
      killTimer(timerId);
  
      delete ui;
  }
  
  void Widget::timerEvent(QTimerEvent *e)
  {
      qDebug()<<"定时器事件触发";
  }
相关推荐
蜀黍@猿33 分钟前
【C++ 基础】从C到C++有哪些变化
c++
Am心若依旧40934 分钟前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
zh路西法44 分钟前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(一):从电梯出发的状态模式State Pattern
c++·决策树·状态模式
轩辰~1 小时前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
lxyzcm1 小时前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
蜀黍@猿2 小时前
C/C++基础错题归纳
c++
雨中rain2 小时前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
ALISHENGYA3 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(实战项目二)
数据结构·c++·算法
arong_xu3 小时前
现代C++锁介绍
c++·多线程·mutex
汤姆和杰瑞在瑞士吃糯米粑粑3 小时前
【C++学习篇】AVL树
开发语言·c++·学习