(8条消息) 第七章 Qt事件(event)处理

一、事件处理器

事件是由窗口系统或者Qt自身产生的,以响应各类事件。当用户按下键盘或者鼠标就会产生相关事件,系统会捕捉到该事件。在使用Qt进行编程时,基本不需要考虑事件,因为发生事件时,Qt会自己发出信号,不过自定义窗口就要特别注意了。

不应该混淆“事件“和”信号”这两个概念。一般情况下,在使用窗口部件时候,信号是十分有用的;而实现窗口部件,事件则十分有用哦~

换句话说,在使用QPushButton时候,clicked()信号会更有用,但是需要实现类似QPushButton功能的类编写事件代码会更有效。

Qt中,事件就是QEvent子类的一个实例。Qt处理的事件类型非常多,每一种事件都有一个枚举类型值对应。例如:QEvent::type()可以返回处理鼠标事件的QEvent::MouseButtonPress。

通过重新实现事件函数可以打到你自己的目的。在前面几章中也有事件使用实例,回顾一下,温故知新。

这里说明另外一种事件,定时器事件。定时器事件允许应用程序可以再一定的时间间隔后执行事件处理。定时器事件可以用来实现光标的闪烁和其他的动画播放,或者只是简单的刷新。下面给出一个滚动字母例子:

头文件

  1. #ifndef TICKER_H
  2. #define TICKER_H
  3. #include <QWidget>
  4. class Ticker : public QWidget
  5. {
  6. Q_OBJECT
  7. Q_PROPERTY(QString text READ text WRITE setText)
  8. public:
  9. Ticker(QWidget *parent = 0);
  10. void setText(const QString &newText);
  11. QString text() const { return myText; }
  12. QSize sizeHint() const;
  13. protected:
  14. void paintEvent(QPaintEvent *event);
  15. void timerEvent(QTimerEvent *event);
  16. void showEvent(QShowEvent *event);
  17. void hideEvent(QHideEvent *event);
  18. private:
  19. QString myText;
  20. int offset;
  21. int myTimerId;
  22. };
  23. #endif


从新实现事件处理器:

  1. protected:
  2. void paintEvent(QPaintEvent *event);
  3. void timerEvent(QTimerEvent *event);
  4. void showEvent(QShowEvent *event);
  5. void hideEvent(QHideEvent *event);

尤其是timerEvent,事件参数大多都是QXXXEvent类型的。

实现文件:

  1. #include <QtGui>
  2. #include "ticker.h"
  3. Ticker::Ticker(QWidget *parent)
  4. : QWidget(parent)
  5. {
  6. offset = 0;
  7. myTimerId = 0;
  8. }
  9. void Ticker::setText(const QString &newText)
  10. {
  11. myText = newText;
  12. update();
  13. updateGeometry();
  14. }
  15. QSize Ticker::sizeHint() const
  16. {
  17. return fontMetrics().size(0, text());
  18. }
  19. void Ticker::paintEvent(QPaintEvent * /* event */)
  20. {
  21. QPainter painter(this);
  22. int textWidth = fontMetrics().width(text());
  23. if (textWidth < 1)
  24. return;
  25. int x = -offset;
  26. while (x < width()) {
  27. painter.drawText(x, 0, textWidth, height(),
  28. Qt::AlignLeft | Qt::AlignVCenter, text());
  29. x += textWidth;
  30. }
  31. }
  32. void Ticker::showEvent(QShowEvent * /* event */)
  33. {
  34. myTimerId = startTimer(30);
  35. }
  36. void Ticker::timerEvent(QTimerEvent *event)
  37. {
  38. if (event->timerId() == myTimerId) {
  39. ++offset;
  40. if (offset >= fontMetrics().width(text()))
  41. offset = 0;
  42. scroll(-1, 0);
  43. } else {
  44. QWidget::timerEvent(event);
  45. }
  46. }
  47. void Ticker::hideEvent(QHideEvent * /* event */)
  48. {
  49. killTimer(myTimerId);
  50. myTimerId = 0;
  51. }


构造函数吧offset变量初始化为0,。用来绘制文本x坐标就去自与这个offset数值。定时器的ID通常是非0的,所以可以使用0表示定时器还没有启动。
setText()调用update()强制执行一个重绘操作。

void Ticker::showEvent(QShowEvent * /* event */)

函数用来提供一个定时器,QObject::startTimer()调用会返回一个ID数字,可以在以后用这个数字识别该定时器。调用之后,大约没30毫秒产生一个定时器事件,切记精度并不是实际上的30毫秒,与操作系统有关的嘞。

void Ticker::timerEvent(QTimerEvent *event)

系统每隔一定时间都会调用一次该函数,通过在offset上加1来模拟移动,从而形成文本宽度的连续滚动。然后,使用scroll()执行滚动一个像素的操作。

void Ticker::hideEvent(QHideEvent * /* event */)

可以用来停止定时器。

main.cpp

  1. #include <QApplication>
  2. #include "ticker.h"
  3. int main(int argc, char *argv[])
  4. {
  5. QApplication app(argc, argv);
  6. Ticker ticker;
  7. ticker.setWindowTitle(QObject::tr("Ticker"));
  8. ticker.setText(QObject::tr("How long it lasted was impossible to "
  9. "say ++ "));
  10. ticker.show();
  11. return app.exec();
  12. }



二、安装事件过滤器

    Qt事件模型是一个非常强大的功能,QObject实例在看到他自己的事件之前,可以通过设置另一个QObject实例先监视这些事件。

    Qt创建了QEvent事件对象之后,会调用QObject的event()函数做事件的分发。有时候,你可能需要在调用event()函数之前做一些另外的操作,比如,对话框上某些组件可能并不需要响应回车按下的事件,此时,你就需要重新定义组件的event()函数。如果组件很多,就需要重写很多次event()函数,这显然没有效率。为此,你可以使用一个事件过滤器,来判断是否需要调用event()函数。QOjbect有一个eventFilter()函数,用于建立事件过滤器。这个函数的签名如下:

virtual bool QObject::eventFilter(QObject * watched, QEvent * event)

如果watched对象安装了事件过滤器,这个函数会被调用并进行事件过滤,然后才轮到组件进行事件处理。在重写这个函数时,如果你需要过滤掉某个事件,例如停止对这个事件的响应,需要返回true。

  1. bool MainWindow::eventFilter(QObject *obj, QEvent *event)
  2. {
  3. if (obj == textEdit) {
  4. if (event->type() == QEvent::KeyPress) {
  5. QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
  6. qDebug() << "Ate key press" << keyEvent->key();
  7. return true;
  8. } else {
  9. return false;
  10. }
  11. } else {
  12. // pass the event on to the parent class
  13. return QMainWindow::eventFilter(obj, event);
  14. }
  15. }

    上面的例子中为MainWindow建立了一个事件过滤器。为了过滤某个组件上的事件,首先需要判断这个对象是哪个组件,然后判断这个事件的类型。例如,我不想让textEdit组件处理键盘事件,于是就首先找到这个组件,如果这个事件是键盘事件,则直接返回true,也就是过滤掉了这个事件,其他事件还是要继续处理,所以返回false。对于其他组件,我们并不保证是不是还有过滤器,于是最保险的办法是调用父类的函数。

在创建了过滤器之后,下面要做的是安装这个过滤器。安装过滤器需要调用installEventFilter()函数。这个函数的签名如下:

void QObject::installEventFilter ( QObject * filterObj )

这个函数是QObject的一个函数,因此可以安装到任何QObject的子类,并不仅仅是UI组件。这个函数接收一个QObject对象,调用了这个函数安装事件过滤器的组件会调用filterObj定义的eventFilter()函数。例如,textField.installEventFilter(obj),则如果有事件发送到textField组件是,会先调用obj->eventFilter()函数,然后才会调用textField.event()。

当然,你也可以把事件过滤器安装到QApplication上面,这样就可以过滤所有的事件,已获得更大的控制权。不过,这样做的后果就是会降低事件分发的效率。

如果一个组件安装了多个过滤器,则最后一个安装的会最先调用,类似于堆栈的行为。

注意,如果你在事件过滤器中delete了某个接收组件,务必将返回值设为true。否则,Qt还是会将事件分发给这个接收组件,从而导致程序崩溃。

事件过滤器和被安装的组件必须在同一线程,否则,过滤器不起作用。另外,如果在install之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。

事件的调用最终都会调用QCoreApplication的notify()函数,因此,最大的控制权实际上是重写QCoreApplication的notify()函数。由此可以看出,Qt的事件处理实际上是分层五个层次:重定义事件处理函数,重定义event()函数,为单个组件安装事件过滤器,为QApplication安装事件过滤器,重定义QCoreApplication的notify()函数。这几个层次的控制权是逐层增大的。

参考资料:

QT学习 之 事件与事件过滤器 点击打开链接

Qt怎样使用事件过滤器 点击打开链接

Qt的事件过滤器 点击打开链接

三、处理密集时的响应保持

当调用了QApplication::exec()时就启动了Qt的时间循环。在开始的时候,Qt会发出一些时间命令来显示和绘制窗口部件。在这之后,事件循环就开始运行,他不断的检查是否有事件发生并且把这些时间发送给应用程序中的QObject。

当处理一个时间时,也可能会同时产生一些其他的事件并且会追加到Qt的事件队列中。若在处理一个特定事件耗费太多时间就会让界面无法响应。当然可以用多线程解决问题,但是我不会哈哈哈~还可以用QApplication::processEvent()。这个函数告诉Qt处理所有那些还没有被处理的各类事件,然后再将控制权返回给调用者。实际上QApplication::exce()就是一个不停调用processEvent的while循环。

参考:点击打开链接

(0)

相关推荐