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)介面即可,如圖:
流程概括如下:
在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);
}