QT事件机制
qt的事件机制是非常重要的,下面来慢慢解析。
C++程序
还记得第一个程序 hello world吗?如下
#include <iostream>
int main()
{
std::cout << "hello world" << std::endl;
return 0;
}
当执行时代码从上到下执行。然后程序就执行完毕了。
QT 窗口程序
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
最后有一个a.exec()
表示启动了QT的事件循环。当进入事件循环后,它就不断检查是否有事件发生并且把这些事件分发出去。比如一个按钮在鼠标移动上去时会有背景色的变化。或者你连接了按钮的点击信号到一个槽函数上。当你点击时就会触发槽函数。这都是因为Qt在获取到了用户的输入事件后将它分发给各个窗口的组件,然后组件在进行处理。
在GUI线程使用延时操作
在GUI线程使用延时操作会造成严重的后果,那就是界面无响应。因为GUI线程一个在处理耗时的操作没有去处理事件。导致界面不能正常地去响应信息,从而出现了无响应地情况,如下
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QThread>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
QThread::sleep(5);
qDebug() << "hello world";
}
Forces the current thread to sleep for secs seconds.
点击按钮后,你会发现鼠标移动界面完全没有效果,但是当睡眠时间结束后界面又移动了。从这里可以得出。虽然GUI线程被阻塞了但是并不影响程序去获取事件,也就是说程序有一个容器保存了事件,当GUI线程阻塞结束后,事件又被分发出去然后由对应地组件去执行。
QEventLoop
QEventLoop 的exec函数用于开启一个事件循环,很重要地一点就是在子事件循环中不会影响父事件循环,并且exec()之后的代码要在事件循环结束后才能够执行。所以我们可以很简单地实现一个效果就是,一个界面有一个按钮点击后10s钟打印hello world
。并且在此时间内不影响窗体的移动等。
void Widget::on_pushButton_clicked()
{
QEventLoop loop;
QTimer::singleShot(10000, &loop, &QEventLoop::quit);
loop.exec();
qDebug() << "hello World";
}
另外还有一个特别重要的函数如下
[static] void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents)
Processes all pending events for the calling thread according to the specified flags until there are no more events to process.
You can call this function occasionally when your program is busy performing a long operation (e.g. copying a file).
In the event that you are running a local loop which calls this function continuously, without an event loop, the DeferredDelete events will not be processed. This can affect the behaviour of widgets, e.g. QToolTip, that rely on DeferredDelete events to function properly. An alternative would be to call sendPostedEvents() from within that local loop.
Calling this function processes events only for the calling thread.
Note: This function is thread-safe.
See also exec(), QTimer, QEventLoop::processEvents(), flush(), and sendPostedEvents().
它就是立马去处理队列里面的事件,直到队列里面没有事件,也就是说对于一些耗时的操作我们可以在耗时的操作里合适的时机里面调用该函数让他去处理事件这样也可以避免界面出现无响应的情况。
void Widget::on_pushButton_clicked()
{
for(int i = 0; i < 1000; ++i){
for(int j = 0; j < 1000; ++j){
qDebug() << "hello";
qApp->processEvents();
}
}
}
这里还可以提一下 QEventLoop::ExcludeUserInputEvents
这个参数的意思是,让QT忽略 输入的事件。在这个例子中就是你可以移动窗体,但是不能能做出输入的操作
异步操作
在QT中真异步操作还是得看 QtConcurrent。首先先看文档
- 要想使用
QtConcurrent
必须要先在qmake中添加模块QT += concurrent
。所谓异步就是提供更加简单得多线程操作。 最重要的函数就是 使用 run 函数运行的函数,是在其它的线程中,这不就是多线程吗?对于有返回值的函数可以通过QFuture
来获取。这样对于简单的耗时操作我们就可以使用该类,而不是自己去创建一个线程去处理了,因为创建一个线程的代码还是挺多的。 也就是说所谓的异步就是,开另外一个线程去执行一些代码,当代码执行完毕,这个线程就结束了之类的。但是返回值还是可以获取的。 还有一点就是 QtConcurrent Namespace 它是一个命名空间,所以使用 run函数 需要进行如下使用
QtConcurrent::run(MyPrint);
使用参数和获取返回值。代码如下
void Widget::on_pushButton_clicked()
{
QString str = "hello";
QFuture<QString> future = QtConcurrent::run(strAdd,str);
QString result = future.result();
qDebug() << "the result is " << result;
qDebug() << QStringLiteral("立刻执行");
}
异步运行类的成员函数
void Widget::on_pushButton_clicked()
{
qDebug() << "UI : " << QThread::currentThread();
QtConcurrent::run(this, &Widget::print);
qDebug() << QStringLiteral("立刻执行");
}
突然发现在 QtConcurrent 中的run函数可以去更新UI,太厉害了,也就是说要想实现界面中展示一个数字不断更新的Label就非常简单了。
void Widget::print()
{
qDebug() << "print : " << QThread::currentThread();
for(int i = 0; i < 1000; ++i){
ui->label->setText(QString::number(i));
QThread::msleep(100);
}
}
void Widget::on_pushButton_clicked()
{
qDebug() << "UI : " << QThread::currentThread();
QtConcurrent::run(this, &Widget::print);
qDebug() << QStringLiteral("立刻执行");
}
QFutureWatcher
对于异步操作有返回值的函数,既然是异步我们当然希望获取异步操作函数的返回值时,不要阻塞代码的运行。但是对于QFuture
中的 result 函数
T QFuture::result() const
Returns the first result in the future. If the result is not immediately available, this function will block and wait for the result to become available. This is a convenience method for calling resultAt(0).
See also resultAt() and results().
也就是说它会阻塞代码的运行直到,异步函数的执行完毕。我们更加希望代码不阻塞,当异步函数执行完毕时有一个信号通知我们异步函数执行完毕了,此时可以去获取异步函数的返回值了。于是 QFutureWatcher
应运而生。如下
void Widget::on_pushButton_clicked()
{
// qDebug() << "UI : " << QThread::currentThread();
// QtConcurrent::run(this, &Widget::print);
// qDebug() << QStringLiteral("立刻执行");
QFutureWatcher<int> *watcher = new QFutureWatcher<int>;
connect(watcher, &QFutureWatcher<int>::finished, this, [&, watcher](){
qDebug() << "every : " << watcher->future().result();
watcher->deleteLater();
});
watcher->setFuture(QtConcurrent::run(this, &Widget::addNum));
}
几点要注意 : 1. 在这里 watcher 必须是new出来的因为是异步的原因如果 watcher 在栈上,那么 on_pushButton_clicked 执行完毕时 watcher对象就被析构了。2. 这里可以使用 deleteLater 来避免内存泄漏。3. 在使用connect 来连接 模板类 对象和信号时必须要指明类型。主要是在我目前的工作中 模板相关用的太少了。4.还有一点就是 watcher 的connect,必须放在 watcher setFuture前否则 QT会有报错信息。
QThread
终于来到多线程了,推荐博客 link 。QT使用多线程的常见的两种方式 1. 写一个派生QThread的类,重写它的 protect 虚函数 run。在这里面进行操作。子线程和GUI线程通过信号和槽进行传递信息。奇妙的是虽然博客里面写不能在子线程里面去操作ui控件之类的。但是经过我测试发现无论是使用QtConcurrent
还是使用QThread
的第一种方式。都是可以在子线程中刷新UI的。但是这肯定是不推荐的用法。
**注意 ** 使用多线程时应该注意,如当子线程还在运行中,用户关闭了主窗口(结束了程序)我们应该怎么处理,如果程序不加处理那么一般来说程序都会异常退出。所以我们的处理方式一般有以下几种,1. 等待子线程运行结束后在结束应用程序。如
#include "widget.h"
#include "ui_widget.h"
#include <QString>
#include <QDebug>
#include <QCloseEvent>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
thread = new MyThread(this);
thread->setLabel(ui->label);
connect(thread, &QThread::finished, this, [=](){
qDebug() << QStringLiteral("子线程运行完毕");
m_flag = true;
});
}
Widget::~Widget()
{
delete ui;
}
void Widget::slotNumber(int number)
{
ui->label->setText(QString::number(number));
}
void Widget::closeEvent(QCloseEvent *event)
{
Q_UNUSED(event);
if(m_flag == true)
return QWidget::closeEvent(event);
else
event->ignore();
//thread->terminate();
}
void Widget::on_pushButton_clicked()
{
qDebug() << "main thread : " << QThread::currentThread();
thread->start();
}
又或者在关闭应用程序时强制终结线程
void Widget::closeEvent(QCloseEvent *event)
{
thread->terminate();
}
Qthread 的第二种创建线程的方式
创建一个继承于 QObject的 类。在类中添加一个公共的成员函数,函数体就是我们要在子线程中执行的业务逻辑。然后在主线程中创建一个QThread对象,就是子线程的对象。在主线程创建工作对象(千万不要指定创建对象的父对象)。然后将MyWork对象移动到创建的子线程对象中,需要调用QObject类提供的movetoThread()方法。如果 工作对象指定了父对象那么此函数将会调用失败。将 QThread对象的 started 信号连接到工作对象的工作函数。连接后使用 QThread的start()接口,就可以启动线程了。一定要把 QThread对象的 started 信号连接到工作对象的工作函数后在使用 QThread start接口。否则子线程将不是运行在一个单独的线程上。代码如下
//main.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QString>
#include <QDebug>
#include <QCloseEvent>
#include <QThread>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
thread = new QThread;
work = new MyWork;
work->moveToThread(thread);
connect(work, &MyWork::sigNumber, this, &Widget::slotNumber);
connect(thread, &QThread::started, work, &MyWork::working);
}
Widget::~Widget()
{
delete ui;
}
void Widget::slotNumber(int number)
{
ui->label->setText(QString::number(number));
}
void Widget::on_pushButton_clicked()
{
qDebug() << "GUI thread : " << QThread::currentThread();
thread->start();
}
#include "MyWork.h"
#include <QThread>
#include <QDebug>
MyWork::MyWork(QObject *parent) : QObject(parent)
{
}
void MyWork::working()
{
qDebug() << "MyWork : " << QThread::currentThread();
for(int i = 0; i <= 1000; ++i){
emit sigNumber(i);
QThread::msleep(1);
if(i == 1000)
break;
}
}
标签:Widget,20,函数,读书笔记,void,线程,2023,ui,include
From: https://blog.51cto.com/u_15885923/7165018