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)介面即可,如圖:

流程概括如下:

flowchart LR;
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);

}
Tags: