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 函数。
- keyPressEvent()
- keyReleaseEvent()
- mouseDoubleClickEvent()
- mouseMoveEvent()
- mousePressEvent()
- 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);}
}
总结
注意,
事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。