消息循环在QEventLoop类中实现。通过QEventLoop::exec()可以进入一个消息循环的阻塞状态中,也就是不断地PeekMessage、TranslateMessage、DispatchMessage(和windows 消息机制差不多的)。
Application类中,除去启动参数、版本等相关东西后,关键就是维护了一个QEventLoop,Application的exec就是QEventLoop的exec。不过Application中的这个EventLoop,我们称作“主事件循环”Main EventLoop。所有的事件分发、事件处理都从这里开始。
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
//或者QGuiApplication, 或者 QApplication
...
...
return app.exec();
}
在windows平台上,其底层原理也是通过PeekMessage、TranslateMessage、DispatchMessage从消息队列进行捕捉、转换、分发。
QT将系统产生的消息转化为QT事件,QT事件被封装为对象,所有的QT事件均继承抽象类QEvent。
比如键盘、鼠标产生的keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件(他们被封装成QMouseEvent和QKeyEvent)。其他的如QPaintEvent、QCloseEvent。
每一个事件处理函数,都是带有参数的,这个参数是QEvent的子类,携带了各种事件的参数。比如按键事件 void keyPressEvent(QKeyEvent *event) 中的QKeyEvent, 就包括了按下的按键值key、 count等等。
Qt还提供了事件过滤机制,在事件分发之前先过滤一部分事件。
a.安装一个事件过滤器.
void QObject::installEventFilter(QObject *filterObj)
延伸:一个是移除对应的事件过滤器
void QObject::removeEventFilter(QObject *obj)
QObject* objA = new MyQObjectA;
QObject* objB = new MyQObjectB;
// 安装事件过滤器;
objA->installEventFilter(objB);
// 移除事件过滤器;
objA->removeEventFilter(objB);
使用 installEventFilter方法 给对象objA安装objB的事件过滤器,这样objB对象的eventFilter方法中就可以接收到objA对象的所有事件了,如果objA对象不想objB对象再监听自己的事件了就使用 removeEventFilter方法移除objB对象对事件的监听。
b.重写QObject类的eventFilter函数。
virtual bool eventFilter(QObject *watched, QEvent *event);
一般的操作办法:父窗口类通过重写eventFilter方法来监听子控件的相关事件进行处理
好处就是:不需要通过重写控件的方式获取某些事件。
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
ui->label->installEventFilter(this);
}
Widget::~Widget()
{
delete ui;
}
bool Widget::eventFilter(QObject *obj, QEvent *event)
{
if(obj == ui->label)
{
//鼠标进入的时候
if (event->type() == QEvent::Enter)
{
ui->label->setText("我是红色");
ui->label->setStyleSheet(redStyle);
return true;
}
else if(event->type() == QEvent::Leave) //鼠标离开
{
ui->label->setText("我是黑色");
ui->label->setStyleSheet(blackStyle);
return true;
}
return false;//别的事件会传给label对象
}
// standard event processing
return QWidget::eventFilter(obj, event);
}
通过QObject类event函数处理
virtual bool event(QEvent *event);
event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。如果传入的事件已被识别并且处理,返回 true,否则返回 false。如果返回值是 true,QApplication 会认为这个事件已经处理完毕,会继续处理事件队列中的下一事件;如果返回值是 false,QApplication 会尝试寻找这个事件的下一个处理函数。
//重写event函数
bool MyWidget::event(QEvent *e)
{
//如果该对象触发的事件类型是键盘按下 将e转成键盘事件并判断是否是tab键 是则处理 否则重新分配给event的默认处理方式
if (e->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast(e);//强制类型转换
if (keyEvent->key() == Qt::Key_Tab) {
qDebug() << "You press tab.";
return true;
}
}
//重新分配给event的默认处理方式 //必要的,重新处理其他事件
return QWidget::event(e);
//若返回的不是再调用QWidget的event函数 而是直接返回false 则表示只能处理tab键 其它键都不能接收了
//return false;
}
注意:其实eventFilter和event函数已经在窗口函数中执行的,相当于在窗口回调函数中进行二次处理(事件过滤或拦截)。
在QWidget子类中,通过重写keyPressEvent、keyReleaseEvent等等事件处理函数,做一些自定义的事件处理。下面为常见的事件处理函数:
virtual void mousePressEvent(QMouseEvent *event);
virtual void mouseReleaseEvent(QMouseEvent *event);
virtual void mouseDoubleClickEvent(QMouseEvent *event);
virtual void mouseMoveEvent(QMouseEvent *event);
#if QT_CONFIG(wheelevent)
virtual void wheelEvent(QWheelEvent *event);
#endif
virtual void keyPressEvent(QKeyEvent *event);
virtual void keyReleaseEvent(QKeyEvent *event);
virtual void focusInEvent(QFocusEvent *event);
virtual void focusOutEvent(QFocusEvent *event);
virtual void enterEvent(QEvent *event);
virtual void leaveEvent(QEvent *event);
virtual void paintEvent(QPaintEvent *event);
virtual void moveEvent(QMoveEvent *event);
virtual void resizeEvent(QResizeEvent *event);
virtual void closeEvent(QCloseEvent *event);
#ifndef QT_NO_CONTEXTMENU
virtual void contextMenuEvent(QContextMenuEvent *event);
#endif
#if QT_CONFIG(tabletevent)
virtual void tabletEvent(QTabletEvent *event);
#endif
#ifndef QT_NO_ACTION
virtual void actionEvent(QActionEvent *event);
#endif
#if QT_CONFIG(draganddrop)
virtual void dragEnterEvent(QDragEnterEvent *event);
virtual void dragMoveEvent(QDragMoveEvent *event);
virtual void dragLeaveEvent(QDragLeaveEvent *event);
virtual void dropEvent(QDropEvent *event);
#endif
virtual void showEvent(QShowEvent *event);
virtual void hideEvent(QHideEvent *event);
virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result);
// Misc. protected functions
virtual void changeEvent(QEvent *);
延伸补充:
操作系统将获取的事件,比如鼠标按键,键盘按键等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、调用信号关联的槽函数
程序产生事件有两种方式, 一种是调用QApplication::postEvent(), 例如QWidget::update()函数,当需要重新绘制屏幕时,程序调用update()函数,new出来一个paintEvent,调用 QApplication::postEvent(),将其放入Qt的消息队列中,等待依次被处理;
另一种方式是调用sendEvent()函数,事件不会放入队列, 而是直接被派发和处理, QWidget::repaint()函数用的就是阻塞型的。
sendEvent()中事件对象的生命期由Qt程序管理,支持分配在栈上和堆上的事件对象;postEvent()中事件对象的生命期由Qt平台管理,只支持分配在堆上的事件对象,事件被处理后由Qt平台销毁。
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对象安装任意个数的事件过滤器。