目錄
1.什么是QT
2.環境搭建
QT SDK的下載
QT的使用
QT構建項目
快捷指令
QT的簡單編寫
?對象樹
編碼問題
組件
初識信號槽
窗口的釋放
窗口坐標體系
1.什么是QT
QT?是一個跨平臺的 C++ 圖形用戶界面庫,支持多個系統,用于開發具有圖形界面的應用程序。它由挪威公司 TrollTech(現為 Digia 的一部分)出品,后來更名為 The Qt Company。QT 不僅僅是一個圖形用戶界面庫,它還包含用于網絡通信、線程、數據庫、正則表達式處理、XML 處理、JSON、國際化、音頻和視頻處理等的庫。
對于GUI的開發方式,QT只是其中一種,由于GUI也大多應用在客戶端,而不是服務器,因此主要是在windows下環境的開發,當然也有許多GUI的開放方式:
基于c/c++的:
1.首先就是比較古老的,windows提供的原生API, Windows API,開發較為繁瑣。
2.利用面向對象思想封裝成類--MFC就是其中之一,由微軟公司提供的GUI開發庫。
3.同時代也誕生出了QT,也是利用了面向對象的思想,但是QT一直在推成出新,一直在更新,但是MFC已經很久不更新了。
后來微軟自己又推出了新的體系例如c# (.net)下的 Windows Forms,之后又升級成了WPF,UWP.
2.環境搭建
無論是哪一種開發工具,我們都需要準備三件事:
1.下載c/c++的編譯器(gcc,cl.exe)。
2.QT SDK(qt軟件開發工具包的安裝),不過一般SDK會內置一個C++的編譯器(mingw下的gcc)。
3.集成開發環境VS (比較重量級,功能更多,但需要額外配置,初學不建議),QTcreater(有些許bug,不過上手方便好用,適合初學),Eclipse等(生態有限)。
不過現在對于這三個東西,你安裝QTcreator就已經足夠了。
QT SDK的下載
首先我們去官方下載qt sdk,官網點擊這里:Index of /archive/qt,選擇一個較新的版本進行安裝例如5.14進入文件夾內,2.6G的這個大小的就是我們qt的sdk,選擇合適的系統安裝。
國外網站下載比較慢,所以下載是比較慢的。安裝之后就點擊一路next基本上就可以了。
其中選擇安裝的組件,windos下我們就用Mingw--與QT creater就可以,如果你使用VS進行開發,那么可以安裝Msvc.最后配置一下環境變量,后面的操作就比較方便。
打開文件會發現關于QT的又許多個文件,其中QT Assistance是官方文檔,QT designer是支持拖拽式的圖形界面開發,linguist QT語言家,支持國際化。我們主要使用的是QT creater集成開發環境。
QT的使用
使用方法和VS類似,我們首先創建項目,在項目里可以看見左側有許多,開發GUI我們就使用application,選擇了左邊,右邊也有許多開發選項,選擇wigets applicatiion(圖形化界面模板)。
此時還要進行構建項目的配置,第一個選擇路徑,其次還有配置構建工具這里就是用qmake,這也是老牌的實用的功能工具了。
之后就是關于文件的設置。
首先使用QT創建項目會創建一些代碼,這些代碼會包含一個類,而這里的Base class就是選擇該類的父類,包括有三個類(QMianWindow? 完整的應用程序窗口,Qwidgets表示一個控件,即窗口上的一個部件? Qdialog表示一個對話框的開發)。當前學習我們就使用第二個Qwidgets即可。
然后選擇翻譯,這個我們不用關注,之后就是構建套件,選擇哪一個編譯器,如果你之前電腦還安裝了VS這樣的,你會發現,這里有許多編譯器,這里我們就使用當前的MingW即可。最后匯總選擇NULL即可。之后就為我們生成了整個項目(包括頭文件,源文件,form文件等)。
對于左下角我們可以進行運行調試。構建的項目中的文件,以及調用的類等QT creator都會幫我們創建好.
QT構建項目
簡單的helloworld標簽的編寫,兩種方式實現:
第一種方式,直接通過圖形化組件的拖拽實現,雙擊widget.ui進入面板設計,選擇Display widget中的lable標簽,此時QT Deigner右上角中會出現一個樹形的你的組件,此時的ui文件會多出一部分代碼代表這個組件,進一步的qmake在編譯項目的時候,基于該部分生成一段c++代碼。
此時我們重新運行就可以看到發生了變化。在我們的項目路徑下的buildXXXX這個文件中的ui_widget.h,也就是我們的父類文件,在這里面生成了對應的代碼:
第二種方式寫代碼的方式
首先重新創建一個項目,一般我們在添加組件的代碼放在.cpp的父類中的構造函數中
在此時前,我們需要直到每創建了一個組件就需要你去包含該組件名對應的頭文件,創建組件時使用堆棧都可以,建議用堆,同時參數為this,指定父對象(因為對象樹)。
對象樹:QT中時使用對象樹將不同組件連接在一起的
由于QT開發的比較早,對于一些基礎類型,用那時候的c/c++的感覺都不好,因此QT自己搞了一套輪子,自己重新的將基本數據類型重新封裝了支持QT的順暢開發如(Qstring ,Qlist,Qvector,Qdict...)。不過后期c++發展的比較好,都比較完善了,但QT也不可能把自己的刪了,所以其實這兩套用法作用其實差不多。
因此開發過程中這兩種容器都可以,但QT的一些原生接口使用的是QT自己的類型。
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//創建標簽對象QLabel * lable=new QLabel(this);//設置文本內容lable->setText("hello world");
}
根據這個簡單代碼我們基本了解的QT。
快捷指令
Qt Creator 中的快捷鍵? 注釋:ctrl + /? 運?:ctrl + R? 編譯:ctrl + B? 字體縮放:ctrl + ?標滑輪? 查找:ctrl + F? 整?移動:ctrl + shift + ?/?? 幫助?檔:F1? ?動對?:ctrl + i;? 同名之間的 .h 和 .cpp 的切換:F4? ?成函數聲明的對應定義: alt + enter使?幫助?檔打開幫助?檔有三種?式. 實際編程中使?哪種都可以.1、光標放到要查詢的類名/?法名上, 直接按 F12、Qt Creator 左側邊欄中直接??標單擊 "幫助" 按鈕
QT的簡單編寫
?對象樹
對于以上代碼,有的同學可能會有疑問,為什么沒有釋放,這是因為在創建的時候,我們將它掛到了對象樹上,調用了析構釋放掉了。
QT通過對象樹將各個組件關聯起來,以N叉樹的結構連接起來,對象樹也會將這些組件統一釋放掉。例如假想一下,如果我們項目的父類是widget,那么在其他組件創建時,都需要傳widget的指針給這些對象連接,此時就是將widgt作為對象樹root,其他組件為子節點,即將這些組件添加到主窗口上,當窗口關閉,調用窗口析構時在調用組件析構,而不需要我們手動釋放。
其次因為我們是界面開發,因此不能存在窗口還在,但是組件已經被釋放掉了,而是當窗口關閉時,統一釋放所有組件,隨意在棧上創建的,也就存在提前釋放的情況。我們可以通過自己創建一個標簽類,析構中打印信息,此時再去創建,關閉窗口就會自動調用析構:
#include "mylable.h"
#include<iostream>
myLable::myLable(QWidget * parent):QLabel(parent)
{}
myLable::~myLable()
{std::cout<<" 你好"<<std::endl;
}//widget.cpp的構造
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);myLable * lable=new myLable(this);lable->setText(" hello world");
}
編碼問題
由于QT的控制臺默認編碼格式是gbk,而QT creator編寫代碼的格式為utf-8,所以會出現編碼不一致的問問題,因此我們可以在高級里修改QT的編碼格式且按照當前編碼設置為gbk可以解決該問題。除此之外,QT提供了Qstring可以進行編碼格式的修改,且也提供了專門打印日志的工具? ? ? ? Qdebug可以用來打印日志。
組件
除了基本父類組件 Qwidget(繼承的子類Widget) 一個簡單窗口,QMainWindow(MainWindow) 包含菜單欄,工具欄的多個組建的窗口,QDialog(QDialog)--對話框。
剩下的就是與窗口齊平的組件,這些組件一般需要傳入父類指針實現組件的綁定其中包括:
初識信號槽
信號槽(Signal-Slot mechanism)是一種在軟件開發中常用的設計模式,特別是在Qt框架中,它用于實現對象間的通信和事件處理。以下是關于信號槽的詳細解釋:
基本概念
- 信號(Signal):對象發出的一種特定事件,類似于廣播,沒有特定的接收者。例如,當按鈕被點擊時,按鈕對象會發出一個“clicked”信號。
- 槽(Slot):用于響應信號的特定函數或方法。槽可以是任何對象的成員函數,也可以是全局函數或靜態成員函數。當信號被發出時,與之相關聯的槽會被自動調用。
實現原理
- 解耦:信號和槽機制實現了對象間的解耦,即發送者和接收者之間不需要知道對方的具體實現細節。
- 連接:通過
connect
函數,可以將信號和槽進行連接。當信號被發出時,與之連接的槽會被自動調用。- 語法和宏:在Qt中,信號和槽通過特定的語法和宏進行聲明和連接,例如
SIGNAL(signal)
和SLOT(slot)
。特性
- 類型安全:需要關聯的信號和槽的簽名必須是一致的,這保證了類型的安全性。
- 松散耦合:信號和槽降低了Qt對象之間的耦合度,使得代碼更加靈活和可維護。
- 多對多關系:多個信號可以與單個槽進行連接,單個信號也可以與多個槽進行連接。
用途
- 窗口間通信:在Qt中,信號槽是實現不同窗口間通信的主要方式。
- 事件處理:當某個事件發生時,通過發出信號并調用相應的槽函數來處理該事件。
示例
以按鈕點擊事件為例,當按鈕被點擊時,按鈕對象會發出一個“clicked”信號。如果有對象對這個信號感興趣,它就可以使用
connect
函數將“clicked”信號與自己的一個槽函數進行連接。當按鈕被點擊時,“clicked”信號被發出,與之連接的槽函數會被自動調用,從而實現了對按鈕點擊事件的處理。優缺點
- 優點:降低了對象間的耦合度,提高了代碼的可維護性和靈活性;實現了類型安全。
- 缺點:與回調函數相比,信號和槽的運行速度可能稍慢,因為需要遍歷信號和槽的關聯關系并執行相應的槽函數。
總之,信號槽是一種強大的對象間通信機制,在Qt框架中得到了廣泛應用。通過合理地使用信號槽,可以編寫出更加靈活、可維護和可擴展的代碼。
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QPushButton *button=new QPushButton(this);button->setText("按鈕");connect(button,&QPushButton::clicked,this,&Widget::HandleClick);
}
例如以上代碼在我們創建一個新的按鈕組件后,利用信號槽進行了信號的捕捉,這個信號對于組件本事有封裝的信號產生函數,當收到該信號,就會去讓處理信號的對象的組件做出對應的操作HandleClick--例如我這里是打開一個新的窗口:
如果是使用圖形化創建窗口,此時并不需要new一個QPushButton,也不需要放在QWidget中,而是在生成項目的時候,就已經在內部的ui中構建了一個成員變量,可以通過ui進行訪問,也不想需要釋放。
窗口的釋放
由于主窗口關閉時,會自動執行析構函數,而子窗口關閉時,卻不會調用析構函數,因此著窗口關閉時調用析構需要我們來設計:
方法一構造添加內置窗口析構函數
直接在構造函數添加如下代碼,然后就可以調用子窗口析構函數了。
setAttribute(Qt::WA_DeleteOnClose);
第二種方法是 重寫虛函數
當窗口關閉瞬間,會發射一個關閉信號,調用槽函數void closeEvent(QCloseEvent* event),只需要在需要關閉的窗口重寫虛函數closeEvent(),在關閉該窗口瞬間,會自動調用該虛函數。該方法不會受其它窗口影響,也即是其它窗口關閉,不會調用這個窗口的closeEvent()虛函數。只會在該窗口類起作用。
void Widget::closeEvent(QCloseEvent *event)
{Q_UNUSED(event);//event->accept();//delete this;qDebug("關閉了窗口");
}
Q_UNUSED()函數在程序中的作用,就如它所代表的英文一樣,unused,即無用的意思。即Q_UNUSED()函數在程序中沒有實質性的作用,用來避免編譯器警告。
第三種方法自定義信號槽
連接close信號到一個自定義槽函數,以便在窗口關閉之前執行特定的操作。接下來我們主要學習QT的信號槽機制。
窗口坐標體系
在計算機中使用的是左手坐標系
當我們創建組件,如果沒有設置坐標系的位置,默認是(0,0),可以通過組件的接口move移動組建的位置,單位是像素,例如我們筆記本電腦的顯示的像素就是(水平)1920 *1080 (垂直),即1080p。其實就是小燈泡的個數。
槽與信號
說到信號大家肯定不陌生,因為我們在學習Linux時就已經知道了,Linux中是有一個信號機制的,且包含大量的信號,通過發送特定的信號,系統捕捉到信號就回去內核態執行對應的信號處理函數,如果是用戶重寫了信號處理函數,那么就存在用戶態到內核態到用戶態的過程。
在QT中為了實現組件之間的通信,也引入了信號機制,雖然兩者并沒什么關系,但還是具有許多相似性。
對于一個信號就要涉及到三個要素:
信號源-----由哪一個組件發出的。
信號的類型---------這是什么樣的信號? 點擊按鈕信號,關閉窗口信號,移動光標信號,下拉框選擇信號.......等等各種組件產生的信號。
信號處理的方式:slot(槽) ------->一個函數
而在QT中就可以使用一個connect函數將一個信號和一個槽關聯起來。這有收到該信號就去調用槽函數處理,這個槽函數其實就是一個回調函數。那么對于該信號槽機制比較重要的就是connect了,那我們來看看connect的原型:
第一個參數為一個部件類的指針,表示信號源,第二個參數表示的是信號的類型,第三個信號表示負責信號的組件,第四個參數表示如何處理信號,第五個參數有初始值一般不關注。
對于信號分兩種:一種是對象自己本身就已經封裝有的,一種是自定義的信號。
像這種載體是的時候有一個這樣的信號的圖標的就是信號,代表點擊的信號。
而這種就是槽函數,這是去做對應操作的,例如這里就是去關閉組件。
其次QT規定了在使用connect的時候,參數一是啥組件,參數二的類型就是在這個組件(我的父類也可以)內定義好的信號類型,即信號類型與信號源相匹配。
對于槽函數也分兩種:一種的已經內置的,一種是自定義的。
參數三與參數四就分別是處理的組件,以及用來處理信號的槽函數的地址。
問題:為什么參數是char*,而我們傳的是函數指針?
實際上這個connect函數聲明是以前舊版本的聲明,首先兩個指針肯定不是同類型的,不能直接轉,而現在的版本實際上提供了兩個宏Signal和SLOT,這兩個宏可以將對應的槽函數與信號函數指針轉會為char*,QT5之后對于參數進行了優化,即使用函數模板,使用泛型參數進行轉化,不需要寫宏。
自定義信號與自定義槽
無論是信號還是槽都是組件的成員函數,所以除了內置的,我們可以自己在類中編寫我們需要的成員函數/
自定義槽
例如按鈕的點擊信號對應處理的槽:
void Widget::HandleClick()
{this->close();//點擊按鈕,關閉窗口
}
但是在類中聲明時需要signal: 或者slot:修飾,在以前時是private/public/protected signals/slots :修飾的,除了這種方式外,我們還可以以圖形化界面的方式自定義信號與槽(底層自動代碼,但相對比較固定)。
當我們選擇后QT會幫我們自動創建好對應的槽函數,生成的槽函數并沒有顯示connect,QT其實還可以通過函數名字自動連接信號,不需要我們再寫connect,每一個組件都有Objectname,如按鈕的On_PushButton_clicked()就說明了處理按鈕點擊事件,但是Objectname一定是按照名字規則
才可以去觸發信號槽處理,QMetaObject::connectSlotsByName(Widget),PushButton->setObjectName(),這些函數我們在使用ui構建部件時,編譯器會自動幫我們創建的。
所以通過ui創建可以根據命名來自動捕獲處理信號,但是手動寫的信號,還是需要手動connect,因為我們這里的代碼是沒有QMetaObject::connectSlotsByName(Widget)來解析函數名的。
自定義信號
除了槽,信號也可以自定義,也是一個成員函數,由private/public/protected signals修飾,但是相對于自定義槽,實際開發中自定義信號不是很用的上,一般外設的信號已經內置有對應的信號函數,因此自定義信號基本上很少用到。
對于自定義信號,是比較特殊的:
1.在QT中,自定義信號只需要聲明,在編譯過程中編譯器會自動生成還信號函數,程序員無法干涉。
2.返回參數類型只能為void,參數有沒有都無所謂,支持重載。
3.內置信號會由于你的某個操作,自動發送信號,是封裝好的,而自定義信號,不僅需要定義,還要由你來決定在哪里發信號,QT提供了一個關鍵則emit,用來發送信號。(實際上emit沒什么大的作用,我們直接調函數也是可以發送信號的)
注意:先綁定信號與槽之后,在發送信號。
例如:
//先定義一個信號
signals:
void mysignal();
//再定義一個槽
public slots:
void HandleMySignal();
//構造中連接信號于槽
connect(this,&Widget::mysignal,this,&Widget::HandleMySignal);//綁定信號與槽
//發送信號
emit mysignal();//mysignal();void Widget::HandleMySignal()
{this->setWindowTitle("自定義處理信號");
}
除了以上不帶參數的信號,也可一定有有參數的信號,此時信號的參數與槽的參數必須保持一致(可以參數一樣,也可以信號參數比槽函數多)。這樣的設定也是可以去讓小于等于槽函數的參數個數的信號都可以來綁定。
//聲明
signals:void mysignal(const QString &str);//再定義一個槽
public slots:void HandleMySignal(const QString &str);//會將信號的參數傳遞給槽//槽的實現
void Widget::HandleMySignal(const QString &str)
{this->setWindowTitle(str);
}
//在點擊按鈕時發送信號,同時傳參數
void Widget::on_pushButton_clicked()
{//發送信號emit mysignal("自定義處理信號");
}
當然內置信號也是有參數的,不過不用我們來傳。
最后一點就是在使用信號與槽機制時一定一定要添加上Q_OBJECT這個宏,有了它才能使用信號槽。
connect(this,&Widget::mysignal,this,([this](const QString &str){ this->setWindowTitle(str);}));//不需要聲明,定義,直接一次性寫
?
因為信號與槽這惡機制,開發者是想要實現除了信號處理的功能,更重要的是實現一對一,一對多,多對多復用信號和槽的這種機制,這種關系與數據庫中的關聯關系時非常類似的,對于這種關系,QT引出了一張關聯表來記錄信號與槽之間的關系。
不過這個表格不是真表格,而是connect所綁定的一個個關系。
但是理想很豐滿,顯示很骨干,實際開發需要中更多的就是一對一,稍微少一點的一對多,多對多基本上是一個偽需求,用不到。
補充:
1.信號與槽的斷開:使用disconnect斷開信號與槽的連接,用法類似。用的也比較少,一般不會斷開,如果是想將之前的連接斷開,建立新的連接,不是一對多的綁定,可以使用該函數。
2.槽函數的定義也可以使用lanmda表達式。在c++的時候我們就已經直到,對于函數指針,仿函數,lanmda表達式,這三者之間其實是可以相互替換,三者作用相同,還可以通過包裝器作為參數傳遞。