背景描述
有耗时的操作需要放到子线程中处理,但是此过程中不允许界面执行其他操作,不能使用阻塞界面的模态对话框实现。
可以通过QEventLoop在子线程执行的同时开启一个事件循环,使UI界面不卡顿,能够正常响应。但是这种不卡顿只是鼠标滑动时UI可以流畅的变化,界面没有变成卡顿状态,此时不会执行鼠标点击或者键盘输入等用户输入的事件,否则会影响子线程中正在执行的操作。
QEventLoop说明
1.exec函数
int QEventLoop::exec(QEventLoop::ProcessEventsFlags flags = AllEvents)
Enters the main event loop and waits until exit() is called.
进入主事件循环并等待,直到exit()被调用。
If flags are specified, only events of the types allowed by the flags will be processed.
如果指定了标志,则只处理标志允许的类型的事件。
It is necessary to call this function to start event handling.
必须调用此函数来启动事件处理。
The main event loop receives events from the window system and dispatches these to the application widgets.
主事件循环从窗口系统接收事件,并将这些事件分发给应用程序小部件。
Generally speaking, no user interaction can take place before calling exec().
一般来说,在调用exec()之前不能进行任何用户交互。
As a special case, modal widgets like QMessageBox can be used before calling exec(), because modal widgets use their own local event loop.
作为一种特殊情况,像QMessageBox这样的模态小部件可以在调用exec()之前使用,因为模态小部件使用它们自己的本地事件循环。
参数 | 描述 |
---|---|
QEventLoop::AllEvents | 所有事件。注意,对DeferredDelete事件进行了特殊处理。参见QObject::deleteLater()了解更多细节。 |
QEventLoop::ExcludeUserInputEvents | 不处理用户输入事件,如ButtonPress和KeyPress。但是请注意,事件不会被丢弃;它们将在下一次不带excludeuserinputtevents标志的processEvents()被调用时传递。 |
QEventLoop::ExcludeSocketNotifiers | 不处理套接字通知事件。请注意,事件不会被丢弃;它们将在下一次不带ExcludeSocketNotifiers标志的processEvents()被调用时传递。 |
QEventLoop::WaitForMoreEvents | 如果没有挂起的事件可用,则等待事件。 |
通过exec函数开始事件循环,此函数默认参数是处理所有事件,使用其它参数也不会丢弃用户输入事件,如果在时间循环期间用户点击了按钮,在事件循环结束后就会执行点击按钮的操作,所以此时还无法做到过滤用户输入,需要添加额外处理。
使用事件过滤器过滤用户输入
- 给应用程序对象的全局指针qApp安装事件过滤器
- 使用eventloop来监听应用程序的所有事件,重写eventloop的eventFilter函数从而实现过滤某些输入事件的效果
- 退出事件循环后,回收eventloop对象,移除事件过滤器
eventFilter函数说明
virtual bool eventFilter(QObject *watched, QEvent *event);
- 如果返回true,表示事件过滤,不会发送到对象本身
- 如果返回false,表示事件未过滤,会通过event()方法将事件分发到对象。
- 返回给基类进行处理,例:return QObject::eventFilter(obj, event)。
代码实现
继承QEventLoop类,重写exec,eventFilter,deleteLater函数
// 头文件
#include <QEventLoop>
class CCustomEventLoop : public QEventLoop
{
Q_OBJECT
public:
CCustomEventLoop(QObject* parent = nullptr);
~CCustomEventLoop();
int exec(ProcessEventsFlags flags = AllEvents);
void deleteLater();
virtual bool eventFilter(QObject*, QEvent* event);
private:
bool m_bUiThread = false;
};
// 源文件
#include "customeventloop.h"
#include <QCoreApplication>
#include <QEvent>
#include <QThread>
CCustomEventLoop::CCustomEventLoop(QObject* parent) : QEventLoop(parent)
{
}
CCustomEventLoop::~CCustomEventLoop()
{
}
int CCustomEventLoop::exec(ProcessEventsFlags flags)
{
//判断当前线程是否为UI线程,此时才需要安装事件过滤器
if (qApp->thread() == QThread::currentThread())
{
// 给qApp安装事件过滤器,eventloop对象监听应用程序的所有事件
qApp->installEventFilter(this);
m_bUiThread = true;
}
return QEventLoop::exec(flags);
}
void CCustomEventLoop::deleteLater()
{
if (m_bUiThread)
{
// 回收eventloop对象时移除qApp的事件过滤器
qApp->removeEventFilter(this);
}
return QEventLoop::deleteLater();
}
bool CCustomEventLoop::eventFilter(QObject* , QEvent* event)
{
// 过滤用户输入事件:鼠标、键盘、快捷键
if (event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease ||
event->type() == QEvent::MouseButtonDblClick ||
event->type() == QEvent::KeyPress ||
event->type() == QEvent::KeyRelease ||
event->type() == QEvent::Shortcut ||
event->type() == QEvent::ShortcutOverride ||
event->type() == QEvent::FocusIn ||
event->type() == QEvent::FocusOut ||
event->type() == QEvent::Wheel)
{
return true;
}
return false;
}
使用方法
CCustomEventLoop *eventLoop = new CCustomEventLoop();
QtConcurrent::run([&]()mutable{
// 1. 执行耗时操作
...
// 2. 执行完耗时操作后退出事件循环
eventLoop->exit();
});
eventLoop->exec(); // 程序会在此处阻塞,直到执行exit()才会继续执行。UI不会卡顿。
eventLoop->deleteLater();
eventLoop = nullptr;
// 继续执行其他操作...