一列Qt事件处理机制分析不到位导致的Bug
一、Qt事件处理机制的知识总结
(1-1)五种Qt事件使用方法总结
-
方法一:重新实现部件的 paintEvent()、mousePressEvent() 等事件处理函数。这是最常用的一种方法(只能用来处理特定部件的特定事件)
-
方法二:重新实现 notify() 函数。这个函数功能强大,提供了完全的控制,可以在事件过滤器得到事件之前就获得它们。(一次只能处理一个事件)
-
方法三:向 QApplication 对象上安装事件过滤器。因为一个程序只有一个 QApplication 对象,所以这样实现的功能与使用 notify() 函数是相同的(可以同时处理多个事件)但是应用程序事件过滤器只对位于主线程中的对象调用。
-
方法四:重新实现 event() 函数。QObject 类的 event() 函数可以在事件到达默认的事件处理函数之前获得该事件。
-
方法五:在对象上安装事件过滤器。使用事件过滤器可以在一个界面类中同时处理不同子部件的不同事件。
在实际编程中,最常用的是方法一,其次是方法五。因为方法二需要继承自 QApplication 类;而方法三要使用一个全局的事件过滤器,这将减缓事件的传递,所以,虽然这两种方法功能很强大,但是却很少被用到。
方法一使用起来较为方便;由于一个界面类中会有多个子部件,所以说方法五使用起来也较为方便。
(1-2)Qt事件的解析机制
在每个程序的 main() 函数的最后都会调用 QApplication 类的 exec() 函数,它会使 Qt 应用程序进人事件循环,这样就可以使应用程序在运行时接收发生的各种事件。一旦有事件发生,Qt 便会构建一个相应的 QEvent 子类的对象来表示,然后将它传递给相应的 QObject 对象或其子对象。
(1-3)Qt事件的传递机制
事件的传递顺序是这样的:先是事件过滤器,然后是焦点部件的 event() 函数,最后是焦点部件的事件处理函数,例如这里的键盘按下事件函数;如果焦点部件忽略了该事件,那么会执行父部件的事件处理函数,如上图所示。注意,event() 函数和事件处理函数,是在该部件内进行重新定义的,而事件过滤器却是在该部件的父部件中进行定义的。
Qt中事件的接收流程如下图所示,*postEvent会将事件插入事件队列*,从而能异步处理事件,*sendEvent直接将事件发出进行处理*。如果返回TRUE或者调用accept(),说明事件已经处理完,不会再向父组件传播,如果返回FALSE或者调用ignore()表明这个事件没有被处理完,Qt会传递给其父组件继续处理该事件。
二、记录一列由Qt事件处理机制分析不到位导致的Bug
2-1、背景描述
首先派生了一个以QGraphicsView为父类的图形视图类,然后在该图形视图类下重写mousePressEvent事件处理函数。在该函数中,需要获取图形视图下的各个QGraphicsItem的data数据,然后判断某个QGraphicsItem被选择(isSelected()),从而发出对应的信号,从而通知其他类组件。
大致就是这样一个代码设计背景。
2-2、Bug记录
在实际开发中,写下了以下的Bug代码:
void RotatelView::mousePressEvent(QMouseEvent *event)
{
QList<QGraphicsItem*> items = this->items();
foreach (QGraphicsItem *item,items)
{
/* ... */
}
QGraphicsView::mousePressEvent(event);
}
在以上代码中,重写mousePressEvent,对鼠标的按下事件进行处理,当进行触摸时,需要获取各个QGraphicsItem的参数进行处理,但是在实际调试过程中,发现各个QGraphicsItem的状态总是延后一次更新!!从而导致处理QGraphicsItem出现异常现象。
分析问题出现的原因,最终发现将 QGraphicsView::mousePressEvent(event);放在了mousePressEvent事件处理函数的末尾,从而导致在获取QGraphicsItem状态数据之前QGraphicsItem没有进行事件传递,导致出现异常。
于是乎,调整代码位置,如下:
void RotatelView::mousePressEvent(QMouseEvent *event)
{
QGraphicsView::mousePressEvent(event);
QList<QGraphicsItem*> items = this->items();
foreach (QGraphicsItem *item,items)
{
/* ... */
}
}
问题得以解决!
三、总结
(1)关于事件处理机制,需要确保事件的正确交付
(2)对于以下函数:
1、bool QObject::eventFilter(QObject *watched, QEvent *event)
2、bool QObject::event(QEvent *e)
3、bool QCoreApplication::notify(QObject *receiver, QEvent *event)
在进行事件接收和处理时,需要确保没有被处理的事件能继续上报传递。例如:
class MyClass : public QWidget
{
Q_OBJECT
public:
...
bool event(QEvent* ev) override
{
......
// 调用QWidget::event(ev) 确保剩下的事件能继续上报并处理
return QWidget::event(ev);
}
};