事件Qevent的接受和忽略 和重定义 事件过滤器

本文详细解析了Qt中事件处理的基本流程,包括事件的触发、事件对象的创建与传递、事件分发以及如何通过重写事件处理函数、安装事件过滤器等手段进行事件的拦截与控制。此外,还介绍了如何在特定场景下优化事件处理效率,例如通过事件过滤器来减少不必要的事件处理,从而提升应用性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

事件处理流程:
某个事件发生
------>exec()循环会接收到这个事件------>

创建一个事件对象,并将对象传递给QObject::event()------> 

在QWidget::event()函数中,分配给特定的事件处理函数------>

在QButton的事件处理函数中emit(clicked消息)

 

 

前面说到了事件的作用,下面来看看我们如何来接收事件。回忆一下前面的代码,我们在子类中重写了事件函数,以便让这些子类按照我们的需要完成某些功能,就像下面的代码:

void MyLabel::mousePressEvent(QMouseEvent * event)
{
         if( event->button() == Qt::LeftButton) {
                 // do something
        } else {
                QLabel::mousePressEvent( event);
        }
}


上面的代码和前面类似,在鼠标按下的事件中检测,如果按下的是左键,做我们的处理工作,如果不是左键,则调用父类的函数。这在某种程度上说,是把事件向上传递给父类去响应,也就是说,我们在子类中“忽略”了这个事件。

我们可以把Qt的事件传递看成链状:如果子类没有处理这个事件,就会继续向其他类传递。其实,Qt的事件对象都有一个accept()函数和ignore()函数。正如它们的名字,前者用来告诉Qt,事件处理函数“接收”了这个事件,不要再传递;后者则告诉Qt,事件处理函数“忽略”了这个事件,需要继续传递,寻找另外的接受者。在事件处理函数中,可以使用isAccepted()来查询这个事件是不是已经被接收了。

事实上,我们很少使用accept()和ignore()函数,而是想上面的示例一样,如果希望忽略事件,只要调用父类的响应函数即可。记得我们曾经说过,Qt中的事件大部分是protected的,因此,重写的函数必定存在着其父类中的响应函数,这个方法是可行的。为什么要这么做呢?因为我们无法确认父类中的这个处理函数没有操作,如果我们在子类中直接忽略事件,Qt不会再去寻找其他的接受者,那么父类的操作也就不能进行,这可能会有潜在的危险。另外我们查看一下QWidget的mousePressEvent()函数的实现:

void QWidget::mousePressEvent(QMouseEvent * event)
{
         event->ignore();
         if ((windowType() == Qt::Popup)) {
                 event->accept();
                QWidget* w;
                 while ((w = qApp->activePopupWidget()) && w != this){
                        w->close();
                         if (qApp->activePopupWidget() == w) // widget does not want to dissappear
                                w->hide(); // hide at least
                }
                 if (!rect().contains( event->pos())){
                        close();
                }
        }
}


请注意第一条语句,如果所有子类都没有覆盖mousePressEvent函数,这个事件会在这里被忽略掉,这暗示着这个组件不关心这个事件,这个事件就可能被传递给其父组件。

不过,事情也不是绝对的。在一个情形下,我们必须使用accept()和ignore()函数,那就是在窗口关闭的时候。如果你在窗口关闭时需要有个询问对话框,那么就需要这么去写:

void MainWindow::closeEvent(QCloseEvent * event)
{
         if(continueToClose()) {
                 event->accept();
        } else {
                 event->ignore();
        }
}

bool MainWindow::continueToClose()
{
         if(QMessageBox::question( this,
                                            tr( "Quit"),
                                            tr( "Are you sure to quit this application?"),
                                            QMessageBox::Yes | QMessageBox::No,
                                            QMessageBox::No)
                == QMessageBox::Yes) {
                 return true;
        } else {
                 return false;
        }
}


这样,我们经过询问之后才能正常退出程序

今天要说的是event()函数。记得之前曾经提到过这个函数,说在事件对象创建完毕后,Qt将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。

event()函数主要用于事件的分发,所以,如果你希望在事件分发之前做一些操作,那么,就需要注意这个event()函数了。为了达到这种目的,我们可以重写event()函数。例如,如果你希望在窗口中的tab键按下时将焦点移动到下一组件,而不是让具有焦点的组件处理,那么你就可以继承QWidget,并重写它的event()函数,已达到这个目的:

bool MyWidget:: event(QEvent * event) {
         if ( event->type() == QEvent::KeyPress) {
                QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event);
                 if (keyEvent->key() == Qt::Key_Tab) {
                         // 处理Tab鍵
                         return true;
                }
        }

         return QWidget:: event( event);
}


event()函数接受一个QEvent对象,也就是需要这个函数进行转发的对象。为了进行转发,必定需要有一系列的类型判断,这就可以调用QEvent的type()函数,其返回值是QEvent::Type类型的枚举。我们处理过自己需要的事件后,可以直接return回去,对于其他我们不关心的事件,需要调用父类的event()函数继续转发,否则这个组件就只能处理我们定义的事件了。

event()函数返回值是bool类型,如果传入的事件已被识别并且处理,返回true,否则返回false。如果返回值是true,QApplication会认为这个事件已经处理完毕,会继续处理事件队列中的下一事件;如果返回值是false,QApplication会尝试寻找这个事件的下一个处理函数。

event()函数的返回值和事件的accept()和ignore()函数不同。accept()和ignore()函数用于不同的事件处理器之间的沟通,例如判断这一事件是否处理;event()函数的返回值主要是通知QApplication的notify()函数是否处理下一事件。为了更加明晰这一点,我们来看看QWidget的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;
                 // more...
        }
         return true;
}


QWidget的event()函数使用一个巨大的switch来判断QEvent的type,并且分发给不同的事件处理函数。在事件处理函数之后,使用这个事件的isAccepted()方法,获知这个事件是不是被接受,如果没有被接受则event()函数立即返回false,否则返回true。

另外一个必须重写event()函数的情形是有自定义事件的时候。如果你的程序中有自定义事件,则必须重写event()函数以便将自定义事件进行分发,否则你的自定义事件永远也不会被调用。

 

创建事件过滤器和安装事件过滤器

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

QOjbect有一个eventFilter()函数,用于建立事件过滤器。这个函数的签名如下:

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


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

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 {
                 // pass the event on to the parent class
                 return QMainWindow::eventFilter(obj, event);
         }
}


上面的例子中为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()函数。这几个层次的控制权是逐层增大的。

 

 

参看

http://blog.csdn.net/xie376450483/archive/2010/10/09/5930564.aspx

<think>好的,我现在需要回答用户关于如何在Qt中实现主界面停止绘图更新事件的问题。首先,我得回忆一下Qt的相关机制,特别是事件处理绘图相关的部分。 用户提到的是“停止绘图更新事件”,这可能指的是阻止界面重绘,或者暂停某些绘图操作。Qt中绘图通常是通过重写paintEvent方法,或者调用update()来触发的。要停止绘图更新,可能需要阻止这些事件的触发或处理。 首先,我应该想到的是Qt事件处理系统。每个控件都有自己的事件处理函数,比如paintEvent、resizeEvent等。要停止绘图,可能需要拦截或忽略paintEvent事件。或者,设置一个标志变量,在paintEvent中根据这个标志决定是否执行绘图代码。 另外,Qt中有一个方法叫做setUpdatesEnabled,可以控制控件是否接收绘制更新。如果调用widget->setUpdatesEnabled(false),那么该控件就不会处理绘制事件,直到重新设置为true。这可能是一个直接的解决方法。但需要确认这个方法的具体效果,是否真的停止重绘,或者只是延迟到重新启用时合并处理。 然后,用户可能需要的是在某个条件下暂停界面刷新,比如处理大量数据时避免频繁重绘。这时候,setUpdatesEnabled可能适用,或者使用blockSignals来阻止信号触发update。不过blockSignals主要是阻止信号的传递,而update()的调用可能来自多个地方,所以可能不够全面。 接下来,可能需要具体步骤。比如,如何通过标志变量控制paintEvent的执行。例如,在自定义的窗口类中添加一个bool成员变量m_disablePaint,在paintEvent中检查这个变量,如果为true,则直接返回,不执行绘图操作。然后提供公有方法enablePaintdisablePaint来修改这个变量,并调用update()来强制重绘或停止。 另外,可能需要考虑多线程的情况。如果主界面因为长时间的计算任务而被阻塞,导致界面无法及时重绘,这时候应该将耗时任务放到子线程中,避免阻塞主事件循环。但这可能与用户的问题不完全相关,用户可能更关注如何主动停止绘图更新,而不是由于阻塞导致的停止。 然后,需要检查是否有其他方法,比如使用QEventLoop来暂停事件处理,但这可能过于复杂。或者重写eventFilter,拦截QPaintEvent事件,但这需要安装事件过滤器。 综合以上思路,可能的解决方案包括: 1. 使用setUpdatesEnabled(false)来暂时禁止控件的更新,但需要注意这可能不会立即停止已经发出的绘制请求,可能需要配合repaint()或update()。 2. 在paintEvent中使用条件判断,通过标志变量控制是否执行绘制代码。 3. 拦截QPaintEvent事件,通过事件过滤器或重写event函数来忽略绘制事件。 接下来,需要验证这些方法的正确性适用性。例如,setUpdatesEnabled的文档说明,当设置为false时,控件会停止绘制,并且所有待处理的绘制事件会被合并,直到重新启用。这可能适用于临时停止更新,比如批量操作时减少重绘次数,但如果用户希望完全停止任何绘制,可能需要更彻底的方法。 而使用标志变量在paintEvent中控制,可以更灵活地决定是否绘制某些部分,或者完全不执行任何绘制操作。但需要注意,如果完全跳过paintEvent的处理,可能需要手动处理背景或其它必要的绘制,否则可能出现界面残留或空白的情况。 另外,直接阻止paintEvent的触发可能比较困难,因为Qt内部会根据需要自动触发绘制事件,比如窗口被覆盖后重新显示,或者调整大小等。因此,单纯阻止update()的调用可能不够,因为系统可能仍然会生成绘制事件。 因此,最可靠的方法可能是在paintEvent中根据标志变量来决定是否执行绘制代码,同时结合setUpdatesEnabled来减少不必要的更新请求。 最后,需要给出具体的代码示例,说明如何实现这些方法。例如,定义一个继承自QWidget的自定义类,添加一个布尔成员变量,重写paintEvent,并提供启用/禁用绘图的方法。或者,在需要的时候调用setUpdatesEnabled。 需要注意,当使用setUpdatesEnabled(false)后,控件可能不会立即停止绘制,而是将多个更新请求合并,直到重新启用时进行一次绘制。因此,如果需要立即停止当前的绘制,可能需要结合其他方法。 总结可能的步骤: 1. 使用setUpdatesEnabled方法控制控件的更新。 2. 在paintEvent中使用条件判断跳过绘制代码。 3. 拦截或忽略QPaintEvent事件。 然后,根据用户的具体需求,选择合适的方法。如果用户只是想暂时停止界面刷新,比如在处理大量数据时避免频繁重绘,setUpdatesEnabled可能更合适。如果是想完全禁止某些绘制操作,使用标志变量更灵活。 此外,需要提醒用户,停止绘制更新可能导致界面看起来无响应,需要谨慎使用,并在适当的时候重新启用更新。</think>在Qt中实现主界面停止绘图更新,可以通过以下三种方法实现: --- ### 方法一:禁用控件更新(推荐) 使用`QWidget::setUpdatesEnabled()`系统级控制: ```cpp // 停止绘图更新 ui->widget->setUpdatesEnabled(false); // 恢复绘图更新 ui->widget->setUpdatesEnabled(true); ``` **特点**: - 立即停止所有新绘制请求 - 已排队的绘制事件会被合并 - 恢复时会触发一次完整重绘 - 不影响其他事件处理 --- ### 方法二:条件式绘制控制 在自定义控件中增加绘制开关: ```cpp // 自定义Widget.h class CustomWidget : public QWidget { Q_OBJECT public: void setPaintEnabled(bool enable) { m_paintEnabled = enable; update(); // 可选:触发状态更新 } protected: void paintEvent(QPaintEvent* event) override { if(!m_paintEnabled) return; // 正常绘制代码 QPainter painter(this); // ...绘制逻辑... } private: bool m_paintEnabled = true; }; ``` **优势**: - 细粒度控制绘制内容 - 可保留背景默认绘制 - 支持部分内容绘制 --- ### 方法三:事件过滤拦截 拦截QPaintEvent事件: ```cpp // 安装事件过滤器 ui->widget->installEventFilter(this); // 事件过滤函数 bool MainWindow::eventFilter(QObject* obj, QEvent* event) { if(obj == ui->widget && event->type() == QEvent::Paint) { if(shouldBlockPaint) { // 自定义拦截条件 return true; // 拦截绘制事件 } } return QObject::eventFilter(obj, event); } ``` **适用场景**: - 需要动态控制多个控件 - 无法修改控件源码时 - 系统级全局过滤 --- ### 注意事项 1. **性能优化**:长时间禁用绘制时,建议配合`QCoreApplication::processEvents()`保持响应 2. **线程安全**:GUI操作必须始终在主线程进行 3. **状态恢复**:重新启用后建议调用`update()`触发刷新 4. **视觉效果**:禁用期间控件可能显示空白或残留内容 --- ### 典型应用场景 ```cpp // 批量操作时优化性能 ui->widget->setUpdatesEnabled(false); for(int i=0; i<1000; i++){ updateData(i); // 数据更新 // 此处不会触发重复绘制 } ui->widget->setUpdatesEnabled(true); ui->widget->update(); // 最终统一刷新 ``` --- 根据具体需求选择实现方式: - **临时优化**:使用方法一 - **绘制可控**:方法二+方法一组合 - **系统级控制**:方法三 建议优先考虑`setUpdatesEnabled()`方案,这是Qt官方推荐的标准做法。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值