原本是想從如何創建GacUI的VC++工程開始寫的,不過最近網友廣泛反映,怎麼建工程都能從 Tutorial 裏面看明白,就打算說到GacUI的XML資源怎麼編譯的時候在順帶講一下。今天主要說的是GacUI的體系架構。html
在構造一個GacUI工程的時候, CppXml 和 MVVM 是兩個推薦的Hello World參考項目。雖然還有一些其餘的方法,但那並非使用GacUI的正確方法,那些Demo只是爲了添加知識作出來的。git
見:http://www.gaclib.net/Document.html#~/ 。這個網站host在Windows Azure上。若是要訪問host在github io上面的鏡像網站,可使用 http://vczh-libraries.github.io/Document.html#~/ 。程序員
這個網頁裏面的全部內容其實都在代碼頭文件的註釋裏,而後我寫了一個工具把他們都抽了出來,作成了這個網頁。至於爲何不用現成的工具,其實我一開始是直接使用VC++的XML註釋功能的。VC++在編譯的時候會幫我作完全部的事情,並且在開發的時候還會把註釋寫進intellisense裏面,特別容易使用。github
可是在這個過程當中,我痛苦地發現,VC++要直接生成chm的話,必須使用託管項目。顯然GacUI並非託管項目,並且他要求託管項目的緣由,僅僅是爲了讀取exe/dll裏面的元數據。那麼Native C++項目的元數據,固然就只能去pdb找了。因而我使用了VC++自帶的一個處理pdb讀寫的COM庫,把這個事情給作出來了。設計模式
後來我又痛苦地發現,VC++的XML文檔,只給託管項目提供對模板的支持。我把註釋寫在模板類上,VC++在編譯出最後的XML註釋彙總文件的時候,寫在模板上面的直接沒有了!因而我只好放棄使用VC++提供的工具鏈,轉而本身使用C#作了一個C++頭文件的parser,從而解決了全部的問題。若是你們感興趣的話,這個文檔生成工具能夠在 這裏 找到。瀏覽器
不過我寫的文檔生成工具,只是產生了一系列的帶元數據的XML註釋文件。實際上這個網頁是另外寫的。網頁會在訪問者點擊了一個類的超連接以後,去後臺獲取相應的XML,而後cache在客戶端內存裏。因此你點擊第二次的話不須要從新加載——直到你關閉了這個網頁。安全
爲何整個文檔只有一個網頁呢?由於那個時候我正在學習如何正確使用Javascript和CSS,就順便練了練手。另外一個緣由是,我想把網站host在github io上,可是github io又不能後臺跑程序,因此只好痛苦的使用Javascript把ASP.NET MVC裏面我喜歡的部分作了出來,所有邏輯跑在瀏覽器裏。數據結構
能夠在兩個地方得到GacUI的源文件。架構
第一個是Release。Release的正確下載方法是到 Release repo的Release頁面 獲取代碼。目前Linux版本正在開發,OSX基本已經完工了,不過他們尚未集成到同一個Release repo下面。因此須要到 XGac 和 iGac 兩個repo下載代碼。app
Windows版本的Release會包含如下幾個文件,他們都是成對的.h和.cpp文件:
Vlpp:跨平臺的C++基礎庫。
Workflow:一個能夠靠反射訪問C++類的腳本引擎,對象模型使用引用計數,跟C++的互操做性無比的好。
GacUI:GacUI的主要部分。
GacUIReflection:GacUI全部能夠被腳本訪問的類的元數據,用於反射。若是不連接這個文件的話,那麼在初始化的時候,構造元數據的過程將被跳過。用戶不須要寫額外的代碼來明確這個過程,只須要選擇連接或者不連接這個文件就能夠了。
GacUIWindows:GacUI在Windows平臺下面的Window Provider和Renderer。
目前你須要使用全部的文件,由於GacUI會把XML資源直接編譯成Workflow腳本引擎的字節碼,嵌入到生成後的二進制資源文件裏。在構造一個窗口的時候,實際上就是在跑腳本。儘管Workflow腳本遠沒有C++快,可是一個窗口再複雜也複雜不到哪裏去,因此加載的時間難以察覺。
明年即將推出把GacUI的XML資源直接編譯成C++的選項,到時候能夠無限縮小文件體積,不再須要帶上GacUIReflection裏面的元數據了,那麼Workflow和GacUIReflection在大部分狀況下就都不須要了。不過若是你的程序想要支持插件,那天然沒法使用這個功能。
上面的每個文件都十分的大。我這樣作純粹是爲了程序員的使用方便。程序員只須要根據需求連接不一樣的文件就能夠了,而不須要把整棵目錄樹都拖進來。不過我本身在開發的時候,顯然不可能直接寫這些文件的。這些文件是我在作Release的時候,調用我寫的一個命令行工具拼裝出來的。包括上面提到的文檔也是。
因此若是須要閱讀GacUI的代碼的話,應該分別去 Vlpp 、 Workflow 和 GacUI 三個repo。
事實上GacUI的架構是分層的,從底層到頂層分別是:
Window Provider
Renderers
Elements + Compositions
Controls + Templates
XML Resource Compiler
Window Provider指的是如何操縱操做系統提供的原生的窗口、圖片資源、鼠標資源、異步原語和一些其餘的東西。畢竟GacUI怎麼作,最頂層的窗口是沒辦法本身作的,最多經過Template來替換掉窗口邊框。把GacUI移植到Linux和OSX的工做,主要就是寫兩個新的Window Provider,而後提供各個平臺上不一樣渲染器的Renderer。其餘的部分都是平臺共享的代碼。
Renderers跟Window Provider是分開的。畢竟同一個操做系統上你可使用不一樣的圖像技術來繪圖,而不一樣的操做系統上你可使用相同的圖像技術來繪圖。舉個例子,OpenGL和Cairo就是在不少平臺上能夠用的。不過OpenGL在每個平臺上都是二等公民,因此我並無真的使用OpenGL來開發。一個GacUI程序在剛開始的時候,若是是Windows的話就是在WinMain函數裏,須要首先選擇一個Renderer,選擇了以後就不能變了。
目前GacUI在不一樣的操做系統上使用的繪圖技術以下所示:
Windows : GDI, Direct2D 1.0, Direct2D 1.1
Linux : Cairo + Pango
OSX : CoreGraphics + CoreText
Direct2D的1.0和1.1的版本雖然只有初始化的代碼有區別,不過這關係到能不能直接跟Direct3D 11.0攪在一塊兒,因此單獨拿出來說了。目前GacUI在Windows上,若是你選擇了使用Direct2D技術:
WinMain.cpp
#include <GacUI.h> #include <Windows.h> int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow) { return SetupWindowsDirect2DRenderer(); }
那麼在GacUI初始化的時候會優先選擇Direct1.1。若是在代碼裏面引用了GacUIWindows.h,那麼你還能夠獲得每一個窗口所使用的系統相關的對象,可讓你在不得不使用非跨平臺技術的時候,提供一個機會。
在GacUI裏面Element和和Composition分別表明基礎的圖元和排版功能。每個Element運行在具體的平臺的時候,都須要具體的Renderer對象。這些對象是前面兩層合做提供的。
Composition是GacUI的其中一個重要部分。這個部分提供了全部的排版功能。一個Composition對象表明了窗口上的一個長方形的區域。每個區域能夠嵌入一個Element。當一個Composition肯定了他的位置的時候,那麼Element會被填充到整個長方形的區域裏面,從而渲染出來。
幾乎全部的Element都是很簡單的幾何圖形,除了渲染文字的 GuiColorizedTextElement 和 GuiDocumentElement 。不過在製做控件皮膚(也就是Template的一部分功能)的時候,文本框控件因爲功能的複雜性,皮膚須要提供一個區域讓控件放置這兩個Element,而不是跟普通的控件同樣,全權處理全部的渲染對象。
目前Composition支持直接定位、Stack、Table、Flow和一些其餘的功能。一個比較特殊的就是,使用 GuiDocumentElement 的 GuiDocumentLabel 和 GuiDocumentViewer 能夠在富文本文檔的中間嵌入Composition。這個功能是其餘的Element所不具有的。所以這兩個控件構成了一種新的排版方法。
Element和Composition的具體介紹將在之後的文章中提供。
Control的結構比較複雜。一個典型的GacUI的Control,包含了用來表明控件自己的操做和數據邏輯的Control對象,和包含了如何渲染這個空間的IStyleController或者IStyleProvider對象。IStyleController擁有整個Composition和Element的控制權。若是當一個Control只決定讓皮膚控制一部分的Composition和Element的時候,那麼他會提供IStyleProvider對象。
不過在開發的時候,程序員不須要區分IStyleController和IStyleProvider,由於使用XML來編寫皮膚的時候,都是使用Template來編輯。最後每一個Template會本身去找一個合適的wrapper對象來把本身wrap成IStyleController或者IStyleProvider而後提供給Control。IStyleController / IStyleProvider 和 Template的區別,就在於一個是Pull模型的,一個是Push模型的。Push模型作data binding特別容易,所以在XML裏面都是經過建立Template對象來修改一個Control是如何渲染的。
每個Control類都有本身相應的Template類。
對於列表控件、譬如 GuiTextList、GuiListView、GuiTreeView 和 GuiVirtualDataGrid 等,除了Template之外,還有ItemTemplate。Template和ItemTemplate是能夠分開指定的。Template確立了整個控件的外觀,而ItemTemplate肯定了每個列表項的外觀。
若是須要對容器的內容作數據綁定的話,那麼須要使用上述4個控件的Bindable版本,分別是它們的子類:GuiBindableTextList、GuiBindableListView、GuiBindableTreeView 和 GuiBindableDataGrid 。在使用這些控件的時候,能夠經過在XML裏面的Workflow腳本——其實一般就是
<GuiBindableListView ItemSource-eval="ViewModel.Something"/>
這種簡單的表達式——把一個C++的容器對象綁定到ListView上。每一個ListViewItem拿到的容器的每個對象,可能最終類型是不同的。GacUI還提供了一個功能,你能夠經過給ListView的ItemTemplate指定一系列的Template對象,經過在XML裏面寫好的這些Template的構造函數的參數的類型,來讓ListView決定到底要使用哪一個Template。因而一個異構的列表就這麼輕鬆的造出來了。
大家可能會注意到,Control並不在這一層裏面。這是正確的。由於整個窗口就是由Element和Composition共同組成的一張超大的動態矢量圖。每個Control負責管理這顆Composition樹的一些子樹,每個Control會告訴你他最外層的Composition和用來作容器的Composition分別是什麼,而後把Control放進Composition、把Composition放進、把Control放進Control的這些動做,實際上都是在操做Composition。在實際的代碼裏面,你的確也是首選獲取Control相應的Composition,而後去操做Composition的。
所以Control和Composition並非平級的,你能夠認爲Control對於Composition使用了Builder和Facade模式,讓你更容易的操做GUI。
固然這種作法對整個GacUI對象的生命週期會有一些影響。當你在C++代碼裏面delete一個Composition的時候,他會把下面的全部Composition子節點一塊兒delete。當你在C++代碼裏面delete一個Control的時候,他會把下面全部的Control子節點,還有對應的全部Composition一塊兒delete。
因此這個時候就會有一個疑問,那delete一個Composition的時候,若是Composition子節點上有Control怎麼辦?爲了解決這個問題,我提供了這樣的兩個函數:SafeDeleteComposition 和 SafeDeleteControl 。
另外值得一提的是,若是你直接delete一個Control(一般狀況下是你用完了一個 GuiWindow 直接把它刪掉),他會先刪掉整棵Composition樹,而後再刪除Control樹。因此本身開發的Control在析構函數裏面,千萬不能訪問Composition,不然直接GG。
GacUI目前提供的XML資源文件,支持讓你構造Window、UserControl、Template、相似CSS那樣的InstanceStyle(主要經過XPath來批量設置XML的屬性,比選擇器好用多了,並且精確控制起來更不費腦)和一些共享的Workflow腳本。共享的Workflow腳本能夠用來定義一些窗口的邏輯代碼,還有MVVM模式須要的ViewModel的接口和數據結構。
當你準備好一個XML資源的時候,Release裏面提供的GacGen.exe會幫你把XML資源編譯成一個二進制的資源文件,還有一系列的C++代碼。生成的C++代碼模擬了C#的partial class的能力,讓你能夠像Windows Forms同樣,準備控件的事件處理,還有在窗口初始化的時候作一些任務等等。並且當你的XML須要更新的時候,GacGen.exe從新生成的C++代碼會跟你修改後的那部分自動合併。
使用Workflow腳本寫的ViewModel相關的接口和數據結構,也會被一併生成C++代碼。在構造一個帶有MVVM模式的窗口的時候,你只須要繼承一下ViewModel接口,而後把這個類的實例當作窗口的參數填進去就行了。全部生成的代碼都是強類型的,若是你對象給錯了,會直接沒法編譯。特別安全。
目前GacUI把全部的用來構造窗口的那部分XML,在編譯以後都轉成了Workflow腳本的字節碼,寫進了二進制資源文件裏面(這項功能將包含在即將到來的下一個Release裏面)。窗口在初始化的時候,會去資源文件裏面找到相應的腳原本運行,從而按照要求建立控件和data binding。
在後續的開發過程當中,我還將爲XML資源提供Visual State、Animation、State Machine和多語言字符串資源等重要部件。明年還計劃讓Workflow腳本能夠被編譯成C++,不只能夠大幅度的提升編寫出來的GacUI程序的性能,還能夠經過讓你不再須要連接GacUIReflection,讓你的二進制文件的尺寸縮小到1/8。
固然了,仍是會有一小部分狀況是沒法讓你徹底放棄在二進制文件裏面帶元數據的,舉個例子,若是你編寫出來的程序須要支持帶GUI的插件,那麼爲了加載那些已經被編譯成二進制資源的、在發佈了以後用戶自行製做的GacUI窗口,那你仍是要保留反射的功能。不過這種需求在廣大的GUI程序裏面仍是比較罕見的。
值得一提的是,GacUI的data binding的功能十分強大,你可使用任何知足語法要求的Workflow腳本表達式(基本上就像C#同樣豐富)來從ViewModel和控件之間作數據綁定。舉個簡單的例子,你徹底能夠寫三個文本框,而後讓第三個文本框永遠等於前兩個文本框的數字之和,而且在輸入錯誤的狀況下報錯:
<SinglelineTextBox ref.Name="textBox1" Text="0"/> <SinglelineTextBox ref.Name="textBox2" Text="0"/> <SinglelineTextBox Text-bind="(cast int textBox1.Text) + (cast int textBox2.Text) ?? '<ERROR>'" />
這個例子要用WPF或者其餘GUI框架來寫就很蛋疼。那麼我在編譯XML資源的時候是怎麼處理這個表達式的呢?其實這主要使用了語言愛好者們很是熟悉可是老是搞不明白的CPS變換(跟各類語言的玄乎的coroutine在編譯的時候其實使用了基本相同的手法),而後把這種pull的代碼轉變成push的代碼,這樣就能夠在textBox1的TextChanged發生的時候,跟換存起來的其餘沒有變化的屬性的計算後的值(如cast int textBox2.Text)一塊兒,作最少的計算,最後寫到第三個控件的Text屬性裏面。
你還能夠在這個表達式裏面引用你在資源文件裏面提供的Workflow腳本,或者乾脆引用你本身用C++寫的類和庫函數,來幫助你作一些不屬於ViewModel可是卻十分蛋疼的、GUI相關的功能。舉個例子,寫一個統計學生成績的程序,你可能須要給學生分優良中差。顯然如何描述一個等級,使用中文和英文的方法就不同。然而這並非ViewModel的功能,ViewModel應該只負責計算等級,而後GUI再根據用戶使用的系統所提供的語言信息來決定到底要如何顯示。
這部分你就能夠從ViewModel中間分離開,獨立的寫成Workflow腳本、XML資源或者C++代碼,從而在定義窗口的XML裏面使用。這樣整個架構分層清晰、測試起來容易、並且需求變動的時候還特別好改。
這篇文章主要介紹了在使用GacUI的過程當中須要瞭解的一些關於GacUI的體系架構的知識。裏面的每個知識點都會陸續在接下來的文章裏面詳細描述。除此以外,我還會偶爾寫一些文章來介紹GacUI的、外部不可見的、跟實現緊密相關的內部架構,以及須要用到的一些編譯原理、設計模式等知識,敬請關注。