首页 > 其他分享 >2023/8/20读书笔记

2023/8/20读书笔记

时间:2023-08-20 23:33:45浏览次数:47  
标签:Widget 20 函数 读书笔记 void 线程 2023 ui include

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。首先先看文档 image.png

  1. 要想使用QtConcurrent必须要先在qmake中添加模块 QT += concurrent。所谓异步就是提供更加简单得多线程操作。 最重要的函数就是 image.png 使用 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

相关文章

  • [2023 上半年] [软件设计师] [下午题] 题解报告
    2023年下午题整体难度有所上升,取消了简单和困难难度,全部设置为中等难度。第一题数据流图随着农业领域科学种植的发展,需要对农业基地及农事进行信息化管理,为租户和农户等人员提供种植相关服务。现欲开发农事管理服务平台,其主要功能是:(1)人员管理。平台管理员管理租户;租户管理农户并......
  • day01-运维介绍与虚拟机安装-20230820
     1.解释我们正在使用哪些互联网行业的软件,移动端?PC端? (1)平台不一样视觉范围更广,可设计的地方更多,设计性更强,相对来说容错度更高一些。操作局限性大,在设计上可用空间显得尤为珍贵,避免原件过小过近。(2)操作系统不一样对于会员系统、视频和音乐、购物支付等功能都进行了精简,使......
  • [SWPUCTF 2021 新生赛]hardrce
    [SWPUCTF2021新生赛]hardrce题目来源:nssctf题目类型:web涉及考点:rec1.上来直接代码审计<?phpheader("Content-Type:text/html;charset=utf-8");error_reporting(0);highlight_file(__FILE__);if(isset($_GET['wllm'])){$wllm=$_GET['wllm'];......
  • CCPC 2023 网络赛 J. Find the gap 另(不可行)解
    题面\(n\)个三维点\((x_i,y_i,z_i)\),求两个距离最近的平行平面夹住所有点。输出距离。精度\(10^{-9}\)。\(1\len\le50,1\lex_i,y_i,z_i\le10^4\)。原题可行解两种case:答案平面平行于一个三点定平面;答案平面平行于一对异面直线的公共平行面(例如:正四面体)。前一种......
  • Photoshop2023(Beta) PS AI版本安装爱国使用教程
    所需准备creative-cloudAdobe-GenP开始什么是creative-cloud你可以把它当成苹果的AppStore或者安卓的PlayStore,这是Adobe自家的应该程序商店,商城,资源管理中心,可以下载Adobe的所有软件,也能购买相关服务。下载creative-cloud官网地址:https://creativecloud.adobe.com/app......
  • python学习日记 2023年8月20日
    fromPILimportImage##pipinstallpillowimportosim=Image.open('./1.jpg')w,h=im.sizeimage_row=3image_column=5names=os.listdir('./img_f')new_img=Image.new('RGB',(image_column*w,image_row*h))foryinra......
  • 【愚公系列】2023年08月 WPF控件专题 CheckBox控件详解
    (文章目录)前言WPF控件是WindowsPresentationFoundation(WPF)中的基本用户界面元素。它们是可视化对象,可以用来创建各种用户界面。WPF控件可以分为两类:原生控件和自定义控件。原生控件是由Microsoft提供的内置控件,如Button、TextBox、Label、ComboBox等。这些控件都是WPF中常见......
  • 20230819比赛
    T1无聊的草稿GMOJ1752Description图中有N个点,每两点间只有唯一的路径,对于这样一个给定的图,最大的“毛毛虫”会有多大。毛毛虫包含一条主链,毛毛虫中的节点,要不在主链上,要么和主链上某节点相邻,如下图所示有两只合法的毛毛虫,点数越多,毛毛虫越大。Input输入......
  • 闲话8.20
    今天真的摆了一天。上午jimmy让做一个S组模拟,当学考做的......
  • 2023-08-20:用go语言写算法。给定一个由'W'、'A'、'S'、'D'四种字符组成的字符串,长度一
    2023-08-20:用go语言写算法。给定一个由'W'、'A'、'S'、'D'四种字符组成的字符串,长度一定是4的倍数,你可以把任意连续的一段子串,变成'W'、'A'、'S'、'D'组成的随意状态,目的是让4种字符词频一样。返回需要修改的最短子串长度。完美走位问题。输入:s="QQQW"。输出:2。解释:我们......