当前位置: 首页 > news >正文

QT三 自定义控件,自定义控件的事件处理自定义事件过滤,原始事件过滤

一 自定义控件

现在的需求是这样:

假设我们要在QWidget 上做定制,这个定制包括了关于 一些事件处理,意味着要重写QWidget的一些代码,这是不实际的,因此我们需要自己写一个MyWidget继承QWidget,然后再MyWidget.cpp中重写事件处理的函数。

而且我们希望在 mainwidget.ui上就有自己写的MyWidget,而不是默认的QWidget,这样我们就能在mainwidget.ui上预留我们想要的位置。这时候怎么办呢?

1. 写一个自己的MyWidget,将自己写的MyWidget可以在mainwindow.ui上显示

如下是我们在ui上的弄了一个QWidget,我们的目标是将这个QWidget变成MyWidget,然后再MyWidget.cpp中重写我们的方法

新建MyWidget.cpp类

我们看到 就多出来了mywidget.h 和 mywidget.cpp文件

然后再回到 ui文件,提升ui上的widget为mywidget

取消提升

2. 给自己写的MyWidget上添加想要的UI

我们现在是想给这个Mywidget 中加入 两个控件,一个是spin box,一个是horizontal slider

当spin box 的值变化的时候,会影响 horizontal slider的变化。

这里为了区分 主要的widget,和我们写的MyWidget,我们将主要的widget改名为mainwidget。主要是为了方便理解,没有额外的意义。

一种方法当前是从ui 上拖过去

我们从ui上拖过去控件,主要是在代码中为了给从这些控件得到数据---例如得到这个控件是否被点击,这个控件的值,这个控件中填写的数据等。那么我们如何得到这个控件呢?

得到从ui上拖过去的spin box 和horizontal slider

每一个控件都有一个唯一的objectName,(从ui上的可以看到)。因此要获得某一个控件,在代码上一般通过ui->objectName可以获得(注意是一般情况下)。

那么这个objectName是个啥呢?

如下图:我们拖进去了两个 SpinBox,随便选中一个,就可以看到 这个SpinBox有一个objectName,我们可以改动这个objectName的值,如下,但是objectName是唯一的值,因此不等重复,我们可以试图将两个SpinBox改名为一样,发现不行,QT会帮我们自动改回来。

通过ui->objectName 获得控件代码

 _spinBox = ui->spinBox;
_horizontalSlider =  ui->horizontalSlider;

看一下源码(ui_mainwidget.h),可以看到实际上QT 在将xxx.ui  构建成 ui_xxx.h 的时候,是先通过 objectName,new 出来组件,new出来组件的名字和objectName名字一致。因此给我们的感觉是ui->objectName,就能得到组件。实际上new出来的组件的名字并不一定和objectName名字一致,因此写代码的时候要注意看源码。

扩展,灯下黑--这里还有一个问题:如何得到最外层的widget呢?也就是主widget。

实际上我们ui就是widget的 成员变量,因此this 就最大的widget。

我们知道这个最大的widget,也就是this有啥用呢?

可以通过this 获得所有我们想要找的组件,比如 findchildren();如果想使用,可以参考 qt api 说明

扩展 在mainwidget构造方法中,让spin box 和 horizontal slider 的值使用信号和槽关联起来

Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//注意我们写的位置,并不是在mywidget.cpp中写的,//是在main 加载的最大的widget上写的,这是因为只有最大的widget才有ui变量,我们要通过ui变量找到对应的控件_spinBox = ui->spinBox;_horizontalSlider =  ui->horizontalSlider;//我们想将 _spinBox 的valuechanged 信号发出去后,让 _horizontalSlider调用setvalue方法//A段代码 ------ A,B,C代码段实现的功能是一样的,如果不理解,先在自己的博客search 一下 函数指针  ,然后再 seacher一下 static_cast,将这两个文章弄明白就理解了void (QSpinBox::* testSignal)(int) = &QSpinBox::valueChanged;connect(_spinBox,testSignal,_horizontalSlider,&QSlider::setValue);//B段代码 ------ A,B,C代码段实现的功能是一样的typedef void (QSpinBox ::*PFUNCTYPE5 )(int) ;PFUNCTYPE5 testSignal4 = &QSpinBox::valueChanged;connect(_spinBox,testSignal4,_horizontalSlider,&QSlider::setValue);//C段代码 ------ A,B,C代码段实现的功能是一样的connect(_spinBox,static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),_horizontalSlider,&QSlider::setValue);connect(_horizontalSlider, &QSlider::valueChanged,_spinBox, &QSpinBox::setValue);
}

一种方法是在Mywidget.cpp中使用代码实现

我们先通过UI将 自己给Mywidget中的画上去的spinbox删除,将horizontal slider 删除。

以免得影响我们测试

那么当前UI上的Mywidget就啥也没有,我们在MyWidget的构造方法中就可以构建新的UI,并加上去。

代码部分

注意这个代码实现的地方。

另外注意:这个代码偷懒,并没有将QHBoxLayout写到.h中,这是不好的。

另外,还需要注意的是,如果我们不加 QHBoxLayout,那么就要手动的标识 spinbox 和 slider的位置,很容易发生问题,因此在自己写,或者使用ui的时候,最好都用 这样layout

MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{QHBoxLayout *layout = new QHBoxLayout(this);//注意我们写的位置,并不是在mywidget.cpp中写的,//是在main 加载的最大的widget上写的,这是因为只有最大的widget才有ui变量,我们要通过ui变量找到对应的控件_spinBox = new QSpinBox(this);layout->addWidget(_spinBox);_horizontalSlider =  new QSlider(Qt::Horizontal,this);layout->addWidget(_horizontalSlider);//我们想将 _spinBox 的valuechanged 信号发出去后,让 _horizontalSlider调用setvalue方法//A段代码 ------ A,B,C代码段实现的功能是一样的,如果不理解,先在自己的博客search 一下 函数指针  ,然后再 seacher一下 static_cast,将这两个文章弄明白就理解了void (QSpinBox::* testSignal)(int) = &QSpinBox::valueChanged;connect(_spinBox,testSignal,_horizontalSlider,&QSlider::setValue);//B段代码 ------ A,B,C代码段实现的功能是一样的typedef void (QSpinBox ::*PFUNCTYPE5 )(int) ;PFUNCTYPE5 testSignal4 = &QSpinBox::valueChanged;connect(_spinBox,testSignal4,_horizontalSlider,&QSlider::setValue);//C段代码 ------ A,B,C代码段实现的功能是一样的connect(_spinBox,static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),_horizontalSlider,&QSlider::setValue);connect(_horizontalSlider, &QSlider::valueChanged,_spinBox, &QSpinBox::setValue);
}

二 自定义控件的事件的处理,自定义控件的事件过滤器

当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。

一些事件在对用户操作做出响应时发出,如键盘事件等;

另一些事件则是由系统自动发出,如计时器事件。

处理流程一:当事件发生时,Qt 将创建一个事件对象。Qt 中所有事件类都继承于QEvent

处理流程二:Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler)

由于event()函数是负责分发的,那么我们就可以定制那么事件分发,那么事件不分发了。

处理流程三:重写我们想要的回调函数-也就是 event handler

Object 将这些event handler都定义成 virtual 的

使用方法:我们在自定义的组件中,重写我们想要额外处理的event handler 函数。

  1. keyPressEvent()
  2. keyReleaseEvent()
  3. mouseDoubleClickEvent()
  4. mouseMoveEvent()
  5. mousePressEvent()
  6. mouseReleaseEvent() 等。

因此我们首先能想到的就是在最后的处理流程中,对于我们感兴趣的event handler函数,进行自定义处理。

我们自定义一个MyLable,继承于QLable,重写几个eventhandler函数,看一下,这里还自己写一个MyWidget,主要是看一下,写两个自定义的Ui的嵌套case下,是怎么样子的。

MyLable.h

#ifndef MYLABLE_H
#define MYLABLE_H#include <QWidget>
#include <QLabel>
#include <QEvent>
#include <QMouseEvent>
#include <QDebug>>class MyLable : public QLabel
{Q_OBJECT
public:explicit MyLable(QWidget *parent = nullptr);signals:protected:void enterEvent(QEvent *event) override;void leaveEvent(QEvent *event) override ;void mouseMoveEvent(QMouseEvent *event)override;};#endif // MYLABLE_H

MyLable.cpp

#include "mylable.h"MyLable::MyLable(QWidget *parent) : QLabel(parent){//这里设置鼠标不用点击,就能看到mousemoveevnetthis->setMouseTracking(true);
}void MyLable::enterEvent(QEvent *event) {qDebug()<<"enterevent call";this->setText("进入了lable");}void MyLable::leaveEvent(QEvent *event){qDebug()<<"leaveEvent call";this->setText("离开了lable");
}void MyLable::mouseMoveEvent(QMouseEvent *event){qDebug()<<"mouseMoveEvent call";qDebug()<<event->x() <<"  " << event->y()<< endl;
}

三 原始控件的 特殊事件处理流程-事件过滤器

前面我们学习了自定义控件,以及自定义控件的 event()和eventhandler处理。

但是要求都是要自定义控件,如果我们有很多的控件,这些自定义控件都有或多或少的event事件要处理,那岂不是工作量很大,因此QT 提供了让我们可以过滤处理的方法,即事件过滤器。

事件过滤器的位置:

使用方法:

1. 注册想要过滤的ui控件,

         void installEventFilter(QObject *filterObj)

                _pushbutton.installEventFilter(this);

        参数 filterObj的含义是:注册完成的ui的过滤事件在 filterIObj的eventFilter()方法中,当前是在 mainwidget中写的,也就是 会在mainwidget 的 eventFilter 函数中过滤 _pushbutton

2. 重写 eventFilter方法,watched就是要过滤的ui,event就是要过滤的event
virtual bool eventFilter(QObject *watched, QEvent *event)

不想让它继续转发,就返回 true,

如果不想处理否则返回 false。实际上 return 给自己的父类去处理

return QWidget::eventFilter(watched,event);

代码实现:

MainWidget::MainWidget(QWidget *parent): QWidget(parent), ui(new Ui::MainWidget)
{ui->setupUi(this);_pushButton = ui->pushButton;_radioButton = ui->radioButton;//正常点下,放松,改动了button 上面的文字connect(_pushButton,&QPushButton::pressed,[=](){_pushButton->setText("该button被按下");});connect(_pushButton,&QPushButton::released,[=](){_pushButton->setText("该button被放松");});connect(_pushButton,&QPushButton::clicked,[=](){_pushButton->setText("该button被点击");});//注册pushbutton有过滤事件,原本 button 的pressed 后,会有显示,现在我们要过滤pressed事件,因此代码后,再点击就没有 setText("该button被按下"); 发生了_pushButton->installEventFilter(this);}//重写eventFulter事件
bool MainWidget::eventFilter(QObject *watched, QEvent *event){if(watched == _pushButton){if(event->type()==QEvent::MouseButtonPress){qDebug()<<"ccc";return true;}else{return QWidget::eventFilter(watched,event);}}else{//如果是其他的 object,则要让父类处理,不能直接return falsereturn QWidget::eventFilter(watched,event);}
}

总结

注意,

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


http://www.mrgr.cn/news/96115.html

相关文章:

  • 自动化测试selenium(Java版)
  • Java基础关键_029_线程(二)
  • Vue3 项目通过 docxtemplater 插件动态渲染 .docx 文档(带图片)预览,并导出
  • linux - centos7 部署 redis6.0.5
  • Echarts使用
  • 字节跳动前端开发实习生面试总结
  • 蓝桥杯高频考点——二分(含C++源码)
  • QT自运行程序
  • 海康HTTP监听报警事件数据
  • Docker+Ollama+Xinference+RAGFlow+Dify+Open webui部署及踩坑问题
  • 2.4 Gannt图【甘特图】
  • EMQX Dashboard
  • 运行前端项目报错解决方法
  • 计算机组成原理的学习day01
  • 【悲观锁和乐观锁有什么区别】以及在Spring Boot、MybatisPlus、PostgreSql中使用
  • 蓝桥杯 第十二天 819 递增序列
  • el-table单元格编辑,动态增删行,回车/上下左右箭头切换单元格
  • Dubbo 全面解析:从 RPC 核心到服务治理实践
  • SpringBoot整合Log4j2进行日志记录异步写入日志文件
  • 【windows搭建lvgl模拟环境(一)之VSCode】