Qt的事件和signal是不一样的。signal通常用来"使用"QWidget,而事件用来"实现" QWidget。比如我们在使用一个按钮时,我们只关心他clicked()的signal,然后响应对应的槽函数。而至于这个按钮如何接收处理鼠标事件,再发射这个信号,就是事件的问题,我们要改变这个响应事件的行为的时候,就需要面对event了。例如我们可以重载KeyPressEvent,使得鼠标按键按下(mousePressEvent) 的时候就触发clicked()的signal,而不是通常在释放( mouseReleaseEvent)的时候。
1)事件的产生
事件的两种来源:
一种是系统产生的,通常是System把从系统得到的消息,比如鼠标按键,键盘按键等,放入系统的消息队列中。Qt事件循环的时候读取这些事件,转化为QEvent(),再依次处理。
一种是由Qt应用程序程序自身产生的。程序产生事件有两种方式,一种是调用QApplication::postEvent()。例如QWidget::update()函数,当需要重新绘制屏幕时,程序调用update()函数,new出来一个paintEvent(),调用QApplication::postEvent(),将其放入Qt的消息队列中,等待依次被处理。 另一种方式是调用sendEvent()函数. 这时候事件不会放入队列, 而是直接被派发和处理, QWidget::repaint()函数用的就是这种方式。
2) 事件的调度
两种调度方式,一种是同步,一种是异步。
Qt的事件循环是异步的,当调用QApplication::exec()时,就进入了事件循环。该循环可以简化的描述为如下的代码:
while ( !app_exit_loop ) {
while( !postedEvents ) {
processPostedEvents();
}
while( !qwsEvnts ){
qwsProcessEvents();
}
while( !postedEvents ) {
processPostedEvents();
}
}
3) 事件的派发和处理
首先说明Qt中事件过滤器的概念。事件过滤器是Qt中一个独特的事件处理机制, 功能强大而且使用起来灵活方便,通过它可以让一个对象侦听拦截另外一个对象的事件。事件过滤器是这样实现的: 在所有Qt对象的基类: QObject中有一个类型为QObjectList的成员变量,名字为eventFilters,当某个QObjec (qobjA)给另一个QObject (qobjB)安装了事件过滤器之后,QObjectB会把QObjectA的指针保存在eventFilters中。在QObjectB处理事件之前,会先去检查eventFilters列表,如果非空,就先调用列表中对象的eventFilter()函数。 一个对象可以给多个对象安装过滤器。同样, 一个对象能同时被安装多个过滤器, 在事件到达之后, 这些过滤器以安装次序的反序被调用. 事件过滤器函数( eventFilter() ) 返回值是bool型, 如果返回true, 则表示该事件已经被处理完毕, Qt将直接返回, 进行下一事件的处理; 如果返回false, 事件将接着被送往剩下的事件过滤器或是目标对象进行处理。
eventFilter()的object参数表示事件发生的来源物件,eventFilter()若返回false,则安装该事件过滤器的对象的event()会继续执行,若返回true,则安装事件过滤器的对象后event()方法就不会被执行,由此进行事件的拦截处理。给本对象安装事件过滤器:this->installEventFilter(this);
4)事件的转发
对于某些类别的事件, 如果在整个事件的派发过程结束后还没有被处理, 那么这个事件将会向上转发给它的父widget, 直到最顶层窗口。事件最先发送给QCheckBox, 如果QCheckBox没有处理, 那么由QGroupBox接着处理, 如果QGroupBox没有处理, 再送到QDialog,因为QDialog已经是最顶层QWidget, 所以如果QDialog不处理, QEvent将停止转发.如何判断一个事件是否被处理了呢? Qt中和事件相关的函数通过两种方式相互通信。QApplication::notify(), QObject::eventFilter(), QObject::event() 通过返回bool值来表示是否已处理. “真”表示已经处理, “假”表示事件需要继续传递. 另一种是调用QEvent::ignore() 或 QEvent::accept() 对事件进行标识. 这种方式只用于event() 函数和特定事件处理函数之间的沟通. 而且只有用在某些类别事件上是有意义的, 这些事件就是上面提到的那些会被转发的事件, 包括: 鼠标, 滚轮, 按键等事件。
QT将事件封装为QEvent实例以后,会呼叫QObject的event()方法,并且将QEvent实例传送给它,在某些情况下,希望在执行event()之前,先对一些事件进行处理或过滤,然后再决定是否呼叫event()方法,这时候可以使用事件过滤器。
可以重新定义一个继承自QObject(或其子类)的类的eventFilter()方法,
bool
FilterObject
::
eventFilter
(
QObject
*
object
,
QEvent
*
event
)
{
if
(
event
->
type
()
==
QEvent
::
KeyPress
)
{
QKeyEvent
*
keyEvent
=
static_cast
<
QKeyEvent
*>(
event
);
if
(
keyEvent
->
key
()
==
Qt
::
Key_Tab
)
{
// Process the Tab Key
return
true
;
}
}
return
false
;
}
5) Qt事件的实际应用
Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发。
1,重新实现特定事件 事件函数。 qt的event是通过QObject通知另一个QObject,event用一个QEvent类表示,它是所有event事件的基类, 特殊event如鼠标event用QE vent的子类, 如QMouseEvent类表示。其他的还如 mousePressEvent(), keyPress-Event(),paintEvent() 。 这是最常规的事件处理方法。
以按键事件为例, 一个典型的处理函数如下:
void imageView::keyPressEvent(QKeyEvent * event)
{
- switch (event->key()) {
case Key_Plus:
zoomIn();
break;
case Key_Minus:
zoomOut();
break;
case Key_Left:
// …
default:
QWidget::keyPressEvent(event);
}
}
下面这个例子演示了如何重载event()函数, 改变Tab键的默认动作: (默认的是键盘焦点移动到下一个控件上. )
bool CodeEditor::event(QEvent * event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = (QKeyEvent *) event;
if (keyEvent->key() == Key_Tab) {
insertAtCurrentPosition('\t');
return true;
}
}
return QWidget::event(event);
}
安装事件过滤器有两个步骤: (假设要用A来监视过滤B的事件)
首先调用B的installEventFilter( const QOject *obj ), 以A的指针作为参数. 这样所有发往B的事件都将先由A的eventFilter()处理。
然后, A要重载QObject::eventFilter()函数, 在eventFilter() 中书写对事件进行处理的代码.
用这种方法改写上面的例子: (假设我们将CodeEditor 放在MainWidget中)
MainWidget::MainWidget()
{
// …
CodeEditor * ce = new CodeEditor( this, “code editor”);
ce->installEventFilter( this );
// …
}
bool MainWidget::eventFilter( QOject * target , QEvent * event )
{
if( target == ce ){
if( event->type() == QEvent::KeyPress ) {
QKeyEvent *ke = (QKeyEvent *) event;
if( ke->key() == Key_Tab ){
ce->insertAtCurrentPosition('\t');
return true;
}
}
}
return false;
}