一、事件处理过程
在Qt里,一个事件是继承自QEvent的对象。事件通过调用QObject::event()被发送到继承自 QObject 的对象。事件发送表明一个事件已经产生,用 QEvent表达这个事件,且QObject 需要做出回应。多数事件针对 QWidget和他的子类的,此外还有些不和图形相关的重要事件,比如,套接字激活,它是QSocketNotifier使用的事件。
一些事件来自窗口系统,如QMouseEvent,一些来自其他地方,如QTimerEvent,还有一些来自应用程序。Qt一视同仁,你可以象Qt自己的事件循环所作的方式一样地发送事件。
大多数事件类型有特定的类,常用的有QResizeEvent、QPaintEvent、QMouseEvent、QKeyEvent和QCloseEvent。有很多别的事件类。每个类派生自QEvent且添加事件特定的函数;例如,QResizeEvent。在QResizeEvent中,就被加入了QResizeEvent::size()和QResizeEvent::oldSize()。
有些类支持多种事件类型。QMouseEvent支持鼠标移动、按压、粘滞按压、拖拽、点击、右按压等。
因为程序需要以多变和复杂的方式作出返回,因此,Qt的事件分发机制就是灵活的。一个事件被发送的通常方法是通过调用一个虚拟函数。例如:QPaintEvent通过调用QWidget::paintEvent()被分发。
(1) 创建用户事件
创建一个自定义类型的事件,你需要定义一个事件号,其值必须大于QEvent::User。为了传递有关你的自定义事件的特性。可能自定义的事件需要从QCustomEvent类继承。
示例1:用户事件类的编写
下面列出了一个用户事件类TEST_Event的示例,编写用户事件类的方法是先定义一个事件号,再实现用户事件类,应用程序将把用户事件类与Qt的事件类一样进行处理。
用户事件类TEST_Event的头文件test_event.h列出如下:
#include <qevent.h>
#include <qstring.h>
#define REFRESHUI_EVENT QEvent::User+2
class TEST_Event: public QCustomEvent
{
public:
TEST_Event();
};
用户事件类TEST_Event的实现文件test_event.cpp列出如下:
#include <test_event.h>
TEST_Event::TEST_Event():QCustomEvent(REFRESHUI_EVENT )
{
}
(2)事件发送
许多应用程序都要创建和发送他们自己的事件。这需要创建一个相应的事件类的对象,然后调用QApplication::sendEvent()或者QApplication::postEvent()发送事件。对用户事件来说,还需要用户有事件对应的操作函数。对于Qt事件来说,Qt中已实现了事件对应的操作函数。
示例2:使用用户事件类
在test_engine.cpp文件中,函数refreshUI发送事件到主窗口,要求刷新主窗口界面。在main_window.cpp文件中,主窗口类MainWindow_UI重载了customEvent函数,实现了事件需求的操作:刷新主窗口界面。
在test_engine.cpp文件中函数refreshUI实现代码列出如下:
void Engine_View::refreshUI()
{
// pmainWindowGui是应用程序主窗口类MainWindow_UI的实例
qApp->postEvent( pmainWindowGui, new TEST_Event() ); //发送事件
qApp->wakeUpGuiThread(); //唤醒Gui线程
}
在main_window.cpp文件中函数customEvent的实现代码列出如下:
void MainWindow_UI::customEvent( QCustomEvent * e)
{
if ( e->type() == REFRESHUI_EVENT )
{
进行主窗口刷新操作
}
}
在应用程序的基类QApplication中有事件处理函数的实现,其中,事件的发送函数说明如下:
bool QApplication::sendEvent ( QObject * receiver, QEvent * event ) [static]
sendEvent() 立即发送事件给接收对象receiver,当sendEvent()返回时,(事件过滤器和)对象已经处理过事件了。对于很多事件类,可以通过调用isAccepted()函数知道该事件能否被处理者所接受或者拒绝。
void QApplication::postEvent ( QObject * receiver, QEvent * event ) [static]
postEvent()投寄事件到一个队列,以便能延迟分发。在下次Qt的主事件循环运行时,它分发全部事件,还可进行一些优化。例如,若有数个resize事件,它们就被压缩成一个。对于paint事件同样如此:QWidget::update()调用了 postEvent(),postEvent()投寄事件可以避免屏幕因多次重画闪烁,同时还加快了运行速度。
postEvent()在对象初始化期间常常被使用,因为在对象完成初始化后,投送的消息会被很快派发。
bool QApplication::notify ( QObject * receiver, QEvent * e ) [virtual]
发送事件到接收对象者receiver,返回值是从receiver的事件处理函数中返回的值。对于某一类型的事件(如:鼠标和键事件)来说,如果接收者对事件不感兴趣(如:返回FALSE),事件将被传播到receiver的父类,如果父类不感兴趣,就一直向上级传递,直到顶层的object类。
(3)事件的处理
有5种不同的处理事件的方法,列出如下:
(1)重载函数QApplication::notify(),这可提供有效的事件控制,但仅能在派生于QApplication的类中重实现这个函数。
(2)在qApp(是QApplication的全局实例)中实现事件过滤,这样的一个事件过滤器能为所有的widget处理所有的事件,而且,可以有超过一个全局应用程序的事件过滤器。如:鼠标事件的全局事件过滤器设置了鼠标跟踪使能,则鼠标移动事件对于所有widget有效。
(3)重载QObject::event()(在QWidget类中),在任何widget特定的事件过滤器之前,QObject::event()能看到所有事件。QObject::event()函数声明列出如下:
bool QObject::event ( QEvent * e ) [virtual]
这个虚函数接收给一个对象的事件,如果事件被识别并被处理,将返回TRUE。这个函数能被用来重实现一个对象的行为。
(4)在对象上安装事件过滤器。
(5)重载Qt基类事件处理函数
当用户发现Qt基类的事件处理函数不能满足用户的需要时,可以在用户类中重载这些函数,对于特定的Qt事件,可以重载特定的事件函数,如:重载paintEvent(), mousePressEvent()等等函数。如果想对多个Qt事件处理函数进行修改,可以重载QObject::event()来实现。
示例1:重载QObject::event()
下面是重载QObject::event()函数的例子,它进行特定的tab键处理,还处理用户事件。
bool MyClass:event( QEvent * e )
{
if ( e->type() == QEvent::KeyPress )
{
QKeyEvent * ke = (QKeyEvent*) e;
if ( ke->key() == Key_Tab )
{
// 这里是特定的tab处理
k->accept();
return TRUE;
}
}
else if ( e->type() >= QEvent::User )
{
QCustomEvent * c = (QCustomEvent*) e;
// 这里是自定义事件处理
return TRUE;
}
QWidget::event( e );
}
二、事件处理模式
1、 事件处理模式一
首先是事件源产生事件,最后是事件处理器对这些事件进行处理。然而也许大家会问,
Qt中有这么多类的事件,我们怎么样比较简便的处理每个事件呢?设想,如果是每个事件都对应同一个事件处理器,在该事件处理器中对不同的事件进行分类处理,这样的弊端有两点:第一,导致该事件处理器过于臃肿复杂;第二,这样不便于扩展,当系统新增加事件类型或者是我们需要使用到自定义事件时,就不得不修改Qt的源码来达到目的。所以Qt设计者的做法是针对不同类型的事件提供不同的事件处理器与之对应。这里又有一个问题了,Qt中是怎么让不同类型事件与之对应的事件处理器相关联的呢?我们不难猜想在事件和事件处理器中间必定有一个桥梁。这个桥梁就是QObject::event()函数,该函数是虚函数,QObject的子类例如QWidget都实现了该函数。该函数的主要功能是进行事件的分发,也就是将不同类型的事件与之相对应的事件处理器相关联,该函数并不对事件进行处理,真正的事件处理是在事件处理器中进行的。
以上内容用一个图形表示就是:
2、事件处理模式二
很多时候,我们只对某些特定的事件比较关心,例如:鼠标单击或者键盘按下等事件。其它的事件我们并不关心它是否发生,也无需对它们进行处理,这个时候最直接的想法就是将这些事件过滤掉,这样做既可以免去对它们进行处理,也可以避免它们对程序其它部分产生影响。因此,处理模式二中我们引入了事件过滤器这个概念。
该模式总结为一个图如下:
注意:这里需要区别对待,如果你是使用installEventFilter()函数给目标对象注册事件过滤器,那么该事件过滤器只对该目标对象有效,只有该对象的事件需要先传递给eventFilter()函数进行过滤,然后调用相应的事件处理器进行处理,非目标对象则不受影响。如果你是给程序中唯一的QApplication对象注册事件过滤器,那么该过滤器对程序中的每一个对象都有效,任何对象的事件都是先传给eventFilter()函数,然后再使用事件处理器进行处理。
3、事件处理模式三
Qt调用QApplicaton来发送一个事件。所以我们可以重新实现QApplication中的notify()函数来获取事件并进行处理,而且使用该函数获取事件的时间要早于事件过滤器获取事件的时间。但是使用事件过滤器较为简便,因为我们可以有多个事件过滤器,但是只能有一个notify()函数。
用一个图来总结该模式就是:
Qt中事件处理的方法
从以上三个处理模式我们可以看出,这是一个不断完善的过程,从3个模式的讨论中我们不难找到可以进行事件处理的地方,而这几个地方就是我们在编写程序的时候可以控制事件处理的地方。
1、event()函数
首先是控制事件分发的event()函数,我们可以改写该函数,改变事件的分发方式,这样就可以改变事件处理的结果。
2、notify()函数
实现该函数可以截获事件,并对事件加以处理,但是该方法很少用,这里不做介绍。
3、事件过滤器
实现自己的事件过滤器就可以改变事件处理的方法和结果,这个方法比较常用。
4、事件处理器
事件处理的最后一步,也是最重要的一步就是事件处理器,因为它才是真正进行事件处理的地方,我们可以改写以有的事件处理器,以此改变已有事件的处理方法和处理结果,我们也可以定义自己的事件类型和相应的事件处理器。
注意:以上四种方法中最常用的是后两者:事件过滤器和事件处理器。
三、 事件运行机制
当应用程序的main函数中调用qApp->exec()时,应用程序进入Qt的主事件循环,Qt的主事件循环从事件队列中取出本窗口及系统事件,把它们翻译成QEvents,并使用函数QApplication::notify发送翻译的事件给相应的对象QObjects。同时,还处理控制台tty的信号、QWSserver服务器的事件,将来自socket的消息转化成事件进行分发。这些事件的处理工作在函数QEventLoop::processEvents(flags)中完成。
Qt的主事件循环函数QApplication::exec()的调用层次图如图3,从函数的调用层次图可看出事件的分发处理流程,这里没有给出源代码分析,读者可参考Qt 3.4源代码。
通常事件来自于窗口系统(用spontaneous()函数检查时返回TRUE),也可能来自使用Application::sendEvent()和QApplication::postEvent()手动发送的事件(用spontaneous()函数检查时返回FALSE)。
QObjects通过调用它们的QObject::event()接收事件,这个函数也可被子类重载来处理事件,最典型的重载是QWidget::event()。
借鉴博客园—哆啦A梦