文章目录
- 原理
- QT信号槽机制 VS 事件
- QT事件总述
- 事件(event)的accept()、ignore()
- event(): 事件分发
- EventFilter事件过滤器: 实现步骤
- Example
- QMouseEvent
- 事件触发源码分析
原理
观察者模式:首先使用S的成员函数installEventFilter函数把G1,G2,G3设置为S的观察者,所有本应传递给S的事件E,则先传递给观察者G1,G2,G3,然后观察者调用其成员函数eventFilter对传递进来的事件进行处理,若eventFilter返回true表示事件处理完毕,返回false则返回给被观察者S进行处理。
QT信号槽机制 VS 事件
- 信号由具体的对象发出,然后会马上交给由connect()函数连接的槽进行处理;
- 而对于事件,Qt 使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部。前一个事件完成后,取出后面的事件进行处理。但是,必要的时候,Qt 的事件也可以不进入事件队列,而是直接处理。
QT事件总述
-
Qt 程序在main()函数创建一个QCoreApplication对象,然后调用它的exec()函数开始 Qt 的事件循环。
-
在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件。
-
当事件发生时,Qt 将创建一个事件对象。
3.1 Qt 中所有事件类都继承于QEvent。在事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler)。 -
在所有组件的父类QWidget中,定义了很多事件处理的回调函数,如keyPressEvent()、keyReleaseEvent()、mouseDoubleClickEvent()、mouseMoveEvent()、mousePressEvent()、mouseReleaseEvent()等。这些函数都是 protected virtual 的,也就是说,我们可以在子类中重写这些函数。
事件(event)的accept()、ignore()
1. 普通pushButton函数响应方式:clicked()信号发射后,onButtonClicked()槽函数接收信号并处理。
// custombutton.h
class CustomButton : public QPushButton
{
Q_OBJECT
public:
CustomButton(QWidget *parent = 0);
private slot:
void onButtonClicked();
};
// custombutton.cpp
CustomButton::CustomButton(QWidget *parent) :
QPushButton(parent)
{
connect(this, &CustomButton::clicked,
this, &CustomButton::onButtonClicked);
}
void CustomButton::onButtonCliecked()
{
qDebug() << "You clicked this!";
}
// main.cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CustomButton btn;
btn.setText("This is a Button!");
btn.show();
return a.exec();
}
2. 若向CustomButton类添加一个事件函数(重写CustomButton中继承自QPushButton的mousePressEvent()函数)
// custombutton.cpp
void CustomButton::mousePressEvent(QMouseEvent *event) // protected
{
if (event->button() == Qt::LeftButton) {
qDebug() << "left";
} else {
QPushButton::mousePressEvent(event);
}
}
=》鼠标按下左键,则只打印出来“left”字符串。该方法把父类的实现mousePressEvent()覆盖掉了(子类事件均继承自父类),clicked()信号永远不会发生,连接到这个信号的槽函数onButtonClicked()不会被执行。
=》当重写事件回调函数时,时刻注意是否需要通过调用父类的同名函数来确保原有实现仍能进行。
ps: Qt 中的事件都是 protected 的,因此,重写的函数必定存在着其父类中的响应函数。
3. 通过调用父类的同名函数,我们可以把 Qt 的事件传递看成链状:如果子类没有处理这个事件,就会继续向其父类传递。
accept()
:这个类的事件处理函数想要处理这个事件,这个事件就不会被继续传播给其父组件。ignore()
:这个类的事件处理函数不想要处理这个事件,Qt 会从其父组件中寻找另外的接受者,继续传播。isAccepted()
:查询这个事件是不是已经被接收。
4. 事件对象默认是 accept 的,而作为所有组件的父类QWidget的默认实现则是调用ignore()。
- 因为我们无法确认父类中的这个处理函数有没有额外的操作。如果我们在子类中直接忽略事件,Qt 会去寻找其他的接收者,该子类的父类的操作会被忽略(因为没有调用父类的同名函数),这可能会有潜在的危险。
=》很少会直接使用accept()和ignore()函数,除closeEvent()中。
=》如果你自己实现事件处理函数,不调用QWidget的默认实现,你就等于是接受了事件;如果你要忽略事件,只需调用QWidget的默认实现。
4.1 直接调用accept() \ ignore()风险
void QWidget::mousePressEvent(QMouseEvent *event)
{
event->ignore();
if ((windowType() == Qt::Popup)) {
.....
}
}
如果子类都没有重写这个函数,Qt 会默认忽略这个事件,继续寻找下一个事件接收者。如果我们在子类的mousePressEvent()函数中直接调用了accept()或者ignore(),而没有调用父类的同名函数,QWidget::mousePressEvent()函数中关于Popup判断的那段代码就不会被执行,因此可能会出现默认其妙的怪异现象。
4.2 closeEvent()中调用ignore()阻止窗口关闭
//!!! Qt5
...
textEdit = new QTextEdit(this);
setCentralWidget(textEdit);
connect(textEdit, &QTextEdit::textChanged, [=]() {
this->setWindowModified(true);
});
setWindowTitle("TextPad [*]");
...
void MainWindow::closeEvent(QCloseEvent *event)
{
if (isWindowModified()) {
bool exit = QMessageBox::question(this, tr("Quit"), tr("Are you sure to quit this application?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes;
if (exit) {
event->accept();
} else {
event->ignore();
}
} else {
event->accept();
}
}
a. 调用accept()意味着 Qt 会停止事件的传播,窗口关闭;
b. 调用ignore()则意味着事件继续传播,即阻止窗口关闭
5. 事件的传播是在组件层次上面的,而不是依靠类继承机制。
class CustomButton : public QPushButton
{
Q_OBJECT
public:
CustomButton(QWidget *parent) : QPushButton(parent)
{
}
protected:
void mousePressEvent(QMouseEvent *event)
{
qDebug() << "CustomButton";
}
};
class CustomButtonEx : public CustomButton
{
Q_OBJECT
public:
CustomButtonEx(QWidget *parent) : CustomButton(parent)
{
}
protected:
void mousePressEvent(QMouseEvent *event)
{
qDebug() << "CustomButtonEx";
}
};
class CustomWidget : public QWidget
{
Q_OBJECT
public:
CustomWidget(QWidget *parent) : QWidget(parent)
{
}
protected:
void mousePressEvent(QMouseEvent *event)
{
qDebug() << "CustomWidget";
}
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0) : QMainWindow(parent)
{
CustomWidget *widget = new CustomWidget(this);
CustomButton *cbex = new CustomButton(widget);
cbex->setText(tr("CustomButton"));
CustomButtonEx *cb = new CustomButtonEx(widget);
cb->setText(tr("CustomButtonEx"));
QVBoxLayout *widgetLayout = new QVBoxLayout(widget);
widgetLayout->addWidget(cbex);
widgetLayout->addWidget(cb);
this->setCentralWidget(widget);
}
protected:
void mousePressEvent(QMouseEvent *event)
{
qDebug() << "MainWindow";
}
};
a. CustomButtonEx的mousePressEvent()
默认是accept的,是否调用event->accept()
没有区别,运行结果如下:
CustomButtonEx
b. CustomButtonEx的mousePressEvent()
中增加 event->ignore()
,运行结果如下:
CustomButtonEx
CustomWidget
=》CustomButtonEx的事件传播给了父组件CustomWidget,而不是它的父类CustomButton。
event(): 事件分发
事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。
event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler: timerEvent() 、mouseMoveEvent()、…)。
bool CustomWidget::event(QEvent *e)
{
if (e->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
if (keyEvent->key() == Qt::Key_Tab) {
qDebug() << "You press tab.";
return true;
}
}
return QWidget::event(e);
}
- CustomWidget是一个普通的QWidget子类。我们重写了它的event()函数,这个函数有一个QEvent对象作为参数,也就是需要转发的事件对象。
- 函数返回值是 bool 类型。如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是 true,并且,该事件对象设置了accept(),那么 Qt 会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象,而是会继续处理事件队列中的下一事件。
- 注意,在event()函数中,调用事件对象的accept()和ignore()函数是没有作用的,不会影响到事件的传播。
- 使用
QEvent::type()
函数可以检查事件的实际类型,其返回值是QEvent::Type类型的枚举。我们处理过自己感兴趣的事件之后,可以直接返回 true,表示我们已经对此事件进行了处理;对于其它我们不关心的事件,则需要调用父类的event()函数继续转发。
- Qt 也是使用QEvent::type()判断事件类型,然后调用了特定的事件处理器。
- event()函数中实际是通过事件处理器来响应一个具体的事件。这相当于event()函数将具体事件的处理“委托”给具体的事件处理器。而这些事件处理器是 protected virtual 的,因此,我们重写了某一个事件处理器,即可让 Qt 调用我们自己实现的版本。
整理自:https://www.devbean.net/2012/09/qt-study-road-2-events-accept-reject/
EventFilter事件过滤器: 实现步骤
installEventFilter():filterObj对象安装(或注册)为事件过滤器
void QObject::installEventFilter (QObject* filterObj);
eventFilter()拦截到的事件处理
bool QObject::eventFilter(QObject* obj, QEvent* e);
- 过滤器对象的eventFilter()函数接受或拒绝拦截到的事件。
- 若该函数返回false,则表示事件需要作进一步处理,此时事件会被发送到目标对象本身进行处理(注意:这里并未向父对象进行传递)。
- 若evetnFilter()返回true,则表示停止处理该事件,此时目标对象和后面安装的事件过滤器就无法获得该事件。
#include<QMouseEvent>
class A:public QObject{
public: //该类的对象用作过滤器对象,使用事件过滤器需继承QObject
bool eventFilter(QObject *w, QEvent *e){
if(e->type()==QEvent::MouseButtonPress)
{
cout<<w->objectName().toStdString(); //验证w为事件本应到达的目标对象
cout<<"=Ak"<<endl;
return 1; //返回1表示该事件不再进一步处理
}
return 0;
}
}; /*返回0表示其余事件交还给目标对象处理,本例应返回0,否则添加了该过滤器的安钮会无法显示。*/
class B:public A{
public: //继承自类A
bool eventFilter(QObject *w, QEvent *e){
if(e->type()==QEvent::MouseButtonPress){
cout<<w->objectName().toStdString()<<"=Bk"<<endl;
return 0;
}
return 0;
}
};
class C:public QWidget{
public:
void mousePressEvent(QMouseEvent *e){
cout<<"Ck"<<endl;
}
};
class D:public QPushButton{
public:
void mousePressEvent(QMouseEvent *e){
cout<<"DK"<<endl;
}
};
int main(int argc, char *argv[]){
QApplication a(argc,argv);
//创建对象,注意:本例父对象应先创建,以避免生命期过早结束
A ma;
B mb;
C mc;
D *pd=new D;
D *pd1=new D;
pd->setText("AAA");
pd->move(22,22);
pd1->setText("BBB");
pd1->move(99,22);
//设置对象名
ma.setObjectName("ma");
mb.setObjectName("mb");
mc.setObjectName("mc");
pd->setObjectName("pd");
pd1->setObjectName("pd1");
//设置父对象
pd->setParent(&mb);
pd1->setParent(&mc);
mb.setParent(&ma); //①
//注册过滤器对象
pd->installEventFilter(&mb); //②
pd1->installEventFilter(&ma); //③
mc.resize(333,222); mc.show(); a.exec();
return 0;
}
removeEventFilter():删除事件过滤器
QObject::removeEventFilter(QObject *obj)
Example
ButtonHoverWatcher * watcher = new ButtonHoverWatcher(this);
ui->pushButton->installEventFilter(watcher);
#include <QObject>
#include <QPushButton>
#include <QEvent>
class ButtonHoverWatcher : public QObject
{
Q_OBJECT
public:
explicit ButtonHoverWatcher(QObject * parent = Q_NULLPTR);
virtual bool eventFilter(QObject * watched, QEvent * event) Q_DECL_OVERRIDE;
};
ButtonHoverWatcher::ButtonHoverWatcher(QObject * parent) :
QObject(parent)
{}
bool ButtonHoverWatcher::eventFilter(QObject * watched, QEvent * event)
{
QPushButton * button = qobject_cast<QPushButton*>(watched);
if (!button) {
return false;
}
if (event->type() == QEvent::Enter) {
// The push button is hovered by mouse
button->setIcon(QIcon(":/images/start_hov.png"));
return true;
}
if (event->type() == QEvent::Leave){
// The push button is not hovered by mouse
button->setIcon(QIcon(":/images/start.png"));
return true;
}
return false;
}
https://blog.csdn.net/hyongilfmmm/article/details/83015541
https://stackoverflow.com/questions/40318759/change-qpushbutton-icon-on-hover-and-pressed
QMouseEvent
mouseMoveEvent(QMouseEvent *event)
在MainWindow构造函数中对需要跟踪鼠标位置的组件,及其所属的各级父类和容器setMouseTracking(true),否则只有在按住左键或右键时才会捕捉鼠标位置。
事件触发源码分析
sendEvent-》notifyInternal2-》notify-》doNotify
doNotify-》notify_helper-》sendThroughApplicationEventFilters=》sendThroughObjectEventFilters-》event