Qt-跨平臺的C++圖形使用者介面應用程式框架(一)

未名小菜發表於2018-10-22

韓元旭、餘橙、沈開洋

Qt介紹

Qt是一個跨平臺的C++圖形使用者介面應用程式框架。它早在1991年奇趣科技公司兩位合夥人著手開發這樣一個平臺,在2008年如日中天的諾基亞由於看好Qt在嵌入式領域的潛力,一擲千金將它收購作為新一代智慧手機作業系統的載體,但是因為諾基亞在智慧手機領域的敗北,不得已而放手Qt,終於Qt於2012被最後一位東家 Digia 公司收購。經過這幾年的發展,Qt不但擁有了完備的C++圖形庫,而且也極大的提高了Qt開發跨平臺應用程式的能力。

Qt可以同時支援桌面應用程式開發、嵌入式開發和移動開發,甚至它可以做移動開發,覆蓋了現有的所有主流平臺。你只需要編寫一次程式碼,釋出到不同平臺前重新編譯即可。

哪些產品都是用Qt開發的?

Qt憑藉一套對原生Windows、Mac、Linux等平臺支援很好的 GUI 庫和豐富的 API 庫,使得它成為了開發跨平臺桌面應用的一個很好的選擇。與中國一些優秀的桌面端軟體選擇自己開發多平臺的 GUI 庫不同,國外的很多優秀桌面應用都偏好採用跨平臺的 GUI 庫進行開發。比如在矽谷有一款很著名的文件管理應用 DropBox,微軟自家的社交聊天工具 Skype,像極品飛車這樣大型的遊戲的GUI頁面,甚至國內的金山軟體公司推出的辦公軟體 WPS Office,當然還有我們的AlphaBox

為什麼AlphaBox選擇Qt?

因為Qt不僅能夠高效率的完成不同平臺GUI內容的開發,更能夠高效率的完成系統級別的一些任務。這也是AlphaBox選擇使用Qt開發的原因。

AlphaBox 其實包含兩個主要的程式,一塊是同步盤的引擎—C語言構建的底層同步程式,我們稱之為 daemon;剩下的圖形頁面以及與作業系統互動的模組都是使用Qt進行開發的。Qt提供的豐富的跨平臺GUI元件能夠保證在不同作業系統中 AlphaBox 都有著美觀和吻合作業系統的樣式,Qt自家生產的 IDE—Qt Creator 提供了一套非常好上手的圖形介面構建工具,即使是剛接觸Qt的小白也可以輕鬆的繪製出想要的頁面並且能夠完成核心頁面邏輯。除此之外,得益於 C++ 的加持,Qt與作業系統有著非常健壯的通訊機制,憑藉這一點,AlphaBox 能夠輕鬆完成精準監控作業系統中檔案的改動、建立本地資料庫進行寫入資料的等操作,不僅於此,優秀的混合程式設計能力能夠讓Qt輕鬆的與 Objective-CC# 等語言進行混編,實現系統級別擴充套件的呼叫,這就是我們能夠在 FinderWindows 資源管理器中能夠看到同步盤檔案不同狀態的原因了。

Qt優勢

優良的跨平臺特性

Qt支援 WindowsLinux/UnixMac OS XAndroidBlackBerryQNX等多種平臺,併為這些不同的平臺提供了統一的開發環境。

物件導向

C++是完全物件導向的,這一點和Objective-c等在開發很相似。而Qt又是基於C++一種語言的擴充套件,大家都知道C++ 有快速、簡易、物件導向等很多優點,所以Qt自然也繼承者C++這些的優點。

Qt良好的封裝機制使得Qt的模組化程度非常高,可重用性較好,對使用者開發來貨是非常方便的。Qt提供一種為signals/slots(訊號和槽) 的安全型別來替代callback,使得各個元件之間的協同工作變得十分簡單。

豐富的API

Qt包括多達 250 個以上的 C++ 類,還提供基於模板的 collections, serialization, file, I/Odevice, directory management, date/time 類。甚至還包括正規表示式的處理功能。
支援 2D/3D 圖形渲染,支援 OpenGL。
大量的開發文件。

XML支援

Webkit 引擎的整合,可以實現本地介面與Web內容的無縫整合, 但是真正使得 Qt 在自由軟體界的眾多 Widgets (如 Lesstif,Gtk,EZWGL,Xforms,fltk 等等)中脫穎而出的還是基於 Qt 的重量級軟體 KDE。

訊號和槽機制

Qt提供了訊號機制用於完成見面操作的響應,是完成任意兩個Qt物件之通訊機制。其中,訊號會在某個特定情況或動作下被觸動,槽是等同於接受並處理訊號的函式。

為什麼方法不是直接呼叫的。中間用到 Signal機制不是多此一舉?

其實在我們生活也是一樣,老闆級別的好說話,老闆給助理分派任務也好說話,但是助理給老闆分任務,可想而知會有什麼後果,在以前的統治階層肯定不允許這樣的事發生。所以在分層思想中,我們所呼叫的函式也是這樣的,上層可以呼叫下層和同一層的函式,下層函式不可以呼叫上層函式,否則程式的層次性會被打破,導致結構錯綜複雜,難以維護和管理。

那麼怎樣才能做到向上管理呢,有任務分配給老闆怎麼辦?

老闆會設立一個機構,也就是一個函式,用無限迴圈來查詢助理的狀態,如果助理真的有事情,這個機構就把這訊息拿到老闆來處理。但是這種處理方式顯得有些複雜,我們想要的簡單明瞭的方式是,如果助理有事件發生,可以直接呼叫老闆函式處理。

說了這麼多其實就是想說,訊號和槽的最大優勢在於,它完善了程式分層的思想,可以在不改變程式的層次性的情況下,完成由下層到上層的呼叫。在下層發出一個 Signal,這時上層與其想關聯的 Slot 函式就會響應。

Qt-跨平臺的C++圖形使用者介面應用程式框架(一)

現在,訊號和槽中存在的問題是:

  • 傳送訊號的物件只負責傳送,但它並不知道由誰來接收訊號;
  • 接收物件中的槽本身只是一個普通的成員函式,它並不知道響應哪個訊號;

要想解決以上問題,就需要將相應的訊號和槽連線起來。當指定的訊號發出時,槽所在的物件就能接收到該訊號,從而呼叫相應的槽函式執行指定的處理。

訊號與槽的連線方式

1.一個訊號可以與另一個訊號相連

connect (Object1,SIGNAL(signal1),Object2,SIGNAL(signal2));
複製程式碼

表示 Object1的訊號1傳送可以觸發Object2的訊號1傳送。

2.同一個訊號可以與多個槽相連:

 connect (Object1,SIGNAL(signal2),Object2,SIGNAL(slot2));
 connect (Object1,SIGNAL(signal2),Object3,SIGNAL(slot1));   
複製程式碼

3.同一個槽可以響應多個訊號:

 connect (Object1,SIGNAL(signal2),Object2,SIGNAL(slot2));
 connect (Object3,SIGNAL(signal2),Object2,SIGNAL(slot2));  
複製程式碼

4.連線可以被移除:

這種情況用得比較少,因為在物件被刪除時,Qt會自動移除與這個物件相關的所有連線。

disconnect(sender, SIGNAL(signal), receiver, SLOT(slot)); 
複製程式碼

但是,常用的連線方式為:

 connect (Object1,SIGNAL(signal),Object2,SIGNAL(slot));
複製程式碼

其中,signal 為物件Object1的訊號,slot 為Object2的槽。

####提示: 訊號與槽機制與普通函式的呼叫一樣,如果使用不當的話,在程式執行時也有可能產生死迴圈。因此,在定義槽函式時一定要注意避免間接形成無限迴圈,即在槽中再次發射所接收到的同樣訊號。
如果一個訊號與多個槽相聯絡的話,那麼,當這個訊號被髮射時,與之相關的槽被啟用的順序將是隨機的。
巨集定義不能用在 signalslot 的引數中。
訊號和槽的引數個數與型別必須一致。

訊號和槽機制優點

型別安全

需要關聯的訊號和槽的簽名必須是等同。即訊號的引數型別和引數個數 同接收該訊號的槽的引數型別和引數個數相同。不過一個槽的引數個數是可以少於訊號的引數的個數的,但是缺少的引數必須是訊號引數的最後一個或者幾個引數。如果訊號和槽的簽名不符,編譯器就會報錯。

鬆散耦合

訊號和槽機制大大降低了Qt物件的耦合度。傳送訊號的Qt物件不需要知道是哪個物件來接收它的訊號,它只需要做的是在適當的時間傳送一個訊號,而且不需要知道也不關心它的訊號有沒有被接收到,更不需要知道哪個物件的哪個槽接收到了訊號。

同樣地,Qt物件的槽也不需要關係是哪些訊號連線了自己,如果訊號和槽連線上了,Qt就能保證了適合的槽得到了呼叫。即使關聯的物件在執行時被刪除。應用程式也不會崩潰。

訊號和槽的效率

訊號和槽機制增強了物件間通訊的靈活性,當然在增加靈活性的同時在效能方面也會有一定的損失。同大家回撥函式相比,訊號和槽機制執行速度有些慢。通常,通過傳遞一個訊號來呼叫槽函式將會比直接呼叫直接呼叫非虛擬函式執行速度慢10倍。

原因:

  1. 需要定位接收訊號的物件。
  2. 安全地遍歷所有的關聯。
  3. 編組/解組傳遞的引數。
  4. 多執行緒的時候。訊號可能需要排隊等待。

然而,與建立堆物件的new操作及刪除堆物件的delete操作相比,訊號和槽的執行代價只是它們很少一部分。訊號和槽機制導致的這點效能損耗,對實時應用程式是可以忽略的;同訊號和槽提供的靈活性和簡便性相比,這點效能損耗是值得的。

Qt 佈局系統介紹

佈局系統

作為一名 iOS 開發人員, 見證著 iOS 佈局系統的不斷完善, 從絕對佈局, Autoresizing 到 Autolayout. 使得開發人員的工作效率越來越高, 專案介面的可讀性和易維護性越來越強. 如今 IDE 中的視覺化介面工具已經非常強大, 許多網友"戲稱" iOS 開發者為"UI 拖拽師", 可見, iOS 開發中介面佈局系統的高效. 所以, 優秀的佈局系統的使命在於讓開發者花更少的時間來完成更易維護的介面.

同樣的, 在 Qt 中, 系統提供了強大的排版機制來為視窗中的檢視進行佈局排版, 經過了對 Qt 佈局一個初步的探索, 不得不對 Qt 佈局系統的簡潔高效而又功能強大表示讚歎.

佈局系統的功能

在 Qt 中, 佈局系統可以完成

  • 定位子控制元件
  • 得知窗體預設大小
  • 得知窗體最小大小
  • 窗體大小變化時進行佈局排版
  • 內容改變(字型大小文字等, 隱藏或顯示, 移除)時進行佈局排版

佈局系統的結構

Qt 提供了 QLayout 類及其子類來為介面進行排版佈局. 結構如下圖:

佈局系統結構圖

QLayout 是佈局系統中的抽象基類, 繼承自 QObject 和 QLayoutItem, 其中四個子類分別為

  • QBoxLayout(箱式佈局)
  • QFormLayout(表單佈局)
  • QGridLayout(網格佈局)
  • QStackedLayout(棧佈局)

在真實使用場景中, 往往需要通過多種佈局的相結合來完成介面的設計, 接下來將分別介紹四中佈局.

QBoxLayout 箱式佈局

箱式佈局提供了兩個子類分別處理水平(QHBoxLayout)和垂直(QVBoxLayout)兩個方向的排版, 可以使檢視排成一行或者一列來顯示. 簡單說, 就是可以讓控制元件進行排排站, 比如在我們的 AlphaBox 中, 頂部的頭像, 姓名, 和重新整理按鈕排成了一排, 這就是水平箱式佈局:

什麼叫排排站

你以為我要講一下這個東西如何實現? NO, 我偏偏要以垂直箱式佈局為例, 用一個最簡單的例子來介紹箱式佈局的使用, 首先建立一個基於 QWidget 的介面, 新增我們需要使用的標頭檔案:

#include <QVBoxLayout>
#include <QPushButton>
複製程式碼

並在建構函式中新增如下程式碼

    //  新增兩個按鈕
    QPushButton *okBtn  = new QPushButton;
    okBtn ->setText(tr("我在上面, 我最牛"));
    QPushButton *celBtn = new QPushButton;
    celBtn->setText(tr("我在下面, 我不服"));

    //  建立一個垂直箱式佈局, 將兩個按鈕扔進去
    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(okBtn);
    layout->addWidget(celBtn);

    //  設定介面的佈局為垂直箱式佈局
    setLayout(layout);
複製程式碼

執行看一下效果, 什麼? 這就可以執行了? 座標呢? 尺寸呢? 是的, 沒看錯...點選執行:

最簡單的箱式佈局

兩個按鈕已經一上一下, 乖乖的在垂直方向自己站好了位置, 就是這麼強大, 就是這麼省心.

QFormLayout 表單佈局

強大的 AlphaBox 是很外向的, 可以很輕鬆的將你的資料分享給其他使用者, 當我們分享的時候, 會有這樣一個介面:

在 AlphaBox 中共享資料

看到這個介面, 聰明的你可能會說, 這很簡單啊, 好幾個水平箱式佈局就可以實現, 可是, 更聰明的 Qt 提供了更高效的方式幫助你完成這樣一個介面, 那就是 QFormLayout.

在我所學習 Qt 所使用的書籍中, 將 QFormLayout 翻譯為窗體佈局, 我個人認為, 將其翻譯為表單佈局更為貼切, 因為 QFormLayout 的強大之處正是可以使用最快的速度完成一個使用者輸入的表單介面的搭建.

那麼, 讓我們揭開 AlphaBox 的神祕面紗, 看看這樣一個介面是怎麼實現的.

首先, 拖拽一個 Form Layout 到 Widget 中.

新增表單佈局

雙擊之後即可為表單增加一行.

為表單增加一行

相信大家看到這張圖時, 就已經能理解到表單佈局是如何使用的, 提供了標籤作為使用者輸入內容的指引, 提供欄位型別作為使用者輸入的控制元件, 作為 iOS 開發者, 深知這樣一個介面的搭建所需要的繁雜的工作量. 當我第一次開啟這個介面時, 被這樣建立介面的方式所驚呆了.

  1. 按照圖中, 建立表單的第一行, 共享給哪個使用者的輸入框, 可以為輸入框填寫佔位文字.
  2. 雙擊 Form Layout 建立欄位型別為 QComboBox (多選框)的一行. 填寫允許的許可權內容.
  3. 設定整個 Widget 佈局為垂直箱式佈局
  4. 在 Form Layout 下拖拽過去一個 Horizontal Layout(水平箱式佈局)
  5. 在箱式佈局中新增 Horizontal Spacer (水平佔位) 後拖拽兩個 Push Button 完成介面佈局

共享介面的佈局

快不快? 快不快! 快不快!!!

同樣的, 如果是使用純程式碼表單佈局的話可以使用addRow()的方法來新增一行.

QGridLayout 網格佈局

強大的 AlphaBox 是這樣的

事實上, 強大的 AlphaBox 是這樣的, 我們可以共享給多個使用者, 而且, 下方會有一個列表, 展示共享的使用者以及許可權列表. 這時, 表單佈局就沒辦法滿足我們, 只好另求新歡 QGridLayout - 網格佈局.

網格佈局顧名思義, 可以將介面分割成行列來進行佈局管理, 在每個單元格中來擺放控制元件. 所以 AlphaBox 分享的介面使用了一個 兩行三列 的網格佈局來實現的.

QGridLayout - 網格佈局

當然, 更更復雜的介面, 用 Qt 佈局的效率也是非常高的, 我做了一個外鏈分享的佈局 Demo, 可以將內部資料生成一個下載連結共享給任何人去下載.

外鏈分享介面

這個介面中, 我在Tab之內使用了網格佈局, 佈局如圖:

外鏈分享介面佈局

從圖中可以看出, 網格佈局像是在操作一個 Excel 一樣簡單, 佈局單元格, 合併單元格, 等等.

在這個介面中, 更靈活的使用了 QLayout 的屬性來完成了介面佈局排版.

同樣的, 在程式碼中, 可以使用如下等的 Api 來為網格檢視新增一個從幾行幾列開始佔據幾行幾列的控制元件:

void addWidget(QWidget *, int row, int column, int rowSpan, int columnSpan)
複製程式碼

QStackedLayout 棧佈局

如在 AlphaBox 中, 我們可以通過雲端檔案瀏覽器直接檢視和操作雲端檔案, 在載入的過程中, 會有一個轉菊花的介面.

在轉菊花的 AlphaBox

載入失敗時的錯誤提示:

菊花轉失敗了的 AlphaBox

以及載入成功時:

通常情況下我們能看見的 AlphaBox

通常應用的介面會根據不同的狀態有不同的內容, 這時就可以使用 QStackedLayout 棧佈局, 棧佈局提供了一個頁面的棧, 每個頁面有完全獨立的介面佈局. 可以非常清晰的對不同狀態下的介面進行佈局管理.

在 Qt 的視覺化佈局工具中, 通過 Stacked Widget 來完成介面的棧佈局

Stacked Widget

通過右鍵來進行頁面的插入移除和排序等操作.

最後

在接下來的文章中我們會從搭建專案起,分模組的更加深入的介紹Qt開發,敬請期待!

相關文章