事件(event)是由系统或者 Qt 自己在不一样的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口须要从新绘制的时候,都会发出一个相应的事件。一些事件在对用户操做作出响应时发出,如键盘事件等;另外一些事件则是由系统自动发出,如计时器事件。框架
事件也就是咱们一般说的“事件驱动(event drive)”程序设计的基础概念。事件的出现,使得程序代码不会按照原始的线性顺序执行。想一想看,从最初的 C 语言开始,咱们的程序就是以一种线性的顺序执行代码:这一条语句执行以后,开始执行下一条语句;这一个函数执行事后,开始执行下一个函数。这种相似“批处理”的程序设计风格显然不适合于处理复杂的用户交互。咱们来想象一下用户交互的情景:咱们设计了一堆功能放在界面上,用户点击了“打开文件”,因而开始执行打开文件的操做;用户点击了“保存文件”,因而开始执行保存文件的操做。咱们不知道用户究竟想进行什么操做,所以也就不能预测接下来将会调用哪个函数。若是咱们设计了一个“文件另存为”的操做,若是用户不点击,这个操做将永远不会被调用。这就是所谓的“事件驱动”,咱们的程序的执行顺序再也不是线性的,而是由一个个事件驱动着程序继续执行。没有事件,程序将阻塞在那里,不执行任何代码。函数
在 Qt 中,事件的概念彷佛同信号槽相似。的确如此,通常来讲,使用 Qt 组件时,咱们并不会把主要精力放在事件上。由于在 Qt 中,咱们关心的更多的是事件关联的一个信号。好比,对于QPushButton
的鼠标点击,咱们不须要关心这个鼠标点击事件,而是关心它的clicked()
信号的发出。这与其余的一些 GUI 框架不一样:在 Swing 中,你所要关心的是JButton
的ActionListener
这个点击事件。由此看出,相比于其余 GUI 框架,Qt 给了咱们额外的选择:信号槽。this
可是,Qt 中的事件和信号槽却并非能够相互替代的。信号由具体的对象发出,而后会立刻交给由connect()
函数链接的槽进行处理;而对于事件,Qt 使用一个事件队列对全部发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部。前一个事件完成后,取出后面的事件进行处理。可是,必要的时候,Qt 的事件也能够不进入事件队列,而是直接处理。信号一旦发出,对应的槽函数必定会被执行。可是,事件则可使用“事件过滤器”进行过滤,对于有些事件进行额外的处理,另外的事件则不关心。总的来讲,若是咱们使用组件,咱们关心的是信号槽;若是咱们自定义组件,咱们关心的是事件。由于咱们能够经过事件来改变组件的默认操做。好比,若是咱们要自定义一个可以响应鼠标事件的EventLabel
,咱们就须要重写QLabel
的鼠标事件,作出咱们但愿的操做,有可能还得在恰当的时候发出一个相似按钮的clicked()
信号(若是咱们但愿让这个EventLabel
可以被其它组件使用)或者其它的信号。设计
在前面咱们也曾经简单提到,Qt 程序须要在main()
函数建立一个QCoreApplication
对象,而后调用它的exec()
函数。这个函数就是开始 Qt 的事件循环。在执行exec()
函数以后,程序将进入事件循环来监听应用程序的事件。当事件发生时,Qt 将建立一个事件对象。Qt 中全部事件类都继承于QEvent
。在事件对象建立完毕后,Qt 将这个事件对象传递给QObject
的event()
函数。event()
函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler)。关于这一点,咱们会在之后的章节中详细说明。code
在全部组件的父类QWidget
中,定义了不少事件处理的回调函数,如keyPressEvent()
、keyReleaseEvent()
、mouseDoubleClickEvent()
、mouseMoveEvent()
、mousePressEvent()
、mouseReleaseEvent()
等。这些函数都是 protected virtual 的,也就是说,咱们能够在子类中从新实现这些函数。下面来看一个例子:对象
#include <QApplication> #include <QMouseEvent> #include <QLabel> class EventLabel : public QLabel { protected: void mouseMoveEvent(QMouseEvent *event); void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); }; 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; msg.sprintf("<center><h1>Release: (%d, %d)</h1></center>", event->x(), event->y()); this->setText(msg); } int main(int argc, char *argv[]) { QApplication a(argc, argv); EventLabel *label = new EventLabel; label->setWindowTitle("MouseEvent Demo"); label->resize(300, 200); label->show(); return a.exec(); }
咱们编译运行上面的代码,就能够理解到有关事件的使用方法。继承
EventLabel
继承了QLabel
,覆盖了mousePressEvent()
、mouseMoveEvent()
和MouseReleaseEvent()
三个函数。咱们并无添加什么功能,只是在鼠标按下(press)、鼠标移动(move)和鼠标释放(release)的时候,把当前鼠标的坐标值显示在这个Label
上面。因为QLabel
是支持 HTML 代码的,所以咱们直接使用了 HTML 代码来格式化文字。队列
QString
的arg()
函数能够自动替换掉QString
中出现的占位符。其占位符以 % 开始,后面是占位符的位置,例如 %1,%2 这种。事件
QString("[%1, %2]").arg(x, y);
语句将会使用 x 替换 %1,y 替换 %2,所以,这个语句生成的QString
为 [x, y]。get
在mouseReleaseEvent()
函数中,咱们使用了另一种QString
的构造方法。咱们使用相似 C 风格的格式化函数sprintf()
来构造QString
。
运行上面的代码,当咱们点击了一下鼠标以后,label 上将显示鼠标当前坐标值。
为何要点击鼠标以后才能在mouseMoveEvent()
函数中显示鼠标坐标值?这是由于QWidget
中有一个mouseTracking
属性,该属性用于设置是否追踪鼠标。只有鼠标被追踪时,mouseMoveEvent()
才会发出。若是mouseTracking
是 false(默认便是),组件在至少一次鼠标点击以后,才可以被追踪,也就是可以发出mouseMoveEvent()
事件。若是mouseTracking
为 true,则mouseMoveEvent()
直接能够被发出。知道了这一点,咱们就能够在main()
函数中直接设置下:
EventLabel *label = new EventLabel; label->setWindowTitle("MouseEvent Demo"); label->resize(300, 200); label->setMouseTracking(true); label->show();
这样子就没有这个问题了。