• 1. QT程序设计进阶-事件www.gec-edu.org
  • 2. QT的事件机制Qt事件 Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发. Qt事件的类型很多, 常见的qt的事件如下: 键盘事件: 按键按下和松开. 鼠标事件: 鼠标移动,鼠标按键的按下和松开. 拖放事件: 用鼠标进行拖放. 滚轮事件: 鼠标滚轮滚动. 绘屏事件: 重绘屏幕的某些部分. 定时事件: 定时器到时. 焦点事件: 键盘焦点移动. 进入和离开事件: 鼠标移入widget之内,或是移出. 移动事件: widget的位置改变. 大小改变事件: widget的大小改变. 显示和隐藏事件: widget显示和隐藏. 窗口事件: 窗口是否为当前窗口. 还有一些非常见的qt事件,比如socket事件,剪贴板事件,字体改变,布局改变等等.www.gec-edu.org
  • 3. QT的事件机制Qt 的事件和Qt中的signal不一样. 后者通常用来使用widget, 而前者用来实现 widget. 比如一个按钮, 我们使用这个按钮的时候, 我们只关心他clicked()的signal, 至于这个按钮如何接收处理鼠标事件,再发射这个信号,我们是不用关心的. 但是如果我们要重载一个按钮的时候,我们就要面对event了. 比如我们可以改变它的行为,在鼠标按键按下的时候(mouse press event) 就触发clicked()的signal而不是通常在释放的( mouse release event)时候.www.gec-edu.org
  • 4. QT的事件机制事件起源: 基于事件如何被产生与分发,可以把事件分为三类: * Spontaneous 事件 * Posted 事件 * Sent 事件 www.gec-edu.org
  • 5. QT的事件机制Spontaneous 事件,由窗口系统产生,它们被放到系统队列中,通过事件循环逐个处理。 本类事件通常是window system把从系统得到的消息,比如鼠标按键,键盘按键等, 放入系统的消息队列中. Qt事件循环的时候读取这些事件,转化为QEvent,再依次处理. Posted 事件,由Qt或是应用程序产生,它们被Qt组成队列,再通过事件循环处理。 调用QApplication::postEvent()来产生一个posted类型事件. 例如:QWidget::update()函数 当需要重新绘制屏幕时,程序调用update()函数 其实现的原理是new出一个paintEvent,调用 QApplication::postEvent(),将其放入Qt的消息队列中,等待依次被处理. www.gec-edu.org
  • 6. QT的事件机制Sent 事件 由Qt或是应用程序产生,但它们被直接发送到目标对象。 调用QApplication::sendEvent()函数来产生一个sent类型事件. sent 类型事件不会放入队列, 而是直接被派发和处理, QWidget::repaint()函数用的就是这种方式. www.gec-edu.org
  • 7. QT的事件机制当我们在main()函数的末尾调用QApplication::exec()时,程序进入了Qt的事件循环 事件循环如下面所示: while (!exit_was_called) { while(!posted_event_queue_is_empty) { process_next_posted_event(); } while(!spontaneous_event_queue_is_empty) { process_next_spontaneous_event(); } while(!posted_event_queue_is_empty) { process_next_posted_event(); } }www.gec-edu.org
  • 8. QT的事件机制事件循环的处理流程: 先处理Qt事件队列中的posted事件,直至为空 再处理系统消息队列中的spontaneous消息,直至为空 在处理系统消息的时候会产生新的Qt posted事件,需要对其再次进行处理 不通过事件循环 sendEvent的事件派发不通过事件循环。QApplication::sendEvent()是通过调用QApplication::notify(),直接进入了事件的派发和处理环节。 www.gec-edu.org
  • 9. QT的事件机制Notify 调用QApplication::sendEvent的时候, 消息会立即被处理,是同步的. 实际上QApplication::sendEvent()是通过调用QApplication::notify(), 直接进入了事件的派发和处理环节.所有的事件都最终通过 notify 派发到相应的对象中。 bool QApplication::notify ( QObject * receiver, QEvent * event ) 它是通过调用receiver->event(event) 来实现的。 目标接受对象的event方法会自动接受notify传来的event事件 event() 会返回一个布尔值,来告诉调用者是否事件被accept或ignore, (true表示accept),从event()返回的布尔值却是用来与QApplication:notify()通讯的。 www.gec-edu.org
  • 10. QT的事件机制event()函数的处理如下所示: bool QWidget::event(QEvent *event) { switch (e->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; ... } return true; }www.gec-edu.org
  • 11. QT的事件机制 Close事件有点不同,调用QCloseEvent:ignore()取消了关闭操作,而accept()告诉Qt继续执行正常的关闭操作。为了避免混乱,最好是在closeEvent()的新实现中明确地进行accept()与ignore()的调用:、 void MainWindow::closeEvent(QCloseEvent *event) { if (userReallyWantsToQuit()) { event->accept(); } else { event->ignore(); } } www.gec-edu.org
  • 12. keyPressEvent在空白窗体页面,重载当前窗体类的keyPressEvent方法,实现按键事件的响应。 步骤一: 添加头文件 在form.cpp中填加void Form1::keyPressEvent(QKeyEvent *k ) 并实现根据不同的键值,执行不同的动作。 步骤二: 添加头文件 在form.h 中为窗体类form1添加 void keyPressEvent(QKeyEvent *k )声明; 步骤三: 重新编译工程并运行测试。 www.gec-edu.org
  • 13. keyPressEventvoid Form1::keyPressEvent( QKeyEvent *k ) { if(k->key() == Key_Left) { qDebug("Left\n"); .... } else if(k->key() == Key_Right) { qDebug("Right\n"); ... } else QWidget::keyPressEvent(k); }www.gec-edu.org
  • 14. keyPressEvent在具备子控件的复杂窗体中,重载当前窗体类的keyPressEvent方法,实现按键事件的响应。 步骤一: 添加头文件 在form.cpp中填加void Form1::keyPressEvent(QKeyEvent *k ) 并实现根据不同的键值,执行不同的动作。 步骤二: 添加头文件 在form.h 中为窗体类form1添加 void keyPressEvent(QKeyEvent *k )声明; 步骤三: 在form.cpp中,消除子控件的焦点策略,使能方向及Tab按键功能。 步骤四: 重新编译工程并运行测试。www.gec-edu.org
  • 15. keyPressEvent例如: pushButton1 = new QPushButton( this, "pushButton1" ); pushButton1->setGeometry( QRect( 200, 150, 111, 41 ) ); pushButton1->setFocusPolicy(QWidget::NoFocus); void QWidget::setFocusPolicy ( FocusPolicy ) 设置这个窗口部件接收键盘焦点的方式。 “focusPolicy”属性保存的是窗口部件接收键盘焦点的策略。 如果窗口部件通过tab来接收键盘焦点,这个策略就是QWidget::TabFocus; 如果窗口部件通过点击来接收键盘焦点,这个策略就是QWidget::ClickFocus; 如果窗口部件上述两种方式都使用,是QWidget::StrongFocus; 如果它不接收焦点(QWidget的默认值),是QWidget::NoFocus。 www.gec-edu.org
  • 16. event重载当前窗体类的event方法,实现针对性事件的处理与过滤效果。 步骤一: 在form.cpp中填加bool Form1::event(QEvent *event) 并实现根据不同的键值,执行不同的动作。 步骤二: 在form.h 中为窗体类form1添加 bool event(QEvent *event)声明; 步骤三: 重新编译工程并运行测试。www.gec-edu.org
  • 17. eventbool Form1::event(QEvent * event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = (QKeyEvent *) event; if (keyEvent->key() == Key_A) { qDebug("--cut the Key_A--\n"); return true; } } return QWidget::event(event); }www.gec-edu.org
  • 18. eventFilterQt事件模型一个真正强大的特色是一个QObject 的实例能够管理另一个QObject 实例的事件。 一个CustomerDialog的小部件。CustomerDialog 包含一系列QLineEdit. 现在,我们想用空格键来代替Tab,使焦点在这些QLineEdit间切换。 一个解决的方法是子类化QLineEdit,重新实现keyPressEvent(),并在keyPressEvent()里调用focusNextChild()。像下面这样: void MyLineEdit::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Space) { focusNextChild(); } else { QLineEdit::keyPressEvent(event); } } www.gec-edu.org
  • 19. eventFilter上述做法有一个缺点。如果CustomerDialog里有很多不同的控件(比如QComboBox,QEdit,QSpinBox),我们就必须子类化这么多控件。这是一个烦琐的任务。 一个更好的解决办法是: 让CustomerDialog去管理他的子部件的按键事件,实现要求的行为。我们可以使用事件过滤器。 一个事件过滤器的安装需要下面2个步骤: 1, 调用installEventFilter()注册需要管理的对象。 2,在eventFilter() 里处理需要管理的对象的事件。 一般,推荐在CustomerDialog的构造函数中注册被管理的对象。像下面这样: CustomerInfoDialog::CustomerInfoDialog(QWidget *parent) : QDialog(parent) { ... firstNameEdit->installEventFilter(this); lastNameEdit->installEventFilter(this); cityEdit->installEventFilter(this); phoneNumberEdit->installEventFilter(this); } 一旦,事件管理器被注册,发送到firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit的事件将首先发送到eventFilter()。 www.gec-edu.org
  • 20. eventFilter下面是一个 eventFilter()函数的实现: bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event) { if (target == firstNameEdit || target == lastNameEdit || target == cityEdit || target == phoneNumberEdit) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Space) { focusNextChild(); return true; } } } return QDialog::eventFilter(target, event); } www.gec-edu.org
  • 21. eventFilter在上面的函数中,我们首先检查目标部件是否是 firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit。接着,我们判断事件是否是按键事件。如果事件是按键事件,我们把事件转换为QKeyEvent。接着,我们判断是否按下了空格键,如果是,我们调用focusNextChild(),把焦点传递给下一个控件。然后,返回,true通知Qt,我们已经处理了该事件。 如果返回false的话,Qt继续将该事件发送给目标控件,结果是一个空格被插入到QLineEdit中。 如果目标控件不是 QLineEdit,或者按键不是空格键,我们将把事件传递给基类的eventFilter()函数。www.gec-edu.org
  • 22. eventFilterQt提供5个级别的事件处理和过滤: 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()方法。 www.gec-edu.org
  • 23. eventFilter在创建了过滤器之后,下面要做的是安装这个过滤器。安装过滤器需要调用installEventFilter()函数。这个函数的签名如下:  void QObject::installEventFilter ( QObject * filterObj ) 这个函数是QObject的一个函数,因此可以安装到任何QObject的子类,并不仅仅是UI组件。这个函数接收一个QObject对象,调用了这个函数安装事件过滤器的组件会调用filterObj定义的eventFilter()函数。 例如,textField->installEventFilter(obj),则如果有事件发送到textField组件是,会先调用obj->eventFilter()函数,然后才会调用textField->event()。 也可以把事件过滤器安装到QApplication上面,这样就可以过滤所有的事件,已获得更大的控制权。不过,这样做的后果就是会降低事件分发的效率。 如果一个组件安装了多个过滤器,则最后一个安装的会最先调用,类似于堆栈的行为。www.gec-edu.org
  • 24. eventFilterpushButton2 = new QPushButton( this, "pushButton2" ); pushButton2->setGeometry( QRect( 200, 160, 111, 31 ) ); pushButton2->installEventFilter( this ); bool Form1::eventFilter( QObject *o, QEvent *e ) { if( pushButton2==o ) { if ( e->type() == QEvent::KeyPress ) { QKeyEvent *k = (QKeyEvent *)e; qDebug( "eat key press %d", k->key() ); return TRUE; } www.gec-edu.org
  • 25. eventFilter if ( e->type() == QEvent::MouseButtonPress ) { QMouseEvent *k = (QMouseEvent *)e; qDebug( "eat Mouse press " ); return TRUE; } else { return FALSE; } } else return QWidget::eventFilter( o, e ); }www.gec-edu.org
  • 26. eventFilterbool Form1::event(QEvent * event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = (QKeyEvent *) event; if (keyEvent->key() == Key_A) { qDebug("--cut the Key_A--\n"); return true; } } return QWidget::event(event); }www.gec-edu.org
  • 27. QT的事件机制事件的产生 QT应用程序可以产生自定义的事件,或是预定义类型,或是自定义类型。 这可以通过创建QEvent类或它的子类的实例,并且调用QApplication:postEvent()或QApplication::sendEvent()来实现。 这两个函数需要一个 QObject* 与一个QEvent * 作为参数,假如你调用postEvent(),你必须用 new 操作符来创建事件对象,Qt会它被处理后帮你删除它。 假如你用sendEvent(), 你应该在栈上来创建事件。www.gec-edu.org
  • 28. QT的事件机制下面举两个例子: 一是posting 事件: QApplication::postEvent(mainWin, new QKeyEvent(QEvent::KeyPress,Key_X,'X',0,"X")); 二是sending 事件: QKeyEvent event(QEvent::KeyPress, Key_X, 'X', 0,"X"); QApplication::sendEvent(mainWin, &event); Qt应用程序很少直接调用postEvent()或是sendEvnet(),因为大多数事件会在必要时被Qt或是窗口系统自动产生。在大多数的情况下,当你想发送一个事件时,Qt已经为你准备好了一个更高级的函数来为你服务。(例如update()与repaint())。 为了提高qt程序的自定义特性,可以显式得采用程序实现事件的发送。www.gec-edu.org
  • 29. QT的事件机制 重绘事件 paintEvent() 当窗口被其他窗口覆盖后,再次重新显示时,系统将产生 spontaneous 事件来请求重绘,事件循环最终从事件队列中捡选这个事件并把它分发到那个需要重画的widget。 当我们调用 QWidget::update() 时,产生的是 Posted 重绘事件 当我们调用 QWidget::repaint() 时,产生的是 Sent 重绘事件 www.gec-edu.org
  • 30. QT的事件机制posting 相对于sending的一个优势是,它给了Qt一个压缩(compress)事件的机会。假如你在一个widget上连续地调用update() 十次,因update()而产生的这十个事件,将会自动地被合并为一个单独的事件,但是QPaintEvents事件附带的区域信息也合并了。 可压缩的事件类型包括:paint,move,resize,layout hint,language change。 最后要注意,可以在任何时候调用QApplication::sendPostedEvent(),强制Qt产生一个对象的posted事件。www.gec-edu.org
  • 31. QT的事件机制Qt 系统还提供了一个 QCustomEvent 类,用于用户自定义事件,这些自定义事件可以利用 QThread::postEvent() 或者QApplication::postEvent() 被发给各种控件或其他 QObject 实例。 QWidget 类的子类可以通过 QWidget::customEvent() 事件处理函数方便地接收到这些自定义的事件。 需要注意的是:QCustomEvent 对象在创建时都带有一个类型标识 id 以定义事件类型,为了避免与 Qt 系统定义的事件类型冲突,该 id 值应该大于枚举类型 QEvent::Type 中给出的 "User" 值。 www.gec-edu.org
  • 32. QT的事件机制演示如何post一个定制事件的代码片段: const QEvent::Type MyEvent = (QEvent::Type)1234; ... QApplication::postEvent(mainwin, new QCustomEvent(MyEvent)); 事件必须是QCustomEvent类型(或子类)的。 构造函数的参数是事件的类型,1000以下被Qt保留。其他可被程序使用。为处理定制事件类型,要重新实现customEvent()函数:www.gec-edu.org
  • 33. QT的事件机制void MyWin::customEvent(QCustomEvent *event) { if (event->type() == MyEvent) { myEvent(); } else { Qwidget::customEvent(event); } } QcustomEvent类有一个void *的成员,可用于特定的目的。你也可以子类化QCustomEvent,加上别的成员。www.gec-edu.org
  • 34. QT的事件机制一些事件类型可以被传递。这意味着假如目标对象不处理一个事件,Qt会试着寻找另外的事件接收者。用新的目标来调用QApplication::notify()。 举例来讲,key事件是传递的,假如拥有焦点的Widget不处理特定键,Qt会分发相同的事件给父widget,然后是父亲的父亲,直到最顶层widget。 可被传递的事件可以“接收”或是“忽略”这个事件。假如事件被处理,这个事件将不会再被传递。否则Qt会试着查找另外的事件接收者。 大部分qt对象对事件的处理缺省情况下是“接收”,在QWidget中的缺省实现是调用“忽略”,假如你希望接收事件,你需要做的是重新实现事件handler,避免调用QWidget的实现。假如你想“忽略”事件,只需简单地传递它到QWidget的实现。www.gec-edu.org
  • 35. QT的事件机制下面的代码演示了这一点: void MyWidget::keyPressEvent(QKeyEvent *event) { if (event->key() == Key_Escape) { doEscape(); } else { QWidget::keyPressEvent(event); } } 在上面的例子里,假如用户按了"ESC"键,我们会调用doEscape()并且事件被“接收”了(这是缺省的情况),事件不会被传递到父widget,假如用户按了别的键,则调用QWidget的缺省实现。www.gec-edu.org
  • 36. QT的事件机制void QWidget::keyPressEvent(QKeyEvent *event) { event->ignore(); } 此处调用ignore(),事件会被传递到父widget中去。 以上假设基类都是QWidget,然而,同样的规则也可以应用到别的层次中,只要用其他基类代替QWidget即可。 举例来说: void MyLineEdit::keyPressEvent(QKeyEvent *event) { if (event->key() == Key_SysReq) { doSystemRequest(); } else { QLineEdit::keyPressEvent(event); } }www.gec-edu.org
  • 37. www.gec-edu.orgThank You !