前言
接触Qt早就有一段时间了,但是从来没有接触过Qt的多线程,但是这段时间接触过C/C++的多线程,就想尝试总结下三者实现的不同点,因此就想自己调试下《Qt 5.9 C++开发指南》中的demo,废话不多说,直接看操作。
测试环境
- Qt Creator 4.8.2
- Qt 5.9.8
- MSVC 2015,32bit
大纲
- 创建项目
- 界面搭建
- 多线程处理
- 信号处理
一、创建项目
- 打开Qt Creator,选择
新建文件或项目
,然后分别选择Application
->Qt Widgets Application
。
2. 配置项目介绍和位置
。
3. 选择自己的编译器。
4. 设置类信息,注意此处我们选择基类QDialog
对话框。
5. 不添加项目管理管理,直接完成即可。
6. 新建项目如下。
二、界面搭建
有些Qt基础的小伙伴可能对知道,一般我们的界面设计可以同行.ui
文件来使用Qt设计师
来实现,此处我们也这样做。我们需要处理的是左侧Forms
目录下的dialog.ui
文件,双击即可。
- 打开如下。
2. 在左侧的Containers
专栏中拖拽一个Group Box
到箭头方向,并查看最右侧的对象结构的改变。
3. 双击控制台中的GroupBox
改名为线程
,从左侧的layouts
中拖拽一个Horizontal Layout
到控制台中,如下所示:
4. 从Buttons
中拖拽5个Buttons
到控制台,并完成显示改名。
5. 我们将开始
、暂停
和结束线程
初始值设置为默认不可点击,此处只说一个,其他两个同理,我们单击控制台的开始
,查看程序的右下角,找到如下界面并取消enabled
中的对钩。
6. 在下面分别添加一个
Label
和Plain Text Edit
,并添加一个Horizontal Layout
包含它们,并查看目录结构是否和下图保持一致。
- 在最下面还添加一个
Label
改显示名称为Thread状态
,小细节(将长拖长些,之后要显示状态信息),完成右侧的对象改名,与下图保持一致。
注意:需要保证按钮改对象名与中文显示一致,这样是为了处理信号的时候不会出错。
由于换了个实现设备,图片和原先风格不一致,但是整个过程是完整的,请放心。
- 添加图片资源,点击
编辑
回到代码编辑区域,点击左侧项目目录结构处(文件夹ThreadSignal处)右击选择Add New...
。
9. 选择后,设置名字为res
,默认路径不变与项目根目录保持一致。下一步
后点击完成
。
10. 在左侧的目录结构中出现了Resources
目录中存在res.qrc
文件,右击Open in Editor
。添加前缀填写/dict
,添加文件指向项目的资源文件。
11. 点击上方的叉号,弹出关闭窗口选择Save all
。
12. 回到界面搭建
中的dialog.ui
文件,按下图序号依次进行点击。
13. 完成添加初始化图片,如下所示。
三、多线程处理
- 完成前面的步骤后点击下图的箭头标识处。注意此处一定要先运行,这是因为这样可以将
.ui
文件编译成C++
头文件对资源文件的定义,否则后面的代码编译器会标红。
2. 运行结果,可以看到Dialog
的默认title
没有修改,自己按之前的知识去修改,此处略过。
3. 新建一个qdicethread.h
和qdicethread.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++开发指南》