Qt之事件处理机制
思维导读
一、事件简介
QT程序是事件驱动的, 程序的每个动作都是由内部某个事件所触发。QT事件的发生和处理成为程序运行的主线,存在于程序整个生命周期。
常见的QT事件类型如下:
键盘事件: 按键按下和松开
鼠标事件: 鼠标移动,鼠标按键的按下和松开
拖放事件: 用鼠标进行拖放
滚轮事件: 鼠标滚轮滚动
绘屏事件: 重绘屏幕的某些部分
定时事件: 定时器到时
焦点事件: 键盘焦点移动
进入和离开事件: 鼠标移入widget之内,或是移出
移动事件: widget的位置改变
大小改变事件: widget的大小改变
显示和隐藏事件: widget显示和隐藏
窗口事件: 窗口是否为当前窗口
QT将系统产生的消息转化为QT事件,QT事件被封装为对象,所有的QT事件均继承抽象类QEvent,用于描述程序内部或外部发生的动作,任意的QObject对象都具备处理QT事件的能力。
二、QT事件产生
(1)操作系统事件
操作系统将获取的事件,比如鼠标按键,键盘按键等keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件, 放入系统的消息队列中,Qt事件循环的时候读取消息队列中的消息,转化为QEvent并被分发到相应的QWidget对象,相应的QWidget中的event(QEvent *)进行事件处理会对事件进行处理,event(QEvent *)会根据事件类型调用不同的事件处理函数,在事件处理函数中发送QT预定义的信号,最终调用信号关联的槽函数。
GUI应用程序的事件处理:
A、QT事件产生后会被立即发送到相应的QWidget对象
B、相应的QWidget中的event(QEvent *)进行事件处理
C、event(QEvent *)根据事件类型调用不同的事件处理函数
D、在事件处理函数中发送QT预定义的信号
E、调用信号关联的槽函数
(2)Qt应用程序自己产生
程序产生事件有两种方式, 一种是调用QApplication::postEvent(), 例如QWidget::update()函数,当需要重新绘制屏幕时,程序调用update()函数,new出来一个paintEvent,调用 QApplication::postEvent(),将其放入Qt的消息队列中,等待依次被处理;
另一种方式是调用sendEvent()函数,事件不会放入队列, 而是直接被派发和处理, QWidget::repaint()函数用的就是阻塞型的。
sendEvent()中事件对象的生命期由Qt程序管理,支持分配在栈上和堆上的事件对象;postEvent()中事件对象的生命期由Qt平台管理,只支持分配在堆上的事件对象,事件被处理后由Qt平台销毁。
三、Qt事件处理
(1)事件调度
事件有两种调度方式,同步和异步。
Qt的事件循环是异步的,当调用QApplication::exec()时,就进入了事件循环,先处理Qt事件队列中的事件, 直至为空,再处理系统消息队列中的消息, 直至为空, 处理系统消息的时候会产生新的Qt事件, 需要对其再次进行处理。
调用QApplication::sendEvent的时候, 消息会立即被处理,是同步的。实际上QApplication::sendEvent()是通过调用QApplication::notify(), 直接进入了事件的派发和处理环节。
(2)事件通知、派发
事件过滤器是Qt中一个独特的事件处理机制, 功能强大而且使用起来灵活方便。通过事件过滤器, 可以让一个对象侦听拦截另外一个对象的事件。事件过滤器实现如下: 在所有Qt对象的基类QObject中有一个类型为QObjectList的成员变量,名字为eventFilters,当某个QObject(A)给另一个QObject(B)安装了事件过滤器后, B会把A的指针保存在eventFilters中。在B处理事件前,会先去检查eventFilters列表, 如果非空, 就先调用列表中对象的eventFilter()函数。一个对象可以给多个对象安装过滤器,一个对象能同时被安装多个过滤器, 在事件到达之后, 事件过滤器以安装次序的反序被调用。事件过滤器函数( eventFilter() ) 返回值是bool型, 如果返回true, 则表示事件已经被处理完毕, Qt将直接返回, 进行下一事件的处理。如果返回false, 事件将接着被送往剩下的事件过滤器或是目标对象进行处理。
QT中,事件的派发是从 QApplication::notify()开始的, 因为QAppliction也是继承自QObject, 所以先检查QAppliation对象, 如果有事件过滤器安装在qApp上, 先调用事件过滤器,接下来QApplication::notify() 会过滤或合并一些事件(比如失效widget的鼠标事件会被过滤掉, 而同一区域重复的绘图事件会被合并),事件被送到reciver::event()处理。
在reciver::event()中, 先检查有无事件过滤器安装在reciever上。若有, 则调用之。然后根据QEvent的类型, 调用相应的特定事件处理函数。常见的事件都有特定事件处理函数, 比如:mousePressEvent(), focusOutEvent(), resizeEvent(), paintEvent(), resizeEvent()等等。在实际应用中, 经常需要重载特定事件处理函数处理事件。对于不常见的事件, 没有相对应的特定事件处理函数,如果要处理这些事件, 就需要使用别的办法, 比如重载event() 函数, 或是安装事件过滤器。
(3)事件的转发
对于某些类别的事件,如果在整个事件的派发过程结束后还没有被处理, 那么这个事件将会向上转发给它的父widget, 直到最顶层窗口。Qt中和事件相关的函数通过两种方式相互通信,一种是QApplication::notify(), QObject::eventFilter(), QObject::event()通过返回bool值来表示是否已处理;另一种是调用QEvent::ignore() 或 QEvent::accept() 对事件进行标识,只用于event()函数和特定事件处理函数之间的沟通,而且只有用在某些类别事件上是有意义的, 这些事件就是上面提到的那些会被转发的事件, 包括: 鼠标, 滚轮, 按键等事件。
(4)事件的处理和过滤
QT提供了五种不同级别的事件处理和过滤:
A、重写特定事件处理函数.
最常见的事件处理办法就是重写mousePressEvent(), keyPressEvent(), paintEvent() 等特定事件处理函数。
B、重写event()函数.
重写event()函数时, 需要调用父类的event()函数来处理不需要处理或是不清楚如何处理的事件。
return QWidget::event(event);
C、在Qt对象上安装事件过滤器
安装事件过滤器有两个步骤: (假设要用A来监视过滤B的事件)
首先调用B的installEventFilter( const QOject *obj ), 以A的指针作为参数,所有发往B的事件都将先由A的eventFilter()处理。然后, A要重写QObject::eventFilter()函数, 在eventFilter() 中对事件进行处理。
D、给QAppliction对象安装事件过滤器
如果给QApplication对象装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过当前eventFilter()。在QApplication::notify() 中, 是先调用qApp的过滤器, 再对事件进行分析, 以决定是否合并或丢弃。
E、继承QApplication类,并重载notify()函数
Qt是用QApplication::notify()函数来分发事件的,要在任何事件过滤器查看任何事件之前先得到这些事件,重写notify()函数是唯一的办法。通常来说事件过滤器更好用一些, 因为不需要去继承QApplication类,而且可以给QApplication对象安装任意个数的事件过滤器。
四、自定义事件和eventFilter示例
class DefineEvent : public QEvent { public: const static QEvent::Type DefineType = static_cast<QEvent::Type>(QEvent::User + 0xFF); explicit DefineEvent(QString data) : QEvent(DefineType) { m_data = data; } QString data() {return m_data;} private: QString m_data; };
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QLineEdit> #include "StringEvent.h" #include <QMouseEvent> #include <QDebug> #include <QApplication> class Widget : public QWidget { Q_OBJECT QLineEdit m_edit; public: Widget(QWidget *parent = 0): QWidget(parent), m_edit(this) { m_edit.installEventFilter(this); } bool event(QEvent* evt) { if( evt->type() == QMouseEvent::MouseButtonDblClick ) { qDebug() << "event: Before sentEvent"; StringEvent e("D.T.Software"); QApplication::sendEvent(&m_edit, &e); qDebug() << "event: After sentEvent"; } return QWidget::event(evt); } bool eventFilter(QObject* obj, QEvent* evt) { if( (obj == &m_edit) && (evt->type() == StringEvent::TYPE) ) { StringEvent* se = dynamic_cast<StringEvent*>(evt); qDebug() << "Receive: " << se->data(); m_edit.insert(se->data()); return true; } return QWidget::eventFilter(obj, evt); } ~Widget() { } }; #endif // WIDGET_H
补充:自定义事件类型可以使用registerEventType
QEvent::Type ImageLoadedEvent::evType(){if(s_evType == QEvent::None){s_evType = (QEvent::Type)registerEventType();}return s_evType;}
事件
1.QEvent -->类型 -> QKeyEvent
QEvent::KeyRelease
QEvent::MouseMove -> QMouseEvent
2.事件处理过程
三大类型事件:
1.Spontaneous events(系统产生) -->事件队列
2.Posted events (QT) ---> 事件队列
3.Sent events
先处理QT事件队列的事件,然后再处理系统消事件队列。在处理系统的事件队例,产生新的QT事件,
a.exec()//事件循环 -->发给目标
{
while( !postedEvents ) { processPostedEvents(); }
while( !qwsEvnts ){ qwsProcessEvents();?? }
while( !postedEvents ) { processPostedEvents(); }
}
读事件后 通bool QApplication::notify ( QObject * receiver, QEvent * event )
目标:带有一个处理函数
bool QWidget::event(QEvent * event)
{
switch (event->type())
{
case QEvent::KeyPress:{
keyPressEvent((QKeyEvent *)event);
if (!((QKeyEvent *)event)->isAccepted())
return false;
break;}
case QEvent::KeyRelease:{
keyReleaseEvent((QKeyEvent *)event);
if (!((QKeyEvent *)event)->isAccepted())
return false;
break;}
...
case
leaveEvent();
case:
}
return QWidget::event(QEvent * event);
}
void QWidget::keyPressEvent(QKeyEvent * event)
{
event->key()==Qt::Key_Delete
}
void QWidget::keyReleaseEvent(QKeyEvent * event)
{
}
3.过滤器(****重点*****)
1.目标注册一个过滤器installEventFilter()
2。修改过滤器的eventFilter()
改变一个事件的效果
1,重新实现事件函数。 比如: mousePressEvent(), keyPress-Event(), paintEvent() 。 这是最常规的事件处理方法。
2,重新实现QObject::event().
这一般用在Qt没有提供该事件的处理函数时。也就是,我们增加新的事件时。
3,安装事件过滤器
4,在 QApplication 上安装事件过滤器。
QApplication 上的事件过滤器将捕获应用程序的所有事件,而且第一个获得该事件。也就是说事件在发送给其它任何一个event filter之前发送给QApplication的event filter。
5,重新实现QApplication 的 notify()方法.
Qt使用 notify()来分发事件。要想在任何事件处理器捕获事件之前捕获事件,唯一的方法就是重新实现QApplication 的 notify()方法。
------------------------------------------------------------------------------------------
过滤器范例:
event.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include<QEvent> #include <QMainWindow> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); bool eventFilter(QObject *obj, QEvent *evt); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
event.cpp #include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); ui->pushButton->installEventFilter(this); } MainWindow::~MainWindow() { delete ui; } bool MainWindow::eventFilter(QObject *obj, QEvent *evt) { if(obj==ui->pushButton) { if (evt->type() == QEvent::Enter) ui->label->setText("welcome"); if(evt->type()==QEvent::Leave) ui->label->setText("byebye"); } return 0; }
ui.h /******************************************************************************** ** Form generated from reading UI file 'mainwindow.ui' ** ** Created by: Qt User Interface Compiler version 5.5.0 ** ** WARNING! All changes made in this file will be lost when recompiling UI file! ********************************************************************************/ #ifndef UI_MAINWINDOW_H #define UI_MAINWINDOW_H #include <QtCore/QVariant> #include <QtWidgets/QAction> #include <QtWidgets/QApplication> #include <QtWidgets/QButtonGroup> #include <QtWidgets/QHeaderView> #include <QtWidgets/QLabel> #include <QtWidgets/QMainWindow> #include <QtWidgets/QMenuBar> #include <QtWidgets/QPushButton> #include <QtWidgets/QStatusBar> #include <QtWidgets/QToolBar> #include <QtWidgets/QWidget> QT_BEGIN_NAMESPACE class Ui_MainWindow { public: QWidget *centralWidget; QPushButton *pushButton; QLabel *label; QMenuBar *menuBar; QToolBar *mainToolBar; QStatusBar *statusBar; void setupUi(QMainWindow *MainWindow) { if (MainWindow->objectName().isEmpty()) MainWindow->setObjectName(QStringLiteral("MainWindow")); MainWindow->resize(400, 300); centralWidget = new QWidget(MainWindow); centralWidget->setObjectName(QStringLiteral("centralWidget")); pushButton = new QPushButton(centralWidget); pushButton->setObjectName(QStringLiteral("pushButton")); pushButton->setGeometry(QRect(140, 30, 99, 81)); label = new QLabel(centralWidget); label->setObjectName(QStringLiteral("label")); label->setGeometry(QRect(140, 150, 101, 41)); MainWindow->setCentralWidget(centralWidget); menuBar = new QMenuBar(MainWindow); menuBar->setObjectName(QStringLiteral("menuBar")); menuBar->setGeometry(QRect(0, 0, 400, 25)); MainWindow->setMenuBar(menuBar); mainToolBar = new QToolBar(MainWindow); mainToolBar->setObjectName(QStringLiteral("mainToolBar")); MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar); statusBar = new QStatusBar(MainWindow); statusBar->setObjectName(QStringLiteral("statusBar")); MainWindow->setStatusBar(statusBar); retranslateUi(MainWindow); QMetaObject::connectSlotsByName(MainWindow); } // setupUi void retranslateUi(QMainWindow *MainWindow) { MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", 0)); pushButton->setText(QApplication::translate("MainWindow", "PushButton", 0)); label->setText(QApplication::translate("MainWindow", "TextLabel", 0)); } // retranslateUi }; namespace Ui { class MainWindow: public Ui_MainWindow {}; } // namespace Ui QT_END_NAMESPACE #endif // UI_MAINWINDOW_H
Qt中的事件处理机制
以ubuntu上的gedit文本编辑器为例,当我们修改了文件内容,在不加以保存的前提下关闭窗口,编辑器就会弹窗提示,并让用户做出选择:
要实现这样的功能?如何做?
一开始自然会想到Qt中的信号和槽机制:程序在合适的地方将该信号与某个槽函数连接,当用户点击关闭按钮的瞬间会产生信号,进而槽函数被调用,实现弹窗操作。但是,对于关闭操作,Qt并没有产生相应的信号,即信号与槽机制不可使用。那么要实现这个功能,就得从Qt的事件处理模型说起。
下图在http://blog.csdn.net/qq_29344757/article/details/77817321一文中出现过:
它反映了包括Qt在内的GUI应用程序的消息处理模型:
(1) 用户操作界面,被操作系统内核空间中的设备驱动程序感知
(2) 设备驱动程序向操作系统的用户空间的GUI应用程序发出系统消息
(3) GUI应用程序将系统消息转换为信号,进而触发槽函数
在GUI应用程序中,一个核心且关键的操作就是将系统消息转换为信号,涉及到Qt的事件处理机制:
(1) Qt平台将系统底层发来的消息转换为Qt事件,并将其Qt事件产生后立即被分发到QWidget对象
(2) QWidget对象中的event(QEvent* )函数对事件进行处理,即根据不同的事件,调用不同的事件处理函数
(3) 在事件处理函数中发送Qt中预定义的对应事件的Qt信号,进而调用到信号关联的槽函数
下面将通过两个阶段,对Qt中的事件处理机制的处理流程进行分析,上述的问题的解答也涵盖其中。
1. 事件处理函数
Qt事件是一个QEvent对象,用于描述程序内部(如定时器超时)和外部发生的动作,任何QObject对象都具备事件处理能力。
从上图也可以明显看出,Qt信号是由继承自QObject的QWidget对象中的event()函数调用的事件处理函数发来的。以按钮QPushButton为例,用户对按钮的点击操作,所对应的的事件处理函数是谁?
在Qt Creator开发环境中可以看出,QPushButton继承自QAbstractButton类,在QAbstractButton中有针对鼠标操作的三个事件处理函数:
void mousePressEvent(QMouseEvent *e); //按下鼠标按钮操作
void mouseReleaseEvent(QMouseEvent *e); //松开鼠标按钮操作
void mouseMoveEvent(QMouseEvent *e); //移动鼠标操作
- 1
- 2
- 3
显然,按钮按下操作产生信号是由上面的mousePressEvent()产生的。为验证上述的事件处理流程机制,我们自定义一个继承于QPushButton的MyQPushButton类:
(1) 定义MyQPushButton类
//MyQPushButton.h
#ifndef MYQPUSHBUTTON_H
#define MYQPUSHBUTTON_H
#include <QPushButton>
#include <QEvent>
class MyQPushButton : public QPushButton
{
public:
MyQPushButton(QWidget *parent=0);
};
#endif // MYQPUSHBUTTON_H
//MyQPushButton.cpp
#include "MyQPushButton.h"
#include <QDebug>
MyQPushButton::MyQPushButton(QWidget *parent) : QPushButton(parent)
{
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
(2) 在Widget对象中使用MyQPushButton类
//Widget.h
#include <QPushButton>
#include "MyQPushButton.h"
#include <QDebug>
class Widget : public QWidget
{
Q_OBJECT
MyQPushButton btn;
public:
Widget(QWidget *parent = 0);
~Widget();
public slots:
//定义按钮的槽函数,用于响应按钮按下信号
void onclick();
};
#endif // WIDGET_H
//Widget.cpp
#include "Widget.h"
Widget::Widget(QWidget *parent) : QWidget(parent), btn(this)
{
//初始化按钮的相关设置,并映射按钮按下和槽函数
btn.setText("1");
btn.move(20, 20);
connect(&btn, SIGNAL(clicked()), this, SLOT(onclick()));
}
Widget::~Widget()
{
}
//按钮按下的槽函数
void Widget::onclick()
{
qDebug() << "Widget::onclick()";
}
//main.cpp
#include "Widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
运行:
在MyQPushButton类中重写产生信号的mousePressEvent()函数:
//MyQPushButton.h
class MyQPushButton : public QPushButton
{
public:
MyQPushButton(QWidget *parent=0);
void mousePressEvent(QMouseEvent *e);
};
//MyQPushButton.cpp
void MyQPushButton::mousePressEvent(QMouseEvent *e)
{
qDebug() << "MyQPushButton::mousePressEvent(QMouseEvent *e)";
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
运行:
(1) 重写后的mousePressEvent()确实被调用,打印出了”MyQPushButton::mousePressEvent(QMouseEvent *e)”
(2) 重写后的mousePressEvent()函数没有发出信号,所以与信号绑定的槽没有被调用
运行效果验证了上面讲的系统消息到信号的转换我的观点是正确的。
(3)另外,注意到一点,没有重写mousePressEvent()函数前,点击按钮,按钮会发生颜色深浅变化,但是重写后,颜色不会发生改变,这说明:在事件处理函数中还需要对UI上的Widget做出相应变化操作。可在代码中加上setDown()函数,该函数会设置按钮被按下的颜色样式,还会触发按下信号,进而槽函数得到调用。
void MyQPushButton::mousePressEvent(QMouseEvent *e)
{
qDebug() << "MyQPushButton::mousePressEvent(QMouseEvent *e)";
e->accept();
setDown(true);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
回到一开始的问题,一个Widget窗口被鼠标点击关闭前需要弹窗,在Widget类的声明有函数:
virtual void closeEvent(QCloseEvent *);
- 1
索性去看了一下Qt源码qwidget.cpp对该虚函数的实现:
void QWidget::closeEvent(QCloseEvent *event)
{
event->accept();
}
- 1
- 2
- 3
- 4
该函数只是调用了QEvent类中的accept()函数,它是QEvent的关键成员函数之一。QEvent的关键成员函数有:
inline bool isAccepted() const { return m_accept; } //判断当前时间是否被处理
inline void accept() { m_accept = true; } //接收者已经处理完毕当前事件
inline void ignore() { m_accept = false; } //接收者忽略当前事件,事件有可能被传递给其父组件
- 1
- 2
- 3
显然QWidget::closeEvent()将事件标志位已经处理完毕,即不是忽略(导致窗口不关闭)也不是忽略(父组件会处理),所以自然就是响应关闭操作了。
所以要实现窗口关闭前弹窗提示,只需要重写closeEvent()函数即可。是不是这样,测试一下:
//Widget.h
class Widget : public QWidget
{
Q_OBJECT
MyQPushButton btn;
public:
Widget(QWidget *parent = 0);
~Widget();
//重写父类的关闭窗口函数
void closeEvent(QCloseEvent *e);
public slots:
void onclick();
};
//Widget.cpp
void Widget::closeEvent(QCloseEvent *e)
{
qDebug() << "Widget::closeEvent(QCloseEvent *e)";
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
运行:
在窗口被关闭前确定打印出了”Widget::closeEvent(QCloseEvent *e)”。那么弹窗效果在重写函数closeEvent()中实现即可:
void Widget::closeEvent(QCloseEvent *e)
{
qDebug() << "Widget::closeEvent(QCloseEvent *e)";
QMessageBox msg(this);
msg.setText("Do you want to close the window?");
msg.setIcon(QMessageBox::Warning);
msg.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
if (msg.exec() == QMessageBox::Ok)
{
QWidget::closeEvent(e);
//e->accept();
}
else
e->ignore();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
运行:
2. 事件对象QEvent到QWidget对象
用Qt中的每一个窗口和窗口内组件都是继承自QWidget,QWidget又继承自QObject类,使得每个Widget都具有事件处理能力。针对每一种系统消息,每个QObject都有默认的事件操作(忽略也是一种事件操作)。当我们不想使用Qt默认实现的针对某事件的操作时候就可以重写对应的事件处理函数。这个在前面已经实现了,即验证了下图中的红色圈部分。
下来验证的是蓝色圈部分的事件处理流程:
(1) 定义一个继承自QLineEdit的类MyQLineEdit:
//MyQLineEdit.h
#ifndef MYQLINEEDIT_H
#define MYQLINEEDIT_H
#include <QLineEdit>
#include <QtGui/QWidget>
#include <QDebug>
#include <QEvent>
class MyQLineEdit : public QLineEdit
{
Q_OBJECT
public:
MyQLineEdit(QWidget* parent = 0);
};
#endif // MYQLINEEDIT_H
//MyQLineEdit.cpp
#include "MyQLineEdit.h"
MyQLineEdit::MyQLineEdit(QWidget* parent) : QLineEdit(parent)
{
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
(2) 定义Widget并使用MyQlineEdit
//Widget.h
#define WIDGET_H
#include <QtGui/QWidget>
#include "MyQLineEdit.h"
#include <QLineEdit>
class Widget : public QWidget
{
Q_OBJECT
MyQLineEdit line_edit;
public:
Widget(QWidget *parent = 0);
~Widget();
};
#endif // WIDGET_H
//Widget.cpp
#include "Widget.h"
Widget::Widget(QWidget *parent) : QWidget(parent), line_edit(this)
{
}
Widget::~Widget()
{}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
(3)main函数
//main.cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.resize(240, 132);
w.show();
return a.exec();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
运行:
输入框是供用户键盘输入数据的,也就是会有对应的键盘事件。在QLineEdit的声明中可看到键盘事件处理函数:
void keyPressEvent(QKeyEvent *);
- 1
该函数肯定是通过QLineEdit的event()函数调用的,所以在MyQLineEdit重写这两个函数:
//MyQLineEdit.h
class MyQLineEdit : public QLineEdit
{
Q_OBJECT
public:
MyQLineEdit(QWidget* parent = 0);
bool event(QEvent *e);
void keyPressEvent(QKeyEvent* e);
};
//MyQLineEdit.cpp
bool MyQLineEdit::event(QEvent *e)
{
if (e->type() == QEvent::KeyPress)
{
qDebug() << "MyQLineEdit::event(QEvent *e)";
}
//交由QLineEdit类中的event()处理
return QLineEdit::event(e);
}
void MyQLineEdit::keyPressEvent(QKeyEvent* e)
{
qDebug() << "MyQLineEdit::keyPressEvent(QKeyEvent* e)";
//交由QLineEdit类中的keyPressEvent()处理
QLineEdit::keyPressEvent(e);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
运行结果:
每次键盘输入字符,都先打印”MyQLineEdit::event(QEvent e)”,然后打印”MyQLineEdit::keyPressEvent(QKeyEvent e)”,同样证实观点。
在GUI应用程序将QEvent事件发生给Widget对象过程中,有一个十分关键的角色存在,事件过滤器。顾名思义,事件过滤器可以将GUI程序发来QEvent事件拦截。
3. 事件过滤器
下来介绍事件过滤器。定义在父类容器类型组件中的Qt事件过滤器可以对容器内的其他组件收到的事件进行监控,任何QObject对象都可以被作为事件过滤器的过滤目标。
没有安装事件过滤器的父类容器组件对象:
安装事件过滤器的父类容器组件对象:
定义的事件过滤器对象需要重写eventFilter()函数,该函数是过滤操作的具体实现,组件再通过installEventFilter()函数安装事件过滤器。安装过滤器后,过滤器会在组件之前接收到事件,事件过滤器能够决定是否将事件转发到组件对象。
基于前面的例子,过滤器组件用于过滤输入的非数字字符:
//Widget.h
class Widget : public QWidget
{
Q_OBJECT
MyQLineEdit line_edit;
public:
Widget(QWidget *parent = 0);
~Widget();
//过滤器的用于实现过滤操作的函数
bool eventFilter(QObject *obj, QEvent *e);
};
//Widget.cpp
//构造函数中为MyLineEdit安装过滤器
Widget::Widget(QWidget *parent) : QWidget(parent), line_edit(this)
{
line_edit.installEventFilter(this);
}
//过滤器的具体过来实现。该函数返回true表示过滤器不将事件对象发回目标组件,反之则发回
bool Widget::eventFilter(QObject *obj, QEvent *e)
{
bool ret = true;
//确认监测的目标组件和监测的事件
if ((obj == &line_edit) && (e->type() == QEvent::KeyPress))
{
qDebug() << "Widget::eventFilter(QObject *obj, QEvent *e)";
//强制类型转换事件
QKeyEvent *key_env = dynamic_cast<QKeyEvent*>(e);
//数字字符可发回目标组件,非数字字符不发回
switch (key_env->key())
{
case Qt::Key_0:
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
case Qt::Key_6:
case Qt::Key_7:
case Qt::Key_8:
case Qt::Key_9:
ret = false;
break;
default:
break;
}
}
else //非监测目标或者非监测的事件,调用父类的默认过滤函数,即不过滤
ret = QWidget::eventFilter(obj, e);
return ret;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
运行:
4. 小结
(1) Qt中的事件(Event)和Qt中的信号(SIGNAL)是两种不同的东西,事件由Object对象(包括继承于Object对象的QWidth对象)处理,对应的组件由对应的对象处理,处理操作
中其中一个就是触发信号,另一个操作是改变界面对应的显示效果。
(2) Qt GUI程序有着严格的事件处理流程,本文通过两个示例验证这一观点。
(3) Qt事件被处理函数处理完毕够可能还会传递给父类对象,关键看处理函数在调用的是标志已经处理完的accept()还是不做处理的ignore()函数,后者会将事件传递给父类对象。
(4) 事件过滤器用于对目标组件接收的目标事件进行监控,它能决定是否将事件发回目标组件对象。被监测的组件对象需要通过installEventFilter()函数安装事件过滤器。
Qt事件的处理流程就分析到这里,再往下分析就是操作系统的从设备驱动程序发出的系统消息。这一部分放在后面的Linux设备驱动程序开发中输入子系统讲解。
上一篇: Spring入门之事件处理
下一篇: 大数据时代引发安全问题,应增强身份管理