QT 学习笔记(九)

news/2024/11/28 9:42:59/

文章目录

  • 一、事件的接收和忽略
    • 1. 准备工作
    • 2. 接收和忽略
  • 二、event() 函数
    • 1. 简介
    • 2. 实例演示
    • 3. 总结
  • 三、事件过滤器
  • 四、总结(细看)
    • 1. 知识点汇总
    • 2. QT 的事件处理
  • 五、事件、事件的接收和忽略、event() 函数和事件过滤器代码
    • 1. 主窗口头文件 mywidget.h
    • 2. 主窗口源文件 mywidget.cpp
    • 3. 标签头文件 mylabel.h
    • 4. 标签源文件 mylabel.cpp
    • 5. 按钮头文件 mybutton.h
    • 6. 按钮源文件 mybutton.cpp

由于每次代码都是在原有程序上修改,因此除了新建项目,不然一般会在学完后统一展示代码。
提示:具体项目创建流程和注意事项见QT 学习笔记(一)
提示:具体项目准备工作和细节讲解见QT 学习笔记(二)

一、事件的接收和忽略

  • 在上文的代码基础上继续进行操作。QT 学习笔记(八)

1. 准备工作

  • 在 ui 界面新建一个按钮,在项目文件当中新建一个 mybutton 的 C++ 类。
  • 将 ui 界面的按钮提升为 mybutton 。自定义控件提升详见QT 学习笔记(七)。
  • 在 主窗口源文件 mywidget.cpp 当中对按钮进行操作,对基本功能进行测验。
  • 注意:要在项目文件 day8.pro 当中添加 CONFIG +=C++11 才可以使 connect 功能正常实现。
    在这里插入图片描述
  • 当我们点击按钮时,会在输出窗口输出按钮被按下,如下图所示:
    在这里插入图片描述

2. 接收和忽略

  • 在上一步的检测操作完成后,在按钮源文件 mybutton.cpp 当中,进行事件的接收和忽略操作。
  • 通过 if 语句,对鼠标左键按下在输出窗口输出按下的是左键,对其他鼠标按键不做处理,调用原函数。
  • 在上述操作当中,鼠标左键对应的就是事件的接收其他按键对应的就是事件的忽略
    在这里插入图片描述
  • 在这里有一个问题,当我们按下鼠标左键时,会输出按下的是左键,但当我们按下其他按键时,并没有调用原函数,即输出按钮被按下。
  • 这是因为当我们按下鼠标左键的时候,事件的信号被 qDebug() 输出语句给拦截了,不会再向后传递,无法走到 else 对应的内容。
  • 因此,在我们编写代码时,事件的忽略不要编写实现代码即可
  • 事件的接收,就不会往下传递。
  • 事件的忽略,事件会继续往下传递。
  • 我们可以把 QT 的事件传递看成链状:如果子类没有处理这个事件,就会继续向其父组件传递。
  • QT 的事件对象有两个函数:accept() 和 ignore() 。
  • accept() 用来告诉 QT,这个类的事件处理函数想要处理这个事件;
  • 如果一个事件处理函数调用了一个事件对象的 accept() 函数,这个事件就不会被继续传播给其父组件。
  • ignore() 用来告诉 QT,这个类的事件处理函数不想要处理这个事件;
  • 如果调用了事件的 ignore() 函数,QT 会从其父组件中寻找另外的接受者。在事件处理函数中,可以使 isAccepted() 来查询这个事件是不是已经被接收了。
  • 我们在主窗口 myWidget.cpp 中,重写鼠标点击事件,每当鼠标按下的时候,输出 ++++++++++,同时,将按钮窗口 mybutton.cpp 当中的 e->ignore() 注释掉,得到如下现象:
    在这里插入图片描述
  • 这样操作并不会输出 ++++++++++。此时,我们取消按钮窗口 mybutton.cpp 当中的 e->ignore() 的注释,得到下面的现象:
    在这里插入图片描述
  • 当我们点击按钮的时候,会同时输出按下的是左键和 ++++++++++,当我们点击按钮外的界面时,只会输出 ++++++++++。
  • 这是因为没有接收到信号,所以没有触发那里的 e->ignore()。而这里的 e->ignore() 可以让这个信号再次传递下去,不过不是传递给 mybutton,而是 父组件 mywidget。
  • 这个 e->ignore() 主要使用在关闭项目当中,也就是窗口右上角大家熟知的红色关闭按钮,在此简单实现一下(只展示实现现象,具体代码会在总体代码当中展示)。
  • 在我们实际关闭窗口的时候,往往会弹出一句 确定关闭吗? 这种类似的提示。
  • 为了实现这样的功能,需要包含头文件 QMessageBox ,使用 question() 函数。question() 函数中的第一个参数是指定父对象,第二个参数是标题,第三个参数是提示内容,第四个参数不写的话默认有两个按钮:yes 和 no 。
  • 为实现功能,只需要一个简单 if 语句即可,如果点击 yes ,则处理关闭窗口事件,接收事件,事件就不会再往下传递;如果点击 no ,则忽略事件,事件继续给父组件传递。
  • 具体现象如下图所示:
    在这里插入图片描述

二、event() 函数

1. 简介

  • 事件对象创建完毕后,QT 将这个事件对象传递给 QObject 的 event() 函数。event() 函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。
  • 因此,event() 函数主要用于 事件的分发。所以,如果你希望在事件分发之前做一些操作,就可以重写这个 event() 函数了。
  • 例如,我们希望在一个 QWidget 组件中监听 tab 键的按下,那么就可以继承 QWidget,并重写它的 event() 函数,来达到这个目的。
  • 事件分发如下图所示。
    在这里插入图片描述
  • 与其他的事件返回值不同的是,event() 事件的返回值是 bool 类型。
  • 如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是 true,那么 QT 会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象, 而是会继续处理事件队列中的下一事件。
  • 在 event() 函数中,调用事件对象的 accept() 和 ignore() 函数是没有作用的,不会影响到事件的传播。
  • 相当于在所有信号接收前进行一个检查,满足要求的停止工作,其他的继续原动作。

2. 实例演示

  • 在此以定时器为例,重写 event() 函数,使得定时器停止工作,其他部件保持原动作。代码和实现结果如下:
//重写event事件
bool myWidget::event(QEvent *e)
{if(e->type()==QEvent::Timer){//干掉定时器//如果返回true,事件停止传播return true;}else{return QWidget::event(e);}
}

在这里插入图片描述

  • 将其恢复正常需要一个强制类型转换,只需在上述代码当中添加如下代码即可:
QTimerEvent *env = static_cast<QTimerEvent *>(e);timerEvent(env);
  • 正常现象如下图所示:

在这里插入图片描述

  • 我们在此继续进行一个按键 B 的 event() 函数重写,只有当我们按下按键 B 的时候,事件才继续,其他的事件均终止。代码和实现结果如下:
else if(e->type()==QEvent::KeyPress){//类型转换QKeyEvent *env = static_cast<QKeyEvent *>(e);if(env->key()==Qt::Key_B){return QWidget::event(e);}return true;}

在这里插入图片描述

3. 总结

  • QTimerEvent() 和 QKeyEvent() 这样的函数,就是前面所说的事件处理器 event handler。
  • event() 函数中实际是通过事件处理器来响应一个具体的事件。这相当于 event() 函数将具体事件的处理“委托”给具体的事件处理器。而这些事件处理器是 protected virtual 的,因此,我们重写了某一个事件处理器,即可让 QT 调用我们自己实现的版本。
  • event() 是一个集中处理不同类型的事件的地方。如果你想重写一大堆事件处理器,就可以重写这个 event() 函数,通过 QEvent::type() 判断不同的事件。由于重写 event() 函数需要十分小心注意父类的同名函数的调用,一不留神就可能出现问题。
  • 因此,一般只重写事件处理器(当然,也必须记得是不是应该调用父类的同名处理器)。这其实也表明了 event() 函数的另外一个作用:屏蔽掉某些不需要的事件处理器

三、事件过滤器

  • 很多时候,对象需要查看、甚至要拦截发送到另外对象的事件。例如,对话框可能想要拦截按键事件,不让别的组件接收到;或者要修改回车键的默认处理。
  • 通过前文,我们已经知道,QT 创建了 QEvent 事件对象之后,会调用 QObject 的 event() 函数处理事件的分发。显然,可以在 event() 函数中实现拦截的操作。由于 event() 函数是 protected 的,因此,需要继承已有类。如果组件很多,就需要重写很多个 event() 函数。这当然相当麻烦,更不用说重写 event() 函数还得小心一堆问题。
  • 因此,我们采用 QT 提供的另外一种机制来达到这一目的:事件过滤器。

在这里插入图片描述

  • QObject 有一个 eventFilter() 函数,用于建立事件过滤器。函数原型如下:
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );
  • 事件过滤器,可以理解成一种过滤代码。事件过滤器会检查接收到的事件。如果这个事件是我们感兴趣的类型,就进行我们自己的处理;如果不是,就继续转发。这个函数返回一个 bool 类型,如果你想将参数 event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回 false。
  • 事件过滤器的调用时间是目标对象(也就是参数里面的 watched 对象)接收到事件对象之前。也就是说,如果你在事件过滤器中停止了某个事件,那么,watched 对象以及以后所有的事件过滤器根本不会知道这么一个事件。
  • 在实现事件过滤器之前,我们要在 ui 界面选取一个合适的标签,在这里选择的是 label_2 。 在选择完成后进行操作安装过滤器,该函数的参数是由哪个函数的父对象进行处理。
//安装过滤器
ui->label_2->installEventFilter(this);
  • 通过事件过滤器,将 label_2 ,转换为鼠标移动,并实时显示出对应的 x 和 y 坐标,其他事件保持不变。(鼠标按下和释放的整体流程与鼠标移动是相同的,按钮的操作方法和标签也是相同的,具体代码在汇总时展示)

在这里插入图片描述

  • 注意:事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。

四、总结(细看)

1. 知识点汇总

  • QT 的事件是整个 QT 框架的核心机制之一。涉及到的函数众多,而处理方法也很多。
  • QT 中有很多种事件:鼠标事件、键盘事件、大小改变的事件、位置移动的事件等等。如何处理这些事件,实际有两种选择:
  • (1) 所有事件对应一个事件处理函数,在这个事件处理函数中用一个很大的分支语句进行选择。
  • (2) 每一种事件对应一个事件处理函数。QT 就是使用的这么一种机制:mouseEvent() ,keyPressEvent() 等。
  • QT 具有这么多种事件处理函数,肯定有一个地方对其进行分发,否则,QT 怎么知道哪一种事件调用哪一个事件处理函数呢?这个分发的函数,就是 event()。显然,当 QMouseEvent 产生之后, event() 函数将其分发给 mouseEvent() 事件处理器进行处理。
  • event() 函数会有两个问题:
  • (1) event() 函数是一个 protected 的函数,这意味着我们要想重写 event() ,必须继承一个已有的类。试想,我的程序根本不想要鼠标事件,程序中所有组件都不允许处理鼠标事件,是不是我得继承所有组件,一 一重写其 event() 函数?protected 函数带来的另外一个问题是,如果我基于第三方库进行开发,而对方没有提供源代码,只有一个链接库,其它都是封装好的。我怎么去继承这种库中的组件呢?
  • (2) event() 函数的确有一定的控制,不过有时候需求更严格一些:我希望那些组件根本看不到这种事件。event() 函数虽然可以拦截,但其实也是接收到了 QMouseEvent 对象。我连让它收都收不到。这样做的好处是,模拟一种系统根本没有那个事件的效果,所以其它组件根本不会收到这个事件,也就无需修改自己的事件处理函数。
  • 这两个问题是 event() 函数无法处理的。于是,QT 提供了另外一种解决方案:事件过滤器。事件过滤器给我们一种能力,让我们能够完全移除某种事件。事件过滤器可以安装到任意 QObject 类型上面,并且可以安装多个。如果要实现全局的事件过滤器,则可以安装到 QApplication 或者 QCoreApplication 上面。这里需要注意的是,如果使用 installEventFilter() 函数给一个对象安装事件过滤器,那么该事件过滤器只对该对象有效,只有这个对象的事件需要先传递给事件过滤器的 eventFilter() 函数进行过滤,其它对象不受影响。如果给 QApplication 对象安装事件过滤器,那么该过滤器对程序中的每一个对象都有效,任何对象的事件都是先传给 eventFilter() 函数。
  • 事件过滤器可以解决刚刚我们提出的 event() 函数的两点不足:
  • (1) 事件过滤器不是 protected 的,因此我们可以向任何 QObject 子类安装事件过滤器;
  • (2) 事件过滤器在目标对象接收到事件之前进行处理,如果我们将事件过滤掉,目标对象根本不会见到这个事件。

2. QT 的事件处理

  • QT 的事件处理,实际上是有五个层次:
  • (1) 重写 paintEvent() 、mousePressEvent() 等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
  • (2) 重写 event() 函数。event() 函数是所有对象的事件入口,QObject 和 QWidget 中的实现,默认是把事件传递给特定的事件处理函数。
  • (3) 在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。
  • (4) 在 QCoreApplication::instance() 上面安装事件过滤器。该过滤器将过滤所有对象的所有事件,因此和 notify() 函数一样强大,但是它更灵活,因为可以安装多个过滤器。全局的事件过滤器可以看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
  • (5) 重写 QCoreApplication::notify() 函数。这是最强大的,和全局事件过滤器一样提供完全控制,并且不受线程的限制。但是全局范围内只能有一个被使用(因为 QCoreApplication 是单例的)。

五、事件、事件的接收和忽略、event() 函数和事件过滤器代码

1. 主窗口头文件 mywidget.h

#ifndef MYWIDGET_H
#define MYWIDGET_H#include <QWidget>namespace Ui {
class myWidget;
}class myWidget : public QWidget
{Q_OBJECTpublic:explicit myWidget(QWidget *parent = nullptr);~myWidget();protected://键盘按下事件void keyPressEvent(QKeyEvent *);//计时器事件void timerEvent(QTimerEvent *);//重写鼠标点击事件void mousePressEvent(QMouseEvent *);//关闭事件void closeEvent(QCloseEvent *);//重写event事件bool event(QEvent *);//事件过滤器bool eventFilter(QObject *,QEvent *);private:Ui::myWidget *ui;int timeid;int timeid2;
};#endif // MYWIDGET_H

2. 主窗口源文件 mywidget.cpp

#include "mywidget.h"
#include "ui_mywidget.h"
#include <QDebug>
#include <QKeyEvent>
#include <QCloseEvent>
#include <QMessageBox>
#include <QEvent>myWidget::myWidget(QWidget *parent) :QWidget(parent),ui(new Ui::myWidget)
{ui->setupUi(this);//启动计时器timeid = this->startTimer(1000);  //毫秒为单位。每隔1s触发一次定时器timeid2 = this->startTimer(500);  //毫秒为单位。每隔0.5s触发一次定时器connect(ui->pushButton,&mybutton::clicked,[=](){qDebug()<<"按钮被按下";});//安装过滤器ui->label_2->installEventFilter(this);//设置鼠标追踪ui->label_2->setMouseTracking(tr);
}myWidget::~myWidget()
{delete ui;
}//键盘按下事件
void myWidget::keyPressEvent(QKeyEvent *e)
{qDebug()<<(char)e->key();if(e->key()==Qt::Key_A){qDebug()<<"Qt::Key_A";}
}//计时器事件
void myWidget::timerEvent(QTimerEvent *e)
{if(e->timerId()==this->timeid){static int sec = 0;ui->label->setText(QString("<center><h1>time out:%1</h1></center>").arg(sec++));//停止计时器/*if(5==sec){this->killTimer(this->timeid);}    */}else if (e->timerId()==this->timeid2){static int sec = 0;ui->label_2->setText(QString("<center><h1>time out:%1</h1></center>").arg(sec++));//停止计时器/*if(5==sec){this->killTimer(this->timeid);}    */}
}//重写鼠标点击事件
void myWidget::mousePressEvent(QMouseEvent *e)
{qDebug()<<"++++++++++";
}//重写鼠标点击事件
void myWidget::closeEvent(QCloseEvent *e)
{int ret = QMessageBox::question(this,"question","是否需要关闭窗口");//question 第一个参数是指定父对象,第二个参数是标题,//第三个参数是提示内容,第四个参数不写的话默认有两个按钮:yes和no。if(ret==QMessageBox::Yes){//关闭窗口//处理关闭窗口事件,接收事件,事件就不会再往下传递e->accept();}else{//不关闭窗口//忽略事件,事件继续给父组件传递e->ignore();}
}//重写event事件
bool myWidget::event(QEvent *e)
{if(e->type()==QEvent::Timer){//干掉定时器//如果返回true,事件停止传播//QTimerEvent *e//QTimerEvent *env = static_cast<QTimerEvent *>(e);//timerEvent(env);return true;}else if(e->type()==QEvent::KeyPress){//类型转换QKeyEvent *env = static_cast<QKeyEvent *>(e);if(env->key()==Qt::Key_B){return QWidget::event(e);}return true;}else{return QWidget::event(e);}
}//事件过滤器
bool myWidget::eventFilter(QObject *obj,QEvent *e)
{if(obj==ui->label_2){//类型转换QMouseEvent *env=static_cast<QMouseEvent *>(e);//判断事件if(e->type()==QEvent::MouseMove){ui->label_2->setText(QString("mouse move:(%1,%2)").arg(env->x()).arg(env->y()));return true;}else{return QWidget::eventFilter(obj,e);}}else{return QWidget::eventFilter(obj,e);}
}

3. 标签头文件 mylabel.h

#ifndef MYLABEL_H
#define MYLABEL_H#include <QLabel>class mylabel : public QLabel
{Q_OBJECT
public:explicit mylabel(QWidget *parent = nullptr);protected://鼠标点击事件void mousePressEvent(QMouseEvent *ev);//鼠标释放事件void mouseReleaseEvent(QMouseEvent *ev);//鼠标移动事件void mouseMoveEvent(QMouseEvent *ev);//进入窗口区域void enterEvent(QEvent *);//离开窗口区域void leaveEvent(QEvent *);signals:public slots:
};#endif // MYLABEL_H

4. 标签源文件 mylabel.cpp

#include "mylabel.h"
#include <QMouseEvent>mylabel::mylabel(QWidget *parent) : QLabel(parent)
{}//鼠标点击事件
void mylabel::mousePressEvent(QMouseEvent *ev)
{int i=ev->x();int j=ev->y();//sprinf 字符串格式化命令/** QString str = QString("abc %1 ^_^ %2").arg(123).arg("mike");* str = abc 123 ^_^ mike*/QString text = QString("<center><h1>mouse press:(%1,%2)</h1></center>").arg(i).arg(j);// center 居中,h1 一级标题this->setText(text);
}//鼠标释放事件
void mylabel::mouseReleaseEvent(QMouseEvent *ev)
{QString text = QString("<center><h1>mouse release:(%1,%2)</h1></center>").arg(ev->x()).arg(ev->y());this->setText(text);
}//鼠标移动事件
void mylabel::mouseMoveEvent(QMouseEvent *ev)
{QString text = QString("<center><h1>mouse move:(%1,%2)</h1></center>").arg(ev->x()).arg(ev->y());//this->setText(text);
}//进入窗口区域
void mylabel::enterEvent(QEvent *e)
{QString text = QString("<center><h1>mouse enter</h1></center>");this->setText(text);
}//离开窗口区域
void mylabel::leaveEvent(QEvent *e)
{QString text = QString("<center><h1>mouse leave</h1></center>");this->setText(text);
}

5. 按钮头文件 mybutton.h

#ifndef MYBUTTON_H
#define MYBUTTON_H#include <QPushButton>class mybutton : public QPushButton
{Q_OBJECT
public:explicit mybutton(QWidget *parent = nullptr);protected:void mousePressEvent(QMouseEvent *e);signals:public slots:
};#endif // MYBUTTON_H

6. 按钮源文件 mybutton.cpp

#include "mybutton.h"
#include <QMouseEvent>
#include <QDebug>mybutton::mybutton(QWidget *parent) : QPushButton(parent)
{}void mybutton::mousePressEvent(QMouseEvent *e)
{if(e->button() == Qt::LeftButton){//如果是左键按下qDebug()<<"按下的是左键";//事件接收后,就会往下传递e->ignore();//忽略,事件继续往下传递,事件传递给了父组件,不是给父类(基类)}else{//不做处理QPushButton::mousePressEvent(e);//事件的忽略,事件继续往下传递}
}

http://www.ppmy.cn/news/4078.html

相关文章

2023养老展,北京老博会,老年用品与适老家具展,老龄产业展

CBIAIE北京老博会的价值|引导社会关注老年人生活&#xff0c;助力老年产业发展&#xff0c;提升企业市场竞争力与美誉度&#xff1b; CBIAIE北京老博会&#xff1a;全称&#xff1a;中国&#xff08;北京&#xff09;国际老年产业博览会China (Beijing) International Aged in…

33-Vue之ECharts-仪表盘图

ECharts-仪表盘图前言仪表盘的特点仪表盘的基本实现仪表盘的常见效果前言 本篇来学习写仪表盘图 仪表盘的特点 可以更直观的表现出某个指标的进度或实际情况 仪表盘的基本实现 ECharts 最基本的代码结构准备数据, 设置给 series 下的 data在 series 下设置 type:gauge &l…

Halcon图像拼接

图像拼接在实际的应用场景很广&#xff0c;比如无人机航拍&#xff0c;遥感图像等等&#xff0c;图像拼接是进一步做图像理解基础步骤&#xff0c;拼接效果的好坏直接影响接下来的工作&#xff0c;所以一个好的图像拼接算法非常重要。 如按下图是将两张楼房图片拼接成一个图像。…

MySQL之聚合查询和联合查询

一、聚合查询&#xff08;行与行之间的计算&#xff09; 1.常见的聚合函数有&#xff1a; 函数 说明 count 查询到的数据的数量 sum 查询到的数据的总和&#xff08;针对数值&#xff0c;否则无意义&#xff09; avg 查询到的数据的平均值&#xff08;针对数值&#xf…

Python开发游戏?也太好用了吧

程序员宝藏库&#xff1a;https://gitee.com/sharetech_lee/CS-Books-Store 当然可以啦&#xff01; 现在日常能够用到和想到的场景&#xff0c;绝大多数都可以用Python实现。 效果怎么样暂且不提&#xff0c;但是得益于丰富的第三方工具包&#xff0c;的确让Python能够很容易…

const成员和static成员详解

const成员和static成员详解1.const成员函数2.static成员&#xff08;1&#xff09;静态成员变量&#xff08;2&#xff09;静态成员函数&#xff08;3&#xff09;静态成员使用场景1.const成员函数 将const修饰的“成员函数”称之为const成员函数&#xff0c;const修饰类成员函…

Linux高级 I/O

目录 一、五种I/O模型 1. 阻塞式I/O 2. 非阻塞式I/O 3. I/O复用&#xff08;多路转接&#xff09; 4. 信号驱动式I/O 5. 异步I/O 二、五种I/O模型的比较 三、I/O复用典型使用在下列网络应用场合 一、五种I/O模型 阻塞式I/O非阻塞式I/OI/O复用&#xff08;多路转接&a…

【华为机试真题详解】开心消消乐【2022 Q4 | 100分】

文章目录 前言题目描述输入描述输出描述示例 1题目解析参考代码前言 《华为机试真题详解 Python实现》专栏含牛客网华为专栏、华为面经试题、华为OD机试真题。 如果您在准备华为的面试,期间有想了解的可以私信我,我会尽可能帮您解答,也可以给您一些建议! 本文解法非最优解…