Qt跨平臺策略
- GUI 模擬:任何平臺都提供了圖形繪制函數(shù)昵观,例如畫點(diǎn)、畫線索昂、畫面等。有些工具庫利用這些基本函數(shù)缤至,在不同繪制出自己的組件康谆,這就是 GUI 模擬。GUI 模擬的工作量無疑是很大的沃暗,因?yàn)樾枰褂米罨镜睦L圖函數(shù)將所有組件畫出來;并且這種繪制很難保證和原生組件一模一樣嚼黔。但是,這一代價(jià)帶來的優(yōu)勢是唬涧,可以很方便的修改組件的外觀——只要修改組件繪制函數(shù)即可。很多跨平臺的 GUI 庫都是使用的這種策略碎节,例如 gtk+(這是一個(gè) C 語言的圖形界面庫。使用 C 語言很優(yōu)雅地實(shí)現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計(jì)胎撇。不過殖氏,這也同樣帶來了一個(gè)問題——使用大量的類型轉(zhuǎn)換的宏來模擬多態(tài),并且它的函數(shù)名一般都比較長受葛,使用下劃線分割單詞,看上去和 Linux 如出一轍纲堵。gtk+ 并不是模擬的原生界面闰渔,而有它自己的風(fēng)格,所以有時(shí)候就會(huì)和操作系統(tǒng)的界面格格不入冈涧。),Swing 以及我們的 Qt督弓。
信號槽
- 信號槽要求信號和槽的參數(shù)一致,所謂一致蒂阱,是參數(shù)類型一致狂塘。如果不一致,允許的情況是荞胡,槽函數(shù)的參數(shù)可以比信號的少,即便如此廊营,槽函數(shù)存在的那些參數(shù)的順序也必須和信號的前面幾個(gè)一致起來。這是因?yàn)樽阜纾憧梢栽诓酆瘮?shù)中選擇忽略信號傳來的數(shù)據(jù)(也就是槽函數(shù)的參數(shù)比信號的少)纵刘,但是不能說信號根本沒有這個(gè)數(shù)據(jù),你就要在槽函數(shù)中使用(就是槽函數(shù)的參數(shù)比信號的多瞬捕,這是不允許的)。
- Qt 5 引入了信號槽的新語法:使用函數(shù)指針能夠獲得編譯期的類型檢查肪虎。
- 如果你使用了 Qt5 的新語法惧蛹,新語法提供了編譯期檢查(取函數(shù)指針),因此取 private 函數(shù)的指針是不能通過編譯的香嗓。
- 在 Qt 5 中,如果你想使用 overloaded 的 signal沧烈,有兩種方式可供選擇:
- 使用 Qt 4 的SIGNAL和SLOT宏像云,因?yàn)檫@兩個(gè)宏已經(jīng)指定了參數(shù)信息,所以不存在這個(gè)問題迅诬;
- 使用函數(shù)指針顯式指定使用哪一個(gè)信號。
- 自動(dòng)連接(默認(rèn))意味著如果接受者所在線程就是當(dāng)前線程惩歉,則使用直接連接铐维;否則將使用隊(duì)列連接。
動(dòng)作
- Qt 使用QAction類作為動(dòng)作嫁蛇。顧名思義,這個(gè)類就是代表了窗口的一個(gè)“動(dòng)作”第煮,這個(gè)動(dòng)作可能顯示在菜單,作為一個(gè)菜單項(xiàng)包警,當(dāng)用戶點(diǎn)擊該菜單項(xiàng),對用戶的點(diǎn)擊做出響應(yīng)害晦;也可能在工具欄,作為一個(gè)工具欄按鈕鲫剿,用戶點(diǎn)擊這個(gè)按鈕就可以執(zhí)行相應(yīng)的操作稻轨。有一點(diǎn)值得注意:無論是出現(xiàn)在菜單欄還是工具欄,用戶選擇之后殴俱,所執(zhí)行的動(dòng)作應(yīng)該都是一樣的。因此明场,Qt 并沒有專門的菜單項(xiàng)類询筏,只是使用一個(gè)QAction類,抽象出公共的動(dòng)作嫌套。當(dāng)我們把QAction對象添加到菜單,就顯示成一個(gè)菜單項(xiàng)踱讨,添加到工具欄,就顯示成一個(gè)工具按鈕莺治。用戶可以通過點(diǎn)擊菜單項(xiàng)帚稠、點(diǎn)擊工具欄按鈕、點(diǎn)擊快捷鍵來激活這個(gè)動(dòng)作滋早。
資源文件
- Qt 資源系統(tǒng)是一個(gè)跨平臺的資源機(jī)制,用于將程序運(yùn)行時(shí)所需要的資源以二進(jìn)制的形式存儲于可執(zhí)行文件內(nèi)部搁进。如果你的程序需要加載特定的資源(圖標(biāo)、文本翻譯等)饼问,那么,將其放置在資源文件中峻堰,就再也不需要擔(dān)心這些文件的丟失驮吱。也就是說,如果你將資源以資源文件形式存儲左冬,它是會(huì)編譯到可執(zhí)行文件內(nèi)部纸型。
對象模型
- Qt 保證的是,任何對象樹中的 QObject對象 delete 的時(shí)候狰腌,如果這個(gè)對象有 parent,則自動(dòng)將其從 parent 的children()列表中刪除瑰枫;如果有孩子丹莲,則自動(dòng) delete 每一個(gè)孩子。Qt 保證沒有QObject會(huì)被 delete 兩次甥材,這是由析構(gòu)順序決定的。
- 下面的代碼會(huì)導(dǎo)致quit析構(gòu)兩次鸳惯,程序崩潰叠萍。
#include "mainwindow.h"
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton quit("Quit");
MainWindow w;
quit.setParent(&w);
w.show();
return a.exec();
}
事件
- Qt 的事件對象有兩個(gè)函數(shù):accept()和ignore()。正如它們的名字一樣苛谷,前者用來告訴 Qt,這個(gè)類的事件處理函數(shù)想要處理這個(gè)事件瓢湃;后者則告訴 Qt,這個(gè)類的事件處理函數(shù)不想要處理這個(gè)事件绵患。在事件處理函數(shù)中,可以使用isAccepted()來查詢這個(gè)事件是不是已經(jīng)被接收了织狐。具體來說:如果一個(gè)事件處理函數(shù)調(diào)用了一個(gè)事件對象的accept()函數(shù)筏勒,這個(gè)事件就不會(huì)被繼續(xù)傳播給其父組件;如果它調(diào)用了事件的ignore()函數(shù)管行,Qt 會(huì)從其父組件中尋找另外的接受者。
- 事實(shí)上荡陷,我們很少會(huì)使用accept()和ignore()函數(shù)迅涮,而是像上面的示例一樣,如果希望忽略事件(所謂忽略叮姑,是指自己不想要這個(gè)事件),只要調(diào)用父類的響應(yīng)函數(shù)即可耘沼。記得我們曾經(jīng)說過,Qt 中的事件都是 protected 的耕拷,因此托享,重寫的函數(shù)必定存在著其父類中的響應(yīng)函數(shù),所以闰围,這個(gè)方法是可行的。為什么要這么做碧查,而不是自己去手動(dòng)調(diào)用這兩個(gè)函數(shù)呢?因?yàn)槲覀儫o法確認(rèn)父類中的這個(gè)處理函數(shù)有沒有額外的操作忠售。如果我們在子類中直接忽略事件,Qt 會(huì)去尋找其他的接收者卦方,該子類的父類的操作會(huì)被忽略(因?yàn)闆]有調(diào)用父類的同名函數(shù))泰佳,這可能會(huì)有潛在的危險(xiǎn)。為了避免自己去調(diào)用accept()和ignore()函數(shù)逝她,而是盡量調(diào)用父類實(shí)現(xiàn),Qt 做了特殊的設(shè)計(jì):事件對象默認(rèn)是 accept 的近刘,而作為所有組件的父類QWidget的默認(rèn)實(shí)現(xiàn)則是調(diào)用ignore()宁昭。這么一來,如果你自己實(shí)現(xiàn)事件處理函數(shù)积仗,不調(diào)用QWidget的默認(rèn)實(shí)現(xiàn)蜕猫,你就等于是接受了事件;如果你要忽略事件隆圆,只需調(diào)用QWidget的默認(rèn)實(shí)現(xiàn)翔烁。這一點(diǎn)我們前面已經(jīng)說明渺氧。
- 事件過濾器和被安裝過濾器的組件必須在同一線程蹬屹,否則,過濾器將不起作用贩耐。另外厦取,如果在安裝過濾器之后,這兩個(gè)組件到了不同的線程,那么更鲁,只有等到二者重新回到同一線程的時(shí)候過濾器才會(huì)有效奇钞。
- 現(xiàn)在我們可以總結(jié)一下 Qt 的事件處理,實(shí)際上是有五個(gè)層次:
- 重寫paintEvent()蛇券、mousePressEvent()等事件處理函數(shù)。這是最普通塘慕、最簡單的形式蒂胞,同時(shí)功能也最簡單。
- 重寫event()函數(shù)骗随。event()函數(shù)是所有對象的事件入口蛤织,QObject和QWidget中的實(shí)現(xiàn),默認(rèn)是把事件傳遞給特定的事件處理函數(shù)鸿染。
- 在特定對象上面安裝事件過濾器指蚜。該過濾器僅過濾該對象接收到的事件。
- 在QCoreApplication::instance()上面安裝事件過濾器涨椒。該過濾器將過濾所有對象的所有事件摊鸡,因此和notify()函數(shù)一樣強(qiáng)大,但是它更靈活蚕冬,因?yàn)榭梢园惭b多個(gè)過濾器免猾。全局的事件過濾器可以看到 disabled 組件上面發(fā)出的鼠標(biāo)事件囤热。全局過濾器有一個(gè)問題:只能用在主線程猎提。
- 重寫QCoreApplication::notify()函數(shù)。這是最強(qiáng)大的旁蔼,和全局事件過濾器一樣提供完全控制,并且不受線程的限制牌芋。但是全局范圍內(nèi)只能有一個(gè)被使用(因?yàn)镼CoreApplication是單例的)躺屁。
model/view 架構(gòu)
- 總的來說,model/view 架構(gòu)將傳統(tǒng)的 MV 模型分為三部分:模型烁兰、視圖和委托沪斟。每一個(gè)組件都由一個(gè)抽象類定義主之,這個(gè)抽象類提供了基本的公共接口以及一些默認(rèn)實(shí)現(xiàn)槽奕。模型房轿、視圖和委托則使用信號槽進(jìn)行交互:
- 來自模型的信號通知視圖囱持,其底層維護(hù)的數(shù)據(jù)發(fā)生了改變纷妆;
- 來自視圖的信號提供了有關(guān)用戶與界面進(jìn)行交互的信息掩幢;
- 來自委托的信號在用戶編輯數(shù)據(jù)項(xiàng)時(shí)使用粒蜈,用于告知模型和視圖編輯器的狀態(tài)枯怖。
- 所有的模型都是QAbstractItemModel的子類度硝。這個(gè)類定義了供視圖和委托訪問數(shù)據(jù)的接口蕊程。模型并不存儲數(shù)據(jù)本身藻茂。這意味著辨赐,你可以將數(shù)據(jù)存儲在一個(gè)數(shù)據(jù)結(jié)構(gòu)中掀序、另外的類中不恭、文件中换吧、數(shù)據(jù)庫中式散,或者其他你所能想到的東西中暴拄。
- Qt 還提供了一系列預(yù)定義好的視圖:QListView用于顯示列表,QTableView用于顯示表格,QTreeView用于顯示層次數(shù)據(jù)豁鲤。這些類都是QAbstractItemView的子類琳骡。這意味著楣号,如果你要?jiǎng)?chuàng)建新的視圖類怒坯,則可以繼承QAbstractItemView剔猿。
- QAbstractItemDelegate則是所有委托的抽象基類酷含。自 Qt 4.4 依賴,默認(rèn)的委托實(shí)現(xiàn)是QStyledItemDelegate。但是扳缕,QStyledItemDelegate和QItemDelegate都可以作為視圖的編輯器躯舔,二者的區(qū)別在于粥庄,QStyledItemDelegate使用當(dāng)前樣式進(jìn)行繪制惜互。在實(shí)現(xiàn)自定義委托時(shí),推薦使用QStyledItemDelegate作為基類坑鱼,或者結(jié)合 Qt style sheets鲁沥。
- 你覺得 model/view 模型過于復(fù)雜画恰,或者有很多功能是用不到的,Qt 還有一系列方便使用的類。這些類都是繼承自標(biāo)準(zhǔn)的視圖類逞度,并且繼承了標(biāo)準(zhǔn)模型档泽。這些類并不是為其他類繼承而準(zhǔn)備的馆匿,只是為了使用方便渐北。它們包括QListWidget赃蛛、QTreeWidget和QTableWidget破托。這些類遠(yuǎn)不如視圖類靈活土砂,不能使用另外的模型,因此只適用于簡單的情形锌俱。
模型
- 模型使用索引來提供給視圖和委托有關(guān)數(shù)據(jù)項(xiàng)的位置的信息,這樣做的好處是吭练,模型之外的對象無需知道底層的數(shù)據(jù)存儲方式;
- 數(shù)據(jù)項(xiàng)通過行號分尸、列號以及父項(xiàng)三個(gè)坐標(biāo)進(jìn)行定位;
- 模型索引由模型在其它組件(視圖和委托)請求時(shí)才會(huì)被創(chuàng)建材蛛;
- 如果使用index()函數(shù)請求獲得一個(gè)父項(xiàng)的可用索引芽淡,該索引會(huì)指向模型中這個(gè)父項(xiàng)下面的數(shù)據(jù)項(xiàng)。這個(gè)索引指向該項(xiàng)的一個(gè)子項(xiàng);如果使用index()函數(shù)請求獲得一個(gè)父項(xiàng)的不可用索引纹笼,該索引指向模型的最頂級項(xiàng);
- 角色用于區(qū)分?jǐn)?shù)據(jù)項(xiàng)的不同類型的數(shù)據(jù)。
QML 語法
- 每一個(gè)屬性都可以發(fā)出信號兄猩,因而都可以關(guān)聯(lián)信號處理函數(shù)。這個(gè)處理函數(shù)將在屬性值變化時(shí)調(diào)用。這種值變化的信號槽命名為 on + 屬性名 + Changed核蘸,其中屬性名要首字母大寫。
QML 基本元素
- 最基本的可視元素:Item、Rectangle疆偿、Text、Image和MouseArea。
QML 組件
- 在 main.qml 中撤蟆,我們直接使用了Button這個(gè)組件盟猖,就像 QML 其它元素一樣反镇。由于 Button.qml 與 main.qml 位于同一目錄下,所以不需要額外的操作。但是,如果我們將 Button.qml 放在不同目錄彻磁,比如構(gòu)成如下的目錄結(jié)果:
app
|- QML
| |- main.qml
|- components
|- Button.qml
那么尘喝,我們就需要在 main.qml 的import部分增加一行import ../components才能夠找到Button組件置吓。
定位器
- QML 提供了很多用于定位的元素。這些元素叫做定位器,都包含在 QtQuick 模塊。這些定位器主要有 Row行贪、Column建瘫、Grid和Flow等崭捍。經(jīng)常結(jié)合定位器一起使用的元素:Repeater。
元素布局
- 除了定位器啰脚,QML 還提供了另外一種用于布局的機(jī)制殷蛇。我們將這種機(jī)制成為錨點(diǎn)(anchor)。錨點(diǎn)允許我們靈活地設(shè)置兩個(gè)元素的相對位置橄浓。它使兩個(gè)元素之間形成一種類似于錨的關(guān)系粒梦,也就是兩個(gè)元素之間形成一個(gè)固定點(diǎn)。錨點(diǎn)的行為類似于一種鏈接荸实,它要比單純地計(jì)算坐標(biāo)改變更強(qiáng)匀们。由于錨點(diǎn)描述的是相對位置,所以在使用錨點(diǎn)時(shí)准给,我們必須指定兩個(gè)元素泄朴,聲明其中一個(gè)元素相對于另外一個(gè)元素。錨點(diǎn)是Item元素的基本屬性之一露氮,因而適用于所有 QML 可視元素祖灰。
輸入元素
- 鍵盤輸入的兩個(gè)元素:TextInput和TextEdit。當(dāng)FocusScope接收到焦點(diǎn)時(shí)畔规,會(huì)將焦點(diǎn)轉(zhuǎn)發(fā)給最后一個(gè)設(shè)置了focus:true的子對象局扶。附加屬性Keys類似于鍵盤事件,允許我們相應(yīng)特定的按鍵按下事件。
Qt Quick Controls
- 自 QML 第一次發(fā)布已經(jīng)過去一年多的時(shí)間三妈,但在企業(yè)應(yīng)用領(lǐng)域畜埋,QML 一直沒有能夠占據(jù)一定地位。很大一部分原因是畴蒲,QML 缺少一些在企業(yè)應(yīng)用中亟需的組件由捎,比如按鈕、菜單等饿凛。雖然移動(dòng)領(lǐng)域,這些組件已經(jīng)變得可有可無软驰,但在桌面系統(tǒng)中依然不可或缺涧窒。為了解決這一問題,Qt 5.1 發(fā)布了 Qt Quick 的一個(gè)全新模塊:Qt Quick Controls锭亏。顧名思義纠吴,這個(gè)模塊提供了大量類似 Qt Widgets 模塊那樣可重用的組件。本章我們將介紹 Qt Quick Controls慧瘤,你會(huì)發(fā)現(xiàn)這個(gè)模塊與 Qt 組件非常類似戴已。
Repeater
動(dòng)態(tài)視圖
- Repeater適用于少量的靜態(tài)數(shù)據(jù)集。但是在實(shí)際應(yīng)用中锅减,數(shù)據(jù)模型往往是非常復(fù)雜的糖儡,并且數(shù)量巨大。這種情況下怔匣,Repeater并不十分適合握联。于是,QtQuick 提供了兩個(gè)專門的視圖元素:ListView和GridView每瞒。這兩個(gè)元素都繼承自Flickable金闽,因此允許用戶在一個(gè)很大的數(shù)據(jù)集中進(jìn)行移動(dòng)。同時(shí)剿骨,ListView和GridView能夠復(fù)用創(chuàng)建的代理代芜,這意味著,ListView和GridView不需要為每一個(gè)數(shù)據(jù)創(chuàng)建一個(gè)單獨(dú)的代理浓利。這種技術(shù)減少了大量代理的創(chuàng)建造成的內(nèi)存問題挤庇。
視圖代理
- 每一個(gè)代理都可以訪問一系列屬性和附加屬性。這些屬性及附加屬性中荞膘,有些來自于數(shù)據(jù)模型罚随,有些則來自于視圖。前者為代理提供了每一個(gè)數(shù)據(jù)項(xiàng)的數(shù)據(jù)信息羽资;后者則是有關(guān)視圖的狀態(tài)信息淘菩。
- 代理中最常用到的是來自于視圖的附加屬性ListView.isCurrentItem和ListView.view。前者是一個(gè)布爾值,用于表示代理所代表的數(shù)據(jù)項(xiàng)是不是視圖所展示的當(dāng)前數(shù)據(jù)項(xiàng)潮改;后者則是一個(gè)只讀屬性狭郑,表示該代理所屬于的視圖。通過訪問視圖的相關(guān)數(shù)據(jù)汇在,我們就可以創(chuàng)建通用的可復(fù)用的代理翰萨,用于適配視圖的大小和展示特性。
模型-視圖高級技術(shù)
- PathView是 QtQuick 中最強(qiáng)大的視圖糕殉,同時(shí)也是最復(fù)雜的亩鬼。PathView允許創(chuàng)建一種更靈活的視圖。在這種視圖中阿蝶,數(shù)據(jù)項(xiàng)并不是方方正正雳锋,而是可以沿著任意路徑布局。沿著同一布局路徑羡洁,數(shù)據(jù)項(xiàng)的屬性可以被更詳細(xì)的設(shè)置玷过,例如縮放、透明度等筑煮。
- 模型視圖的性能很大程度上取決于創(chuàng)建新的代理所造成的消耗辛蚊。
- 應(yīng)該使每個(gè)代理中包含的 JavaScript 代碼盡可能少。最好能做到在代理之外調(diào)用復(fù)雜的 JavaScript 代碼真仲。這將減少代理創(chuàng)建時(shí)編譯 JavaScript 所消耗的時(shí)間袋马。
Canvas
- 為了使用腳本化的繪圖機(jī)制,Qt 5 引入的Canvas元素袒餐。Canvas元素提供了一種與分辨率無關(guān)的位圖繪制機(jī)制飞蛹。通過Canvas,你可以使用 JavaScript 代碼進(jìn)行繪制灸眼。
- Canvas元素的基本思想是卧檐,使用一個(gè) 2D 上下文對象渲染路徑。這個(gè) 2D 上下文對象包含所必須的繪制函數(shù)焰宣,從而使Canvas元素看起來就像一個(gè)畫板霉囚。這個(gè)對象支持畫筆、填充匕积、漸變盈罐、文本以及其它一系列路徑創(chuàng)建函數(shù)。
粒子系統(tǒng)
- 粒子系統(tǒng)是一種計(jì)算機(jī)圖形學(xué)的技術(shù)闪唆,用于模擬一些特定的模糊現(xiàn)象盅粪,這些現(xiàn)象用傳統(tǒng)的渲染技術(shù)難以達(dá)到一定的真實(shí)感。雖然名為“粒子”悄蕾,但卻可以模擬爆炸票顾、煙础浮、水流、落葉奠骄、云豆同、霧、流星尾跡或其它發(fā)光軌跡這樣的抽象視覺效果含鳞。粒子系統(tǒng)的特色是“模糊”影锈,其渲染效果并非完全取決于像素,而是使用特定的邊界參數(shù)描述隨機(jī)粒子蝉绷。
QML 存儲
- Qt 5.2 起鸭廷,QML 引入了一個(gè)新的類Settings,顧名思義熔吗,它就是QSettings的 QML 版本靴姿。值得注意的是,直到目前最新的 Qt 5.5.1磁滚,Settings依然是試驗(yàn)性質(zhì) API,所以宵晚,它的 API 可能會(huì)在未來版本中有所變化垂攘。使用Settings需要添加import Qt.labs.settings 1.0語句。
- Qt Quick 借鑒了 localStorage API淤刃,提供了類似的解決方案晒他,名字也被稱為 LocalStorage。為了使用該 API逸贾,需要添加語句import QtQuick.LocalStorage 2.0陨仅。
- LocalStorage 使用 SQLite 數(shù)據(jù)庫保存數(shù)據(jù)。這個(gè)數(shù)據(jù)庫的文件按照給定的數(shù)據(jù)庫名字和版本保存在系統(tǒng)的指定位置铝侵,使用唯一 ID 標(biāo)識灼伤。但是,系統(tǒng)并不允許列出或刪除已創(chuàng)建的數(shù)據(jù)庫咪鲜『模可以使用 C++ 的QQmlEngine::offlineStoragePath()函數(shù)查看數(shù)據(jù)庫文件存儲路徑。
使用 C++ 擴(kuò)展 QML
- 擴(kuò)展 QML 一般有三種方式:
- 上下文屬性:setContextProperty()
- 向引擎注冊類型:在 main.cpp 調(diào)用qmlRegisterType
- QML 擴(kuò)展插件