signed

QiShunwang

“诚信为本、客户至上”

菜鸟手把手实现“掷骰子的多线程应用程序“,看不懂太打我

2021/6/24 22:57:35   来源:

前言

接触Qt早就有一段时间了,但是从来没有接触过Qt的多线程,但是这段时间接触过C/C++的多线程,就想尝试总结下三者实现的不同点,因此就想自己调试下《Qt 5.9 C++开发指南》中的demo,废话不多说,直接看操作。

测试环境

  • Qt Creator 4.8.2
  • Qt 5.9.8
  • MSVC 2015,32bit

大纲

  • 创建项目
  • 界面搭建
  • 多线程处理
  • 信号处理

一、创建项目

  1. 打开Qt Creator,选择新建文件或项目,然后分别选择Application->Qt Widgets Application

在这里插入图片描述
2. 配置项目介绍和位置

在这里插入图片描述
3. 选择自己的编译器。

在这里插入图片描述
4. 设置类信息,注意此处我们选择基类QDialog对话框。

在这里插入图片描述
5. 不添加项目管理管理,直接完成即可。

在这里插入图片描述
6. 新建项目如下。

在这里插入图片描述

二、界面搭建

有些Qt基础的小伙伴可能对知道,一般我们的界面设计可以同行.ui文件来使用Qt设计师来实现,此处我们也这样做。我们需要处理的是左侧Forms目录下的dialog.ui文件,双击即可。

  1. 打开如下。

在这里插入图片描述
2. 在左侧的Containers专栏中拖拽一个Group Box到箭头方向,并查看最右侧的对象结构的改变。

在这里插入图片描述
3. 双击控制台中的GroupBox改名为线程,从左侧的layouts中拖拽一个Horizontal Layout到控制台中,如下所示:

在这里插入图片描述
4. 从Buttons中拖拽5个Buttons到控制台,并完成显示改名。

在这里插入图片描述
5. 我们将开始暂停结束线程初始值设置为默认不可点击,此处只说一个,其他两个同理,我们单击控制台的开始,查看程序的右下角,找到如下界面并取消enabled中的对钩。

在这里插入图片描述6. 在下面分别添加一个LabelPlain Text Edit,并添加一个Horizontal Layout包含它们,并查看目录结构是否和下图保持一致。
在这里插入图片描述

  1. 在最下面还添加一个Label改显示名称为Thread状态,小细节(将长拖长些,之后要显示状态信息),完成右侧的对象改名,与下图保持一致。
    注意:需要保证按钮改对象名与中文显示一致,这样是为了处理信号的时候不会出错。

在这里插入图片描述


由于换了个实现设备,图片和原先风格不一致,但是整个过程是完整的,请放心。

  1. 添加图片资源,点击编辑回到代码编辑区域,点击左侧项目目录结构处(文件夹ThreadSignal处)右击选择Add New...

在这里插入图片描述
9. 选择后,设置名字为res,默认路径不变与项目根目录保持一致。下一步后点击完成

在这里插入图片描述
10. 在左侧的目录结构中出现了Resources目录中存在res.qrc文件,右击Open in Editor。添加前缀填写/dict,添加文件指向项目的资源文件。

在这里插入图片描述
在这里插入图片描述
11. 点击上方的叉号,弹出关闭窗口选择Save all

在这里插入图片描述
12. 回到界面搭建中的dialog.ui文件,按下图序号依次进行点击。

在这里插入图片描述
13. 完成添加初始化图片,如下所示。

在这里插入图片描述

三、多线程处理

  1. 完成前面的步骤后点击下图的箭头标识处。注意此处一定要先运行,这是因为这样可以将.ui文件编译成C++头文件对资源文件的定义,否则后面的代码编译器会标红。

在这里插入图片描述
2. 运行结果,可以看到Dialog的默认title没有修改,自己按之前的知识去修改,此处略过。

在这里插入图片描述
3. 新建一个qdicethread.hqdicethread.cpp文件完成对掷骰子的线程类定义。在项目结构处(文件夹ThreadSignal处)右击Add New...,添加一个C++ class

在这里插入图片描述
4. 命名如下,下一步完成

在这里插入图片描述

以下为对应文件的源代码,自己敲。

qdicethread.h

#ifndef QDICETHREAD_H
#define QDICETHREAD_H

#include <QThread>

class QDicethread : public QThread
{
    Q_OBJECT
private:
    int m_seq = 0;          //掷骰子(zhì tóu zǐ)次数序号
    int m_diceValue;        //骰子点数
    bool m_Paused = true;   //暂停
    bool m_stop = false;    //停止
protected:
    void run() Q_DECL_OVERRIDE; //线程任务
public:
    QDicethread();
    void diceBegin();       //掷一次骰子
    void dicePause();       //暂停
    void stopThread();      //结束线程
signals:
    void newValue(int seq,int diceValue);   //产生新点数的信号
};

#endif // QDICETHREAD_H

qdicethread.cpp

#include "qdicethread.h"
#include <QTime>

QDicethread::QDicethread()
{//构造函数

}

void QDicethread::diceBegin()
{//开始掷骰子
    m_Paused = false;
}

void QDicethread::dicePause()
{//暂停掷骰子
    m_Paused = true;
}

void QDicethread::stopThread()
{//停止线程
   m_stop = true;
}

void QDicethread::run()
{//线程任务
   m_stop = false;  //启动线程时令m_stop=false
   m_seq = 0;       //掷骰子次数
   qsrand(QTime::currentTime().msec());
   while(!m_stop)   //循环主体
   {
       if(!m_Paused)
       {
           m_diceValue = qrand();
           m_diceValue = (m_diceValue % 6) + 1;
           m_seq ++;
           emit newValue(m_seq,m_diceValue);    //发射信号
       }
       msleep(500);     //线程休眠500ms
    }
   quit();  //相当于exit(0)
}

四、信号处理

主要是处理QDialog按钮事件和子线程连接的业务逻辑处理。
dialog.h

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
#include "qdicethread.h"

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
    Q_OBJECT
private:
    QDicethread threadA;
protected:
    void    closeEvent(QCloseEvent *event);
public:
    explicit Dialog(QWidget *parent = nullptr);
    ~Dialog();
private slots:
//自定义槽函数
    void onthreadA_started();
    void onthreadA_finished();
    void onthreadA_newValue(int seq,int diceValue);

    void on_btnClear_clicked();

    void on_btnDiceEnd_clicked();

    void on_btnDiceBegin_clicked();

    void on_btnStopThread_clicked();

    void on_btnStartThread_clicked();
private:
    Ui::Dialog *ui;
};

#endif // DIALOG_H

dialog.cpp

#include "dialog.h"
#include "ui_dialog.h"
#include <QDebug>

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{//构造函数
    ui->setupUi(this);
    connect(&threadA,SIGNAL(started()),this,SLOT(onthreadA_started()));
    connect(&threadA,SIGNAL(finished()),this,SLOT(onthreadA_finished()));
    connect(&threadA,SIGNAL(newValue(int,int)),this,SLOT(onthreadA_newValue(int,int)));
}

Dialog::~Dialog()
{
    delete ui;
}

void Dialog::onthreadA_started()
{//线程的started()信号的响应槽函数
    ui->LabA->setText("Thread状态:thread started");
}
void Dialog::onthreadA_finished()
{//线程的finished()信号的响应槽函数
    ui->LabA->setText("Thread状态:thread finished");
}

void Dialog::onthreadA_newValue(int seq,int diceValue)
{//QDiceThred的newvalue()信号的响应槽函数,显示骰子次数和点数
    QString str = QString::asprintf("第%d次掷骰子,点数为:%d",seq,diceValue);
    ui->plainTextEdit->appendPlainText(str);
    QPixmap pic;    //图片显示
    QString filename = QString::asprintf(":/dice/images/d%d.jpg",diceValue);
    qDebug()<<filename<<endl;
    pic.load(filename);
    ui->LabPic->setPixmap(pic);
}

//处理窗口中5个按钮的代码
//默认on_对象名_clicked()
void Dialog::on_btnStartThread_clicked()
{//启动线程 按钮
    threadA.start();
    ui->btnStartThread->setEnabled(false);  //启动按钮不能点击
    ui->btnStopThread->setEnabled(true);    //开始线程
    ui->btnDiceBegin->setEnabled(true);     //开始掷骰子
    ui->btnDiceEnd->setEnabled(false);      //结束掷骰子
}

void Dialog::on_btnStopThread_clicked()
{//结束线程 按钮
    threadA.stopThread();                   //结束线程的run()函数执行
    threadA.wait();                         //阻止线程执行,直到线程结束(从run()函数返回),或等待时间超过time毫秒,x相当于join()
    ui->btnStartThread->setEnabled(true);   //可以开始线程
    ui->btnStopThread->setEnabled(false);   //不能结束线程
    ui->btnDiceBegin->setEnabled(false);    //不能开始掷骰子
    ui->btnDiceEnd->setEnabled(false);      //不能结束掷骰子
}

void Dialog::on_btnDiceBegin_clicked()
{//开始 掷骰子按钮
    threadA.diceBegin();
    ui->btnDiceBegin->setEnabled(false);    //
    ui->btnDiceEnd->setEnabled(true);
}

void Dialog::on_btnDiceEnd_clicked()
{//暂停 掷骰子按钮
    threadA.dicePause();
    ui->btnDiceBegin->setEnabled(true);
    ui->btnDiceEnd->setEnabled(false);
}

void Dialog::on_btnClear_clicked()
{//清空文本 按钮
    ui->plainTextEdit->clear();
}

//重载closeEvent()事件,在窗口关闭时确保线程被停止
void Dialog::closeEvent(QCloseEvent *event)
{//窗口关闭事件,必须结束线程
   if(threadA.isRunning())
   {
    threadA.stopThread();
    threadA.wait();
   }
   event->accept();
}

后记

如若有小伙伴对中间部分存在疑惑,咱们可以商讨一帆。

资料

《Qt 5.9 C++开发指南》