事件(认识事件,重写event(),事件过滤器,定时器,事件总结)
来源:互联网 发布:淘宝二手苹果ipad 编辑:程序博客网 时间:2024/06/01 08:10
一、认识事件
1、事件(event) 是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
2、事件也就是我们通常说的“事件驱动(event drive) ”程序设计的基础概念。事件的出现,使得程序代码不会按照原始的线性顺序执行。想想看,从最初的 C 语言开始,我们的程序就是以一种线性的顺序执行代码:这一条语句执行之后,开始执行下一条语句;这一个函数执行过后,开始执行下一个函数。这种类似“批处理”的程序设计风格显然不适合于处理复杂的用户交互。我们来想象一下用户交互的情景:我们设计了一堆功能放在界面上,用户点击了“打开文件”,于是开始执行打开文件的操作;用户点击了“保存文件”,于是开始执行保存文件的操作。我们不知道用户究竟想进行什么操作,因此也就不能预测接下来将会调用哪一个函数。如果我们设计了一个“文件另存为”的操作,如果用户不点击,这个操作将永远不会被调用。这就是所谓的“事件驱动”,我们的程序的执行顺序不再是线性的,而是由一个个事件驱动着程序继续执行。没有事件,程序将阻塞在那里,不执行任何代码。3、在 Qt 中,事件的概念似乎同信号槽类似。的确如此,一般来说,使用 Qt 组件时,我们并不会把主要精力放在事件上。因为在 Qt 中,我们关心的更多的是事件关联的一个信号。比如,对于 QPushButton 的鼠标点击,我们不需要关心这个鼠标点击事件,而是关心它的clicked()信号的发出。这与其他的一些 GUI 框架不同:在 Swing 中,你所要关心的是JButton 的 ActionListener 这个点击事件。由此看出,相比于其他 GUI 框架, Qt 给了我们额外的选择:信号槽。
4、但是, Qt 中的事件和信号槽却并不是可以相互替代的。信号由具体的对象发出,然后会马上交给由 connect()函数连接的槽进行处理;而对于事件, Qt 使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部。前一个事件完成后,取出后面的事件进行处理。但是,必要的时候, Qt 的事件也可以不进入事件队列,而是直接处理。信号一旦发出,对应的槽函数一定会被执行。但是,事件则可以使用“事件过滤器”进行过滤,对于有些事件进行额外的处理,另外的事件则不关心。总的来说,如果我们使用组件,我们关心的是信号槽;如果我们自定义组件,我们关心的是事件。因为我们可以通过事件来改变组件的默认操作。比如,如果我们要自定义一个能够响应鼠标事件的 EventLabel,我们就需要重写 QLabel 的鼠标事件,做出我们希望的操作,有可能还得在恰当的时候发出一个类似按钮的 clicked()信号(如果我们希望让这个 EventLabel 能够被其它组件使用)或者其它的信号。
#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QMouseEvent>#include <QLabel>class MainWindow : public QMainWindow{ Q_OBJECTpublic: MainWindow(QWidget *parent = 0); ~MainWindow();};/* * EventLabel 继承了 QLabel,覆盖了 mousePressEvent()、 mouseMoveEvent() * 和MouseReleaseEvent() 三个函数。*/class EventLabel:public QLabel{protected: void mouseMoveEvent(QMouseEvent *event); void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event);};#endif // MAINWINDOW_H
#include "mainwindow.h"#include <QString>MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){}MainWindow::~MainWindow(){}/* *QLabel支持HTML代码,<center>设置文本为水平居中,<h1>设置字体为黑体,</h1></center>是结束符。 * QString 的 arg() 函数可以自动替换掉 QString 中出现的占位符。其占位符以 % 开始,后面是占位 * 符的位置,例如 %1, %2 这种。*/void EventLabel::mouseMoveEvent(QMouseEvent *event){ this->setText(QString("<center><h1>Move:(%1,%2)</h1></center>").arg(QString::number(event->x()),QString::number(event->y())));}void EventLabel::mousePressEvent(QMouseEvent *event){ this->setText(QString("<center><h1>Press:(%1,%2)</h1></center>").arg(QString::number(event->x()),QString::number(event->y())));}void EventLabel::mouseReleaseEvent(QMouseEvent *event){ QString msg; /* * 使用另外一种 QString 的构造方法。类似C风格的格式化函数sprintf()来构造 QString。 */ msg.sprintf("<center><h1>Release:(%d,%d)</h1></center>",event->x(),event->y()); this->setText(msg);}
#include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); EventLabel *la=new EventLabel; la->setWindowTitle("鼠标事件"); la->resize(300,200);//设置窗口大小 /* * QWidget 中有一个 mouseTracking 属性,该属性用于设置是否追踪鼠标。只有鼠标被追踪时, * mouseMoveEvent() 才会发出。如果 mouseTracking 是 false(默认即是),组件在至少一次 * 鼠标点击之后, 才能够被追踪, 也就是能够发出 mouseMoveEvent() 事件。如果 mouseTracking * 为 true,则 mouseMoveEvent() 直接可以被发出。 */ la->setMouseTracking(true); la->show(); return a.exec();}
二、重写event()
event() 函数主要用于事件的分发。所以,如果你希望在事件分发之前做一些操作,就可以重写这个 event() 函数了。我们可以通过使用QEvent::type()函数可以检查事件的实际类型,其返回值是 QEvent::Type类型的枚举。我们处理过自己感兴趣的事件之后,可以直接返回 true,表示我们已经对此事件进行了处理;对于其它我们不关心的事件,则需要调用父类的 event() 函数继续转发,否则这个组件就只能处理我们定义的事件了。
#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QWidget>#include <QEvent>class MainWindow : public QMainWindow{ Q_OBJECTpublic: MainWindow(QWidget *parent = 0); ~MainWindow();};class CustomWidget:public QWidget{protected: bool event(QEvent *e);};#endif // MAINWINDOW_H
#include "mainwindow.h"#include <QKeyEvent>#include <QDebug>#include <Qt>MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){}MainWindow::~MainWindow(){}/* * CustomWidget 是一个普通的 QWidget 子类。我们重写了它的 event()函数,这个函数有 * 一个 QEvent 对象作为参数,也就是需要转发的事件对象。函数返回值是 bool 类型。如果 * 传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是 true,并 * 且,该事件对象设置了 accept(),那么 Qt 会认为这个事件已经处理完毕,不会再将这个事 * 件发送给其它对象,而是会继续处理事件队列中的下一事件。注意,在 event()函数中,调 * 用事件对象的 accept()和 ignore()函数是没有作用的,不会影响到事件的传播。*/bool CustomWidget::event(QEvent *e){ if(e->type()==QEvent::KeyPress){ QKeyEvent *keyEvent=static_cast<QKeyEvent *>(e); if(keyEvent->key()==Qt::Key_Tab){ qDebug()<<"tab键被按下"; return true; } } return QWidget::event(e);}三、事件过滤器
有时候,对象需要查看、甚至要拦截发送到另外对象的事件。例如,对话框可能想要拦截按键事件,不让别的组件接收到;或者要修改回车键的默认处理。通过前面的章节,我们已经知道, Qt 创建了 QEvent 事件对象之后,会调用 QObject 的event()函数处理事件的分发。显然,我们可以在event()函数中实现拦截的操作。由于event()函数是protected 的,因此,需要继承已有类。如果组件很多,就需要重写很多个 event()函数。这当然相当麻烦,更不用说重写 event()函数还得小心一堆问题。好在 Qt 提供了另外一种机制来达到这一目的:事件过滤器。
QObject 有一个 eventFilter()函数,用于建立事件过滤器。这个函数的签名如下:
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );
这个函数正如其名字显示的那样,是一个“事件过滤器”。所谓事件过滤器,可以理解成一种过滤代码。想想做化学实验时用到的过滤器,可以将杂质留到滤纸上,让过滤后的液体溜走。事件过滤器也是如此:它会检查接收到的事件。如果这个事件是我们感兴趣的类型,就进行我们自己的处理;如果不是,就继续转发。这个函数返回一个 bool 类型,如果你想将参数event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回 false。事件过滤器的调用时间是目标对象(也就是参数里面的 watched 对象)接收到事件对象之前。也就是说,如果你在事件过滤器中停止了某个事件,那么, watched 对象以及以后所有的事件过滤器根本不会知道这么一个事件。
#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QTextEdit>#include <QObject>#include <QEvent>class MainWindow : public QMainWindow{ Q_OBJECTprivate: QTextEdit *textedit;protected: bool eventFilter(QObject *obj, QEvent *event);public: MainWindow(); ~MainWindow();};#endif // MAINWINDOW_H
#include "mainwindow.h"#include <QEvent>#include <QDebug>#include <QKeyEvent>MainWindow::MainWindow(){ textedit=new QTextEdit; setCentralWidget(textedit); /* * eventFilter() 函数相当于创建了过滤器,然后我们需要安装这个过滤器。安装过滤器需要调用 * QObject::installEventFilter() 函数。我们可以向一个对象上面安装多个事件处理器,只要调 * 用多次 installEventFilter() 函数。如果一个对象存在多个事件过滤器,那么,最后一个安装 * 的会第一个执行,也就是后进先执行的顺序。 */ textedit->installEventFilter(this);}/* * MainWindow 是我们定义的一个类。我们重写了它的 eventFilter() 函数。为了过滤特定组件上的事件, * 首先需要判断这个对象是不是我们感兴趣的组件,然后判断这个事件的类型。在上面的代码中,我们不想 * 让 textEdit 组件处理键盘按下的事件。所以,首先我们找到这个组件,如果这个事件是键盘事件,则直 * 接返回 true,也就是过滤掉了这个事件,其他事件还是要继续处理,所以返回 false。对于其它的组件, * 我们并不保证是不是还有过滤器,于是最保险的办法是调用父类的函数。*/bool MainWindow::eventFilter(QObject *obj, QEvent *event){ if(obj==textedit){ if(event->type()==QEvent::KeyPress){ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); qDebug() << "Ate key press" << keyEvent->key(); return true; } else{ return false; } } else{ return QMainWindow::eventFilter(obj,event); }}/* * 注意,如果你在事件过滤器中 delete 了某个接收组件,务必将函数返回值设为 true。否则, * Qt 还是会将事件分发给这个接收组件,从而导致程序崩溃。事件过滤器和被安装过滤器的组件 * 必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了 * 不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。*/MainWindow::~MainWindow(){}四、定时器
Qt中有两种方法来使用定时器,一种是定时器事件,另一种是使用信号和槽。一般使用了多个定时器时最好使用定时器事件来处理。
#ifndef WIDGET_H#define WIDGET_H#include <QWidget>#include <QTimerEvent>#include <QLabel>#include <QLineEdit>#include <QTimer>#include <QDateTime>class Widget : public QWidget{ Q_OBJECTprivate slots: void timerUpdate();private: int id1,id2,id3; QLabel *label1; QLabel *label2; QLineEdit *lineEdit;protected: void timerEvent(QTimerEvent *event);public: Widget(QWidget *parent = 0); ~Widget();};#endif // WIDGET_H
#include "widget.h"#include <QString>Widget::Widget(QWidget *parent) : QWidget(parent){ resize(600,600); setWindowTitle(tr("定时器和随机数")); label1=new QLabel(this); label1->move(100,100); label2=new QLabel(this); label2->move(200,100); label2->setFixedSize(40,30); /* * 这里开启了三个定时器,分别返回了它们的id,这个id用来区分不同的定时器。 * 定时器的时间单位是毫秒。每当一个定时器溢出时,都会调用定时器事件处理函数, * 我们可以在该函数中进行相应的处理。 */ id1=startTimer(1000); id2=startTimer(2000); id3=startTimer(10000); /* * 如果只是想开启少量的定时器,也可以使用信号和槽来实现。 * 这里创建了一个定时器,并将其溢出信号和更新槽关联起来,最后使用start()函数来开启定时器。 */ QTimer *timer=new QTimer(this); connect(timer,&QTimer::timeout,this,&Widget::timerUpdate); timer->start(1000); lineEdit=new QLineEdit(this); lineEdit->move(100,300); lineEdit->setFixedSize(200,50); /* *关于随机数,在Qt中是使用qrand()和qsrand()两个函数实现的。在前面的程序中已经看到了qrand() * 函数的使用,其可以产生随机数,qrand()%10可以产生0-9之间的随机数。要想产生100以内的随机数 * 就是%100,以此类推。在使用qrand()函数产生随机数之前,一般要使用qsrand()函数为其设置初值, * 如果不设置初值,那么每次运行程序,qrand()都会产生相同的一组随机数。为了每次运行程序时, * 都可以产生不同的随机数,我们要使用qsrand()设置一个不同的初值。这里使用了QTime类的secsTo() * 函数,它表示两个时间点之间所包含的秒数,比如代码中就是指从零点整到当前时间所经过的秒数。 */ qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));}Widget::~Widget(){}/* * 这里先使用timerId()函数返回了溢出的定时器的id,然后根据该id来判断是哪个定时器溢出了, * 并进行相应的处理。每当第一个定时器溢出时都产生一个小于10的随机数;当第二个定时器溢出时, * 就更改标签的文本。*/void Widget::timerEvent(QTimerEvent *event){ if(event->timerId()==id1){ label1->setText(tr("%1").arg(qrand()%10)); } else if(event->timerId()==id2){ label2->setText(tr("呵呵")); } else{ return QWidget::timerEvent(event); }}void Widget::timerUpdate(){ QDateTime time=QDateTime::currentDateTime();//获取系统现在的时间 QString str=time.toString("yyyy-MM-dd hh:mm:ss ddd");//设置系统时间显示格式 lineEdit->setText(str);//在标签上显示时间 int rand=qrand()%300;//坐标随机数 int rand2=qrand()%200; lineEdit->move(rand,rand2);}
五、事件总结
Qt 中有很多种事件:鼠标事件、键盘事件、大小改变的事件、位置移动的事件等等。如何处理这些事件,实际有两种选择:
1. 所有事件对应一个事件处理函数,在这个事件处理函数中用一个很大的分支语句进行选择。
2. 每一种事件对应一个事件处理函数。
Qt 具有这么多种事件处理函数,肯定有一个地方对其进行分发,否则, Qt 怎么知道哪一种事件调用哪一个事件处理函数呢?这个分发的函数,就是 event()。显然,当 QMouseEvent产生之后, event()函数将其分发mouseEvent()事件处理器进行处理。
event()函数会有两个问题:
1. event()函数是一个 protected 的函数,这意味着我们要想重写 event(),必须继承一个已有的类。试想,我的程序根本不想要鼠标事件,程序中所有组件都不允许处理鼠标事件,是不是我得继承所有组件,一一重写其 event()函数? protected 函数带来的另外一个问题是,如果我基于第三方库进行开发,而对方没有提供源代码,只有一个链接库,其它都是封装好的。我怎么去继承这种库中的组件呢?
2. event()函数的确有一定的控制,不过有时候我的需求更严格一些:我希望那些组件根本看不到这种事件。event()函数虽然可以拦截,但其实也是接收到了 QMouseEvent对象。我连让它收都收不到。这样做的好处是,模拟一种系统根本没有那个事件的效果,所以其它组件根本不会收到这个事件,也就无需修改自己的事件处理函数。这种
需求怎么办呢?这两个问题是 event()函数无法处理的。于是, Qt 提供了另外一种解决方案:事件过滤器。
事件过滤器给我们一种能力,让我们能够完全移除某种事件。事件过滤器可以安装到任意QObject 类型上面,并且可以安装多个。如果要实现全局的事件过滤器,则可以安装到QApplication 或者 QCoreApplication 上面。这里需要注意的是,如果使用installEventFilter()函数给一个对象安装事件过滤器,那么该事件过滤器只对该对象有效,
只有这个对象的事件需要先传递给事件过滤器的 eventFilter()函数进行过滤,其它对象不受影响。如果QApplication 对象安装事件过滤器,那么该过滤器对程序中的每一个对象都有效,任何对象的事件都是先传给 eventFilter()函数。
事件过滤器可以解决刚刚我们提出的 event()函数的两点不足:首先,事件过滤器不是protected 的,因此我们可以向任何 QObject 子类安装事件过滤器;其次,事件过滤器在目标对象接收到事件之前进行处理,如果我们将事件过滤掉,目标对象根本不会见到这个事件。
Qt 的事件处理,实际上是有五个层次:
1. 重写 paintEvent()、 mousePressEvent() 等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
2. 重写 event() 函数。 event() 函数是所有对象的事件入口, QObject 和 QWidget中的实现,默认是把事件传递给特定的事件处理函数。
3. 在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。
4. 在 QCoreApplication::instance() 上面安装事件过滤器。该过滤器将过滤所有对象的所有事件,因此和 notify() 函数一样强大,但是它更灵活,因为可以安装多个过滤器。全局的事件过滤器可以看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
5. 重写 QCoreApplication::notify() 函数。这是最强大的,和全局事件过滤器一样提供完全控制,并且不受线程的限制。但是全局范围内只能有一个被使用(因为QCoreApplication 是单例的)。
#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QWidget>#include <QMouseEvent>#include <QEvent>#include <QObject>/* * 事件过滤器可以解决 event() 函数的两点不足:首先,事件过滤器不是 * protected 的,因此我们可以向任何 QObject 子类安装事件过滤器;其次,事件过滤器在目标 * 对象接收到事件之前进行处理,如果我们将时间过滤掉,目标对象根本不会见到这个事件。*/class Label:public QWidget{public: Label(){ installEventFilter(this); } bool eventFilter(QObject *watched, QEvent *event); ~Label(){}protected: void mousePressEvent(QMouseEvent *); bool event(QEvent *e);};class EventFilter:public QObject{private: QObject *m_watched;public: EventFilter(QObject *watched,QObject *parent=0):QObject(parent),m_watched(watched){} bool eventFilter(QObject *watched, QEvent *event); ~EventFilter(){}};#endif // MAINWINDOW_H
#include "mainwindow.h"#include <QDebug>/* * 第二是全局对象上面的事件过滤器*/bool Label::eventFilter(QObject *watched, QEvent *event){ if(watched==this){ if(event->type()==QEvent::MouseButtonPress){ qDebug()<<"eventFilter"; } } return false;}/* * 最后才是特定的事件处理函数*/void Label::mousePressEvent(QMouseEvent *){ qDebug()<<"mousePressEvent";}/* * 第三是重写的event函数*/bool Label::event(QEvent *e){ if(e->type()==QEvent::MouseButtonPress){ qDebug()<<"event"; } return QWidget::event(e);}/* * 全局事件过滤器最先被调用*/bool EventFilter::eventFilter(QObject *watched, QEvent *event){ if(watched==m_watched){ if(event->type()==QEvent::MouseButtonPress){ qDebug()<<"Application::eventFilter"; } } return false;}
#include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); Label label; a.installEventFilter(new EventFilter(&label,&label));//安装过滤器 label.show(); return a.exec();}
- 事件(认识事件,重写event(),事件过滤器,定时器,事件总结)
- 安装事件过滤器(Installing Event Filters)
- 安装事件过滤器(Installing Event Filters)
- QT event事件过滤器
- Libevent 编程- 定时器事件(timer event)
- 事件(event)
- 事件(Event)
- event事件(1)
- 创建事件(event)
- 事件(event)
- MySQL事件(Event)
- C# 事件(Event)
- C# 事件(Event)
- C#事件(Event)
- MySQL事件(Event)
- C# 事件(Event)
- 事件(Event)
- 【C#】事件(Event)
- mac 下配置 svn 开机自启动
- CCF NOI1052 Self-Numbers
- Linux网络编程学习笔记--socket编程4--7
- poj 2409 Let it Bead
- JDBC
- 事件(认识事件,重写event(),事件过滤器,定时器,事件总结)
- Spring Boot Servlet配置
- 多条目加载--->(如:新闻的布局)
- java——二叉查找树(BST)算法
- 网站商品信息爬虫代码及详细注释
- Problem C: 平面上的点和线——Point类、Line类 (III)
- Android 多状态加载布局的开发 Tips
- C++多线程入门
- duality