QT快速入門
QT快速入門
本文檔將介紹QT工程的創建、UI介面布局,並以計數器為例了解QT中多執行緒的用法,最終完成一個基礎的QT項目。
1 創建QT工程文件
在安裝好QT之後,能夠在其安裝組件中找到Qt Creator
,點擊

設置項目名稱及路徑等,設置支援32位與64位,其他都直接下一步;

創建完成,項目中包含以下幾個文件:
QT項目文件QTTEST.pro
,主窗口頭文件mainwindow.h
,主窗口程式mainwindow.cpp
,主函數main.cpp
以及窗口UI文件mainwindow.ui
。

我們當然可以直接在QT creator中編完這個工程,但推薦使用更加成熟、穩健的VS完成後續的編程與設計。
2 UI介面設計
首先,使用VS打開新建的.pro文件,同樣能看到這幾個文件。在一切正常的情況下,此時點擊運行程式會出現一個空白窗口(圖④)。

隨後根據需求設計窗體介面與布局,雙擊打開UI文件,默認UI操作介面如下:

其中控制項區域的所有組件為:

然後回到主介面,UI設計的主要思路為:將控制項從工具欄拖拽到主介面中,使介面能夠簡潔明了地反映工作流並回饋執行狀態;通過修改顯示名稱、對象屬性、訊號與槽函數使介面與背後的主程式鏈接。通常一個介面包括輸入、輸出、中間過程、計算、退出等操作,具體例子如下:

3 訊號與槽函數、連接
(1) 內置的連接方法
以上述介面為例,重點演示Exit功能以及Input data的瀏覽和單行輸入框。
對於最簡單的Exit,單擊(click)時只需執行關閉(close)介面即可,如圖:

流程概括如下:
Z(選中控制項)–>A;
A(修改屬性名)–>B(添加指定的訊號與槽);
B–>C(保存);
在QT設計師中添加的訊號與槽函數,只需通過簡單的點擊即可建立連接。其實質是:
在mainwindow.cpp
文件中,能夠看到#include "ui_mainwindow.h"
引用了這樣一個頭文件,打開之後,可以找到:
QObject::connect(ExitPushBotton, SIGNAL(clicked()), MainWindow, SLOT(close()));
其底層原理是通過connect
將單擊Exit按鈕這一訊號與關閉介面這個槽函數相關聯,訊號由按鈕PushBotton發射,主窗口MainWindow接收。
(2) 自定義槽函數
期望達到的效果如圖,在點擊Browse之後,我們能夠瀏覽文件目錄並將文件名、路徑填入到前面的文本框中。而在之後的操作中,可以直接從介面上獲取文件資訊。

分析這個步驟,即是在單擊(click)按鈕Browse後,彈出選擇文件/路徑的對話框,並將值傳給文本編輯框中顯示。
step1: 修改屬性名
命名的規則為:控制項功能+控制項名(如Inputdata+lineEdit),這是為了在後台調用控制項時能夠快速、準確定位。

step2: 編寫槽函數
在主窗口頭文件中聲明槽函數:
class MainWindow : public QMainWindow
{
Q_OBJECT
...
private slots:
//3個Browse對應的槽函數
void inputdataSelect(); //輸入文件選擇格式
void outputdataPathSelect(); //輸出文件路徑選擇
void waveletFileSelect(); //子波文件選擇
};
在mainwindow.cpp文件中定義槽函數:
// 以輸入和輸出兩個為例,其他的瀏覽可類推
#include <QFileDialog> //需引入QFileDialog頭文件才能使用對話窗口選擇文件
void MainWindow::inputdataSelect() { //輸入數據路徑及文件名選擇
// 文件名將存為QString字元串格式
QString fileNameInput = QFileDialog::getOpenFileName(this, //getOpenFileName獲取文件名
tr("Input File"),
"F:", // 默認啟動位置為F盤
tr("Seismic(*sgy *segy *SEGY);;")); //創建文件名及路徑選擇對話窗口、支援的格式為segy
if (fileNameInput.isEmpty() == false) {
ui->InputdatalineEdit->setText(fileNameInput); //將選擇輸入數據的文件名路徑填入文本框
}
}
void MainWindow::outputdataPathSelect(){ //輸出數據的路徑選擇
QString fileNameInputPath = QFileDialog::getExistingDirectory(this, //getExistingDirectory獲取路徑
tr("Select Output File Folder"),
tr("C:")); //讀取輸出文件保存路徑,只有路徑因此無需預設文件格式
if (fileNameInputPath.isEmpty() == false) {
ui->OutputlineEdit->setText(fileNameInputPath); //顯示選擇保存路徑
}
}
使用ui->xxx
可以調取介面中的控制項,如ui->InputdatalineEdit
則會指定到主窗口中的第一個文本框,這裡的命名為step1中修改後的屬性名,可從.ui介面中複製粘貼。
step3:建立連接
在UI介面中建立clicked()與我們自定義的槽函數的連接,保存後,重新運行程式,即可實現上述功能。

(3)自定義訊號與槽函數
演算法計算(Compute)是這個QT工程的核心,按照同樣的方法將介面上的Compute按鈕與compute()槽函數連接;
而在計算過程中,我們希望能夠顯示進度並列印日誌,這部分通常沒有固定的連接,因此需要自定義發射訊號與接收槽函數。在展開介紹Compute的實現過程之前,再次強調以下Qt中使用多執行緒的注意事項:
- 默認的執行緒在Qt中稱之為窗口執行緒,也就叫主執行緒,負責窗口事件處理或者窗口控制項數據的更新;
- 子執行緒負責後台的業務邏輯處理,子執行緒中不能對窗口對象做任何操作,這些事情需要交給窗口執行緒處理;
- 主執行緒和子執行緒之間如果要進行數據的傳遞,需要使用Qt中的訊號槽機制
- 子執行緒一般不允許越級進行對窗口參數進行操作
簡單來說,當我們需要執行計算處理,並同步更新結果到窗口時,如果只使用一個執行緒,會出現窗口卡頓的情況。於是,我們將計算、處理放在子執行緒中,計算的中間過程與結果通過訊號槽機制傳遞到主執行緒進行顯示。
①mainwindow.h
- 創建一個子執行緒類,它繼承自
QThread
,通過在protected
成員方法中重新實現run()
; - 在主執行緒(Mainwindow)中聲明:子執行緒成員,以及接收訊號的主窗口上的槽函數。
#include <QThread>
#include <QProgressDialog>
/****************** 子執行緒--發射端 *****************/
class MyThread :public QThread { //MyThread子類繼承自QThread
Q_OBJECT
public:
MyThread() {
}
~MyThread() {
}
protected: // 受保護的成員
void run() { //重寫run()方法,此處的方法為一個間隔0.1s從0~100的計數器
for (int i = 0; i < 101; i++) {
emit SendNumber(i); //使用emit發射子執行緒中的訊號SendNumber。它將傳遞出當前的實參:一個1~100之間的整數
//emit是一個宏定義,本質上會在moc_*.cpp文件中生成一個SendNumber()訊號
//QT內部進行調用時,會找到底層的相應程式碼並進行訊號與槽函數的連接
msleep(100); //停滯0.1s
}
}
signals:
void SendNumber(const int nNum); //在類中聲明訊號函數
};
/****************** 主執行緒--主窗口--接收端 *****************/
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
MyThread *_mthread; //在主窗口中聲明一個子執行緒成員
private:
Ui::MainWindow *ui;
private slots: //聲明私有的槽函數
void compute(); //compute功能函數
void updateProgress(int iter); //刷新進度條
void printlog(int num); //列印日誌
};
②mainwindow.cpp
首先在compute訊號中啟動子執行緒;
void MainWindow::compute(){
_mthread->start(); //使用start()啟動子執行緒
}
然後定義主執行緒中的槽函數方法;
void MainWindow::updateProgress(int iter) { //Qt設置進度條的槽函數
ui->computeprogressBar->setValue(iter);
}
void MainWindow::printlog(int num) { //Qt列印計算日誌
QString qs = "Process:";
QString q1;
q1 = q1.sprintf("%d", num);
qs = qs + q1;
ui->computeLogTextBrowser->append(tr("Reading Data Suceesed!"));
ui->computeLogTextBrowser->append(qs);
}
最後在主窗口中連接子執行緒訊號與槽函數。
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->_mthread = new MyThread(); //實例化一個子執行緒對象
// 從子執行緒_mthread發送一個訊號SendNumber
// 由主執行緒(this)的槽函數updateProgress與printlog接收
connect(_mthread, &MyThread::SendNumber, this, &MainWindow::updateProgress);
connect(_mthread, &MyThread::SendNumber, this, &MainWindow::printlog);
}
關於connect官方給出的文檔中包含5個參數,具體如下:
QObject::connect(const QObject *sender, // 發送者
const char *signal, // 訊號
const QObject *receiver, // 接收者
const char *method, // 接收的槽函數
Qt::ConnectionType type) /* 連接方式
Qt::ConnectionType type = Qt::AutoConnection (1)默認連接
Qt:: DirectConnection (2)立即調用
Qt::QueuedConnection (3)非同步調用
Qt::BlockingQueuedConnection (4)同步調用
Qt:: UniqueConnection (5)單一連接 */
完善Compute,在讀取文件名稱、路徑中加入判空:
if(filenameInput.isEmpty()==true){ //如果輸入文件名為空
QMessageBox msgbox;
msgbox.setText("no select input data");
msgbox.exec();
return;
}
if(filenameOutputPath.isEmpty() == true) { //如果輸出路徑為空
QMessageBox msgbox;
msgbox.setText("no select output data path");
msgbox.exec();
return;
}
if (filenameOutput.isEmpty() == true){ //如果輸出文件名為空
QMessageBox msgbox;
msgbox.setText("no input output data name");
msgbox.exec();
return;
}
最終效果:

完整程式碼
Ⅰ mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QFileDialog>
#include <QProgressDialog>
#include <QDebug>
#include <QMessageBox>
#include <QThread>
class MyThread :public QThread {
Q_OBJECT
public:
MyThread() {
}
~MyThread() {
}
protected:
void run() {
for (int i = 0; i < 101; i++) {
emit SendNumber(i);
msleep(100);
}
}
signals:
void SendNumber(const int nNum);
};
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
MyThread *_mthread;
private:
Ui::MainWindow *ui;
private slots:
void inputdataSelect(); //輸入文件選擇格式
void outputdataPathSelect(); //輸出文件路徑選擇
void waveletFileSelect(); //子波文件選擇
void compute(); //計算函數
void updateProgress(int iter); //刷新進度條
void printlog(int num); //列印日誌
};
#endif // MAINWINDOW_H
Ⅱ mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->_mthread = new MyThread();
connect(_mthread, &MyThread::SendNumber, this, &MainWindow::updateProgress);
connect(_mthread, &MyThread::SendNumber, this, &MainWindow::printlog);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::inputdataSelect(){ //輸入數據路徑及文件名選擇
QString fileNameInput = QFileDialog::getOpenFileName(this,
tr("Input File"),
"F:",
tr("Seismic(*sgy *segy *SEGY);;")); //創建文件名及路徑選擇對話窗口
if (fileNameInput.isEmpty() == false) {
ui->InputdatalineEdit->setText(fileNameInput); //將選擇輸入數據的文件名路徑
qDebug() << "filename : " << fileNameInput;
}
else {
}//end if(fileNameInput.isEmpty()==false)
}
void MainWindow::outputdataPathSelect(){ //輸出數據的路徑選擇
QString fileNameInputPath = QFileDialog::getExistingDirectory(this,
tr("Select Output File Folder"),
tr("C:")); //讀取輸出文件保存路徑
if (fileNameInputPath.isEmpty() == false) {
ui->OutputlineEdit->setText(fileNameInputPath); //顯示選擇保存路徑
}
else {
}//end if(fileNameInputPath.isEmpty()==false)
}
void MainWindow::waveletFileSelect(){
QString fileNameInput = QFileDialog::getOpenFileName(this,
tr("Input File"),
"F:",
tr("wavelet file(*dat *txt);;")); //創建文件名及路徑選擇對話窗口
if (fileNameInput.isEmpty() == false){
ui->waveletFileNamelineEdit->setText(fileNameInput); //將選擇輸入數據的文件名路徑
qDebug() << "filename : " << fileNameInput;
}
}
void MainWindow::compute(){
QString filenameInput = ui->InputdatalineEdit->text(); //從介面獲取輸入模型的SEGY文件
QString filenameOutputPath = ui->OutputlineEdit->text(); //從介面獲取輸出模型的SEGY文件文件路徑
QString filenameOutput = ui->outputDataFilenamelineEdit->text(); //從介面獲取輸出數據的SEGY的文件名
if(filenameInput.isEmpty()==true){
QMessageBox msgbox;
msgbox.setText("no select input data");
msgbox.exec();
return;
}
if(filenameOutputPath.isEmpty() == true) {
QMessageBox msgbox;
msgbox.setText("no select output data path");
msgbox.exec();
return;
}
if (filenameOutput.isEmpty() == true){
QMessageBox msgbox;
msgbox.setText("no input output data name");
msgbox.exec();
return;
}
_mthread->start();
}
void MainWindow::updateProgress(int iter) { //Qt設置進度條的槽函數
ui->computeprogressBar->setValue(iter);
}
void MainWindow::printlog(int num) { //Qt列印計算日誌
QString qs = "Process:";
QString q1;
q1 = q1.sprintf("%d", num);
qs = qs + q1;
ui->computeLogTextBrowser->append(tr("Reading Data Suceesed!"));
ui->computeLogTextBrowser->append(qs);
}