全局變量糊肠,顧名思義就是在程序中到處都能使用的變量皆看。這在一定程度上違背了“模塊化設(shè)計”這個思想龙誊。在筆者剛接觸編程的時候老師就說過全局變量有害,就跟goto
一樣春哨;但在實際工程中它其實很有用,使用得當(dāng)?shù)脑挿炊茏屨麄€軟件結(jié)構(gòu)更清晰恩伺、緊湊赴背。本文結(jié)合實際經(jīng)驗向大家介紹在QML程序中如何有效使用全局變量。
全局變量的作用
首先要說明的是晶渠,我們這里說的全局變量不是整數(shù)凰荚、浮點數(shù)這樣的簡單變量,而是復(fù)雜的類對象褒脯。那什么時候會用到全局變量呢便瑟?在筆者的實踐中,一般下面幾種情況會用到全局變量:
- 資源共享番川、重用到涂。整個應(yīng)用程序相關(guān)的設(shè)置脊框,比如說程序的版本、風(fēng)格(theme)践啄、字體資源等浇雹,這些數(shù)據(jù)適合放入一個全局變量,從而可以在整個程序的任何地方反復(fù)使用屿讽;
- 數(shù)據(jù)傳遞昭灵。全局變量在代碼里都能訪問到,相當(dāng)于一塊共享的內(nèi)存空間伐谈,所以可以在不同的地方傳遞數(shù)據(jù)烂完。但如果是多線程環(huán)境下的話,需要考慮好互斥诵棵;
- 事件驅(qū)動窜护。我們可以將該變量申明為
QObject
子類,然后定義好信號與槽非春,然后在程序需要的地方連接這些信號或者調(diào)用槽函數(shù)柱徙,這樣我們的程序就通過這個全局變量連接起來了。這就相當(dāng)于有了一個核心驅(qū)動奇昙,比如只要一個信號發(fā)出來护侮,程序中所有連接的槽函數(shù)就都會被調(diào)用,而無需關(guān)心是否漏掉了一個储耐。
這幾個特點加起來其實也就是Facebook所推崇的Flux設(shè)計模塊羊初,核心思想簡單講就是把程序的層級關(guān)系變得簡單,單一驅(qū)動什湘。如果你被綜錯復(fù)雜的模塊化設(shè)計弄糊涂了长赞,那可以試試Flux這種清晰明了的設(shè)計思路。
和C++中的使用全局變量相比闽撤,QML中使用起來更加方便得哆,因為QML中有屬性綁定,尤其是上面第三點哟旗,確實可以讓我們的程序“智能化”很多贩据。同時也要說明的是,這里說的“全局”并不是真的全局闸餐,而是指QML執(zhí)行環(huán)境中的全局饱亮;C++中的全局變量不在本文討論范圍之內(nèi)。
QML中定義全局變量
我們知道QML是需要QML引擎(即QQmlEngine
)來解釋執(zhí)行的舍沙,所以QML中的全局變量本質(zhì)是QML執(zhí)行上下文(QQmlContext
)的屬性近上。定義QML全局變量也就是把我們的對象設(shè)置為QML執(zhí)行上下文的屬性。
有兩種方式:單獨定義拂铡,或者批量定義壹无,其中批量定義又可分為C++形式和QML形式葱绒。我們把這些方法都介紹下。
單獨定義
該方法主要步驟是:
定義一個
QObject
的子類格遭,設(shè)計好它的信號哈街、槽還有屬性;在
main
函數(shù)里構(gòu)建對象拒迅;-
在
QQmlEngine
構(gòu)建之后還未加載任何QML文件之前骚秦,將該對象設(shè)置為執(zhí)行上下文的屬性,并取一個合理的名字:engine->rootContext()->setContextProperty("$hub", cppBackend);
這樣$hub
就成為了QML中的全局變量璧微,你可以直接使用它內(nèi)部的各種元數(shù)據(jù)(信號作箍、槽、屬性前硫、枚舉類型等等)胞得。
這里我們約定,用$
作為全局變量的開頭字母屹电,這個在JavaScript和QML中是合法的阶剑,便于我們區(qū)分普通局部變量和全局變量。
C++形式批量定義
如果我們的程序比較復(fù)雜危号,把功能都放在一個全局變量里不合適牧愁,我們可以將它們拆開來,用不同的C++類來實現(xiàn)外莲,然后定義一個總的C++類猪半,將這些功能類作為這個總類的屬性,主要步驟是:
-
根據(jù)功能定義不同類偷线,例如:
程序設(shè)置類:class Settings : public QObject{ Q_OBJECT public: Q_PROPERTY(QString appName MEMBER m_appName) private: QString m_appName = "MyApp"; }
和網(wǎng)絡(luò)類:
class Networks : public QObject{ Q_OBJECT }
-
定義一個總的類:
class GlobalObject : public QObject{ Q_OBJECT public: Q_PROPERTY(Settings* settings MEMBER m_settings) Q_PROPERTY(Networks* networks MEMBER m_networks) private: Settings* m_settings; Networks* m_networks; }
-
在
main
函數(shù)中創(chuàng)建總類的對象:auto globalObject = new GlobalObject();
-
在
QQmlEngine
構(gòu)建之后還未加載任何QML文件之前磨确,將該對象設(shè)置為執(zhí)行上下文對象:engine->rootContext()->setContextObject(globalObject);
QML中約定,contextObject
的所有屬性都自動變?yōu)?code>contextProperty声邦,就像他們用第一種方法單獨定義一樣乏奥。所以如果需要的功能比較多,建議使用批量定義的方法翔忽,更方便快捷英融。
需要注意,如果一個程序中同時使用了這兩種方法定義全局變量而且有變量重名了歇式,那么以單獨定義的優(yōu)先。
QML形式批量定義
可以用QML文件來代替上面的C++總類胡野。
在定義好Settings
和Networks
之后材失,接下去的步驟改為:
-
將這些C++類注冊到QML中:
qmlRegisterType<Settings>("MyCppBackend", 1, 0, "Settings"); qmlRegisterType<Networks>("MyCppBackend", 1, 0, "Networks");
-
然后新建一個QML文件:
//globalobject.qml import QtQuick 2.7 import MyCppBackend 1.0 QtObject { id: root property var $settings: Settings{} property var $networks: Networks{} }
-
將該QML文件作為QML引擎加載的第一個文件:
engine->load(QUrl("qrc:///globalobject.qml"));
QML引擎約定,加載的第一個QML文件就是contextObject
硫豆,所以和C++定義類似龙巨,它的屬性也就成了contextProperty
笼呆。
和C++批量定義相比,QML批量定義有如下優(yōu)勢:
變量名前面可以加
$
旨别,從而方便區(qū)分全局變量和局部變量诗赌,這個在C++定義屬性的時候是不允許的;-
如果某個全局變量(一般是QML對象)構(gòu)造很慢秸弛,可以通過QML中的
Loader
來很方便異步構(gòu)造铭若,從而加速程序啟動:property var loader: Loader{ asynchronous: true source: "qrc:/UI/Main.qml" onLoaded: { // 當(dāng)加載完畢會進(jìn)入這里 } }
QML全局變量的中樞作用
最后我們結(jié)合批量定義中的例子來看下QML中全局變量起的數(shù)據(jù)、消息中樞作用递览。這個主要是利用了QML的屬性綁定特性(Property Binding)叼屠。
假如說我們有兩個QML文件:
//View1
import QtQuick 2.7
Item{
id: root
Text{
text: $settings.appName
}
}
和:
//View2
import QtQuick 2.7
import QtQuick.Controls 2.2
Rectangle{
id: root
TextField{
text: $settings.appName
}
Button{
text: "Click!"
onClick:{
$settings.appName = "New Name!";
}
}
}
View1
和View2
中的text都和$settings
中的appName
這個屬性做了綁定。當(dāng)我點擊View2
中的按鈕绞铃,$settings.appName
被修改镜雨,所有綁定的屬性也就會自動更新,不會遺忘儿捧。由于$settings
是全局變量荚坞,這種用法可以深入到任意復(fù)雜、任意層級的界面中菲盾,非常方便颓影。