總結:
本文先通過一個例子介紹了Qt項目的大致組成,即其一個簡單的項目框架,如何定義窗口類,綁定信號和槽,然后初始化窗口界面,顯示窗口界面,以及將程序的控制權交給Qt庫。
然后主要對Qt中的信號與槽機制、Qt 元對象系統、布局管理器的概念進行分析。
---------------------------------------------
1. 下載 Qt5
http://download.qt.io/official_releases/qt/5.8/5.8.0/
2. 新建Qt 項目
具體過程見我的另一篇隨筆,如何新建QT項目。
有兩種創建圖形化程序界面的方法,一、通過系統自動生成的.ui文件,二、通過代碼實現。
方法一可通過鼠標拖拉布局界面,較方便,但在設計較復雜的界面時最好還是使用Qt提供的布局管理器。
本文的例子是采用代碼實現計算圓面積的界面程序,便於展示信號與槽的通信機制以及整個程序的框架。
3. 計算圓面積的程序代碼實例
整個程序的架構圖如下圖

程序運行界面:

4、QT項目框架分析
4.1 每一個工程都有一個執行的入口函數,此項目中的main.cpp中的 main()函數就是此工程的入口。
main.cpp
#include "dialog.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); Dialog w; w.show(); return a.exec(); }
如上所示,其中:
1) #include "dialog.h" 包含了程序中要完成功能的Dialog類的定義,在Dialog類中封裝完成所需要的功能。
注意:程序中使用哪個類,就要引用包含該類定義的頭文件。如 dialog.h中使用QLabel、QLineEdit、QPushButton必須包含頭文件
#include <QLabel> #include <QLineEdit> #include <QPushButton>
2) #include <QApplication>:Application類的定義。
3) int main(int argc, char *argv[]): 應用程序的入口,幾乎在所有使用Qt的情況下,main()函數只需要在將控制權交給Qt庫之前執行初始化,然后Qt 庫通過事件向程序告知用戶的行為。所有的Qt程序中都 必須有且只有一個main()函數。
4) QApplication a(argc, argv): a 是這個程序的 QApplication對象。
在每一個使用Qt圖形化應用程序中都必須使用一個QApplication 對象。QApplication管理了各種和樣的圖形化應用程序的廣泛資源、基本設置、控制流及事件處理等。
在任何的窗口部件被使用之前必須創建QApplication 對象。它在這里被創建並且處理這些命令行變量。所有被Qt識別的命令行參數都將從argv中被移去(並且 argc 也因此而減少)。
5) w.show():當創建一個窗口部件的時候,默認它是不可見的,必須調用show()函數使它變為可見。、
6) return a.exec():程序進入消息循環,等待可能的輸入進行響應。這里就是main()函數將控制權交給Qt, Qt完成事件處理工作,當應用程序退出的時候,exec()函數的值就會返回。在exec()函數中,Qt接收並處理用戶和系統的事件並且將它們傳遞給適當的窗口部件。
4.2 dialog.h 頭文件
在類Dialog中的定義中,Q_OBJECT宏的作用是啟動Qt元對象系統的一些特性(如支持信號和槽等),它必須放到類定義的私有區。
private slots: 表示下面的函數為槽函數。
dialog.h
#ifndef DIALOG_H #define DIALOG_H #include <QDialog> #include <QLabel> #include <QLineEdit> #include <QPushButton> #include <QGridLayout> const static double PI = 3.1416; class Dialog : public QDialog { Q_OBJECT public: Dialog(QWidget *parent = 0); ~Dialog(); private: QLabel *label1, *label2; QLineEdit *lineEdit; QPushButton *button; private slots: void showArea(); }; #endif // DIALOG_H
4.3 dialog.cpp
在該文件中初始化部件,創建布局管理器,將部件加到布局管理器中,並且連接
#include "dialog.h" Dialog::Dialog(QWidget *parent) : QDialog(parent) {
//初始化部件 label1 = new QLabel(this); label1->setText(tr("請輸入圓的半徑:")); lineEdit = new QLineEdit(this); label2 = new QLabel(this); button = new QPushButton(this); button->setText(tr("顯示對應的圓的面積"));
//創建布局管理器,將部件加到布局管理器中 QGridLayout *mainLayout = new QGridLayout(this); mainLayout->addWidget(label1,0,0); mainLayout->addWidget(lineEdit,0,1); mainLayout->addWidget(label2,1,0); mainLayout->addWidget(button,1,1);
// 連接信號與槽
//分別是 點擊和文本內容改變信號 connect(button,SIGNAL(clicked()),this,SLOT(showArea())); connect(lineEdit,SIGNAL(textChanged(QString)),this,SLOT(showArea())); } Dialog::~Dialog() { delete label1,label2,lineEdit,button; } //定義槽函數 void Dialog::showArea() { bool ok; QString tempStr; QString valueStr = lineEdit->text(); int valueInt = valueStr.toInt(&ok); double area = valueInt * valueInt * PI; label2->setText(tempStr.setNum(area)); }
4.4 上面三部分為這個工程代碼的分析,介紹了主程序中完成圖形化界面程序所需的編寫的代碼,即窗口部件的初始化,以及窗口的使用。dialog.h dialog.cpp主要告訴如何自定義一個對話框Dialog類(繼承自QDialog)即定義窗口部件,以及如何將窗口部件的信號與處理事件的槽函數進行綁定。
通過上面的分析我們可以創建一個簡單的計算圓面積的圖形應用程序。
5、概念解析
5.1 信號和槽機制(Signal & Slot)
Qt提供了信號和槽機制用於完成界面操作的響應,是完成任意兩個Qt對象之間的通信機制。其中,信號會在某個特定的情況或動作下被觸發,槽是等同於接收並處理信號的函數。
每個Qt對象都包含若干個預定義的信號和若干個預定義的槽,當某一個特定的事件發生時,一個信號被發送,與信號相關的槽則會響應信號並完成相應的處理。當一個類被繼承時,該類的信號和槽也同時被繼承,也可以根據需要自定義信號和槽。
1. 信號與槽的連接方式
1)一個信號可以與另一個信號相連
connect(Object1,SIGNAL(signal1),Object2,SLOT(signal2));
表示Object1的信號1可以觸發Object2的信號2的發送。
2)一個信號可以與多個槽相連
connect(Object1,SIGNAL(signal1),Object2,SLOT(slot2));
connect(Object1,SIGNAL(signal1),Object3,SLOT(slot3));
3)一個槽可以響應多個信號
connect(Object1,SIGNAL(signal1),Object2,SLOT(slot2));
connect(Object3,SIGNAL(signal3),Object2,SLOT(slot2));
常用的連接方式為
connect(Object1,SIGNAL(signal),Object2,SLOT(slot));
其中,signal為Object1的信號,slot為Object2的槽
SIGNAL()和SLOT()是Qt定義的兩個宏,它們返回其參數的C語言風格的字符串(const char*)。如下所示,兩者等同。
connect(Object1,SIGNAL(signal),Object2,SLOT(slot));
connect(Object1,“signal”,Object2,“slot”);
2.信號與槽機制的優點
1)類型安全。需要關聯的信號和槽的簽名必須是等同的,即信號的參數類型和參數個數與接收該信號的槽的參數類型和參數個數相同。
但是槽的參數個數可以少於信號的參數個數,但缺少的參數必須是信號參數的最后一個或幾個參數。如信號和槽的參數簽名不符,編譯器就會報錯。
2)松散耦合。信號與槽機制減弱了Qt對象的耦合度。
激發信號的Qt對象無須知道是哪個對象的槽接收它發出的信號,它只需在適當的時候發送適當的信號即可,它不需要關心它發出的信號有沒有被接收到,以及哪個對象的哪個槽接收到該信號。
對象的槽也不需要知道哪些信號關聯了自己,而一旦關聯信號和槽,Qt就保證了適合的槽得到了調用。即使關聯的對象在運行時被刪除程序也不會崩潰。
注意:一個類若要支持信號和槽,就必須從QObject或QObject的子類繼承。Qt的信號和槽機制不支持對模板的使用。
3)信號與槽機制的效率
信號與槽機制增強了對象間通信的靈活性,然而損失發一些性能。同回調函數相比,信號和槽機制運行速度有些慢。通常,傳遞一個信號來調用槽函數將會比直接調用非虛函數運行速度慢10倍。原因如下:
a. 需要定位接收信號的對象。
b. 安全地遍歷所有的關聯(如一個信號關聯多個槽的情況)。
c. 編組(marshal)/解組(unmarshal)傳遞的參數。
d. 多線程的時候,信號可能需要排隊等待。
然而,與創建對象的new操作和刪除對象的delete操作相比,信號和槽的運行代價只是它們很少的一部分。信號和槽機制導致的這點性能損耗,對實時應用程序是可以忽略的;同信號和槽提供的靈活性和簡便性相比,這點性能損耗是值得的。
5.2 Qt5元對象系統
Qt 元對象系統提供了對象間的通信機制(信號和槽)、運行時類型信息和動態屬性系統的支持,是標准C++的一個,它使Qt能夠更好地實現GUI圖形用戶界面編程。
Qt的元對象系統不支持C++模板,盡管模板擴展了標准C++的功能,但是元對象系統提供了模板無法提供的一些特性。
Qt元對象系統基於以下三個事實。
1)基類QObject: 任何需要使用元對象系統功能的類必須繼承自QObject。
2)Q_OBJECT宏:Q_OBJECT宏必須出現在類的私有聲明區,用於啟動元對象的特性。
3)元對象編譯器 (Meta-Object Complier, moc): 為QObject子類實現元對象特性提供必要的代碼實現。
5.3 布局管理器
在設計較復雜的GUI用戶界面時,僅通過指定窗口部件的父子關系以期達到加載和排列窗口部件的方法是行不通的,最好的辦法是使用Qt提供的布局管理器。
QGridLayout *mainLayout = new QGridLayout(this); //(a) mainLayout->addWidget(label1,0,0); //(b) mainLayout->addWidget(lineEdit,0,1); mainLayout->addWidget(label2,1,0); mainLayout->addWidget(button,1,1); setLayout(mainLayout); //(c)
其中:
(a) QGridLayout *mainLayout = new QGridLayout(this) : 創建一個網格布局管理器對象 mainLayout, 並且 this 指出父窗口。
(b) mainLayout->addWidget(...) : 分別將控件label1等放置在該布局管理器中,還可以在創建布局管理器對象時不必指明父窗口。
(c) QWidget::setLayout(...) : 將布局管理器添加到對應的窗口部件對象中。因為這里的主窗口就是父窗口,所以直接調用 setLayout(mainLayout)即可。
