UI神器-SOUI

  • 前言 
在Windows平臺上開發客戶端產品是一個很是痛苦的過程,特別是還要用C++的時候。
儘管不少語言不少方法均可以開發Windows桌面程序,目前國內流行的客戶端產品都是C++開發的,好比QQ,YY語音,迅雷等。
快速,穩定是我認爲的應用軟件開發框架最基本的要求,對於UI還有兩個要求就是界面美觀,配置靈活。
C++語言知足了快速的要求,傳統的客戶端軟件開發框架如MFC,WTL等知足了穩定的要求。然而界面美觀,配置靈活是MFC,WTL這樣的開發框架所不能知足的。
騰訊是作客戶端發家的,他們的UI經驗積累很是好,有本身專門的UI框架;迅雷有一個專業的團隊開發本身的UI框架;然而大多數公司只但願有一個可以快速完成項目開發的UI庫來使用,它們沒有專業的團隊來維護UI庫。國企有錢任性,因此成就了UIPower:一個商業化的DirectUI庫(具體怎麼樣很差說,優勢在於有人給你服務),通常的小公司沒有誰願意當這個冤大頭。這就是Duilib這樣一個簡單到簡陋的UI庫(請原諒我這樣說)爲何這樣流行的緣由(百度一下Duilib就知道它有多少人在用)。
Duilib基本知足了界面美觀 ,配置靈活的需求,然而因爲框架自己的限制,要實現複雜的效果將不可避免的遇到各類坑。好在Duilib代碼量不多,隨便一個有經驗的UI開發工程師都可以相對容易的使用並修改它,因此在通常的應用中使用並不會有太大的問題,這也應該是爲何會有那麼多的Duilib變種的緣由:每個使用它的公司或者我的都會有一份獨一無二的副本。
其實上面我還漏了說QT, QT在國外有專業的團隊維護,文檔也很好,但至少有兩個缺點:一、它是跨平臺的,跨平臺便是優勢,也是缺點,爲了實現跨平臺,不少時候須要作出取捨,就算抽象的100%的完美,它也不可避免的帶來體積龐大;二、代碼量太大,普通人很難駕馭:就算是看懂都不容易,更別說修改了,這樣的結果就是一旦在使用中遇到問題你惟一的選擇就是提交BUG給QT開發小組等待補丁(要知道不存在沒有BUG的產品)。
  • SOUI是什麼?

SOUI是一套和Duilib相似的開源C++ UI開發框架。它的祖宗是金山衛士開源版本中使用的UI庫Bkwin,以後由啓程軟件(也就是我了)開發維護升級爲Duiengine,最後歷經屢次重構更名爲SOUI,寓意「瘦UI」,「UI, just so so!」。使用MIT開源協議,公司、我的兼可免費做用,只須要發佈時帶上SOUI的license。

  • SOUI代碼的獲取
SVN:  http://code.taobao.org/svn/soui2/trunk 不要在瀏覽器中打開該網址,只能使用SVN客戶端簽出。
GIT: https://github.com/setoutsoft/soui 
 
  • SOUI界面效果


  • SOUI的特色

使用層:高速,穩定,美觀,可配置
代碼層:精心設計,模塊低耦合,插件化設計,對象可靠的命令週期管理,相似WTL的編碼方式,現代化的事件處理模型及優異的擴展能力。
代碼量:核心模塊代碼量4W+,編譯後DLL Release版本在900K左右。得益於精心組織的代碼框架,雖然代碼量較Duilib這樣的UI庫有比較大的提升(核心框架更完善,控件更多,註釋量更大),可是閱讀代碼仍是很輕鬆的(大量實際用戶的親身體驗)。
高速主要體如今3個方面: 
一、框架設計扁平化,層次簡單(和QT相比):從宿主窗口收到消息到控件響應消息只有一箇中間層。
二、簡單有效的刷新策略:經過對剪裁區及刷新時機的有較控制, 能有效的提升刷新效率。
三、高效的渲染引擎:經過 將渲染引擎接口化,成功的將skia渲染引擎引入到SOUI中,Skia是Google的Chrome的渲染引擎,Chrome比IE渲染速度快,Skia功不可沒。
穩定性方面,SOUI脫胎於Bkwin,再通過本人的不斷精心重構,已經在多個大量用戶的產品中應用,包括最近開發的瑞雪醫生客戶端,多玩魔盒2.0, Dota2遊戲盒子及多玩多個遊戲盒子中使用, 及百度雲管家的大部分界面。
百度雲管家聽說最初使用的是騰訊QQ界面庫的早期版本(無從考證),然而QQ界面庫大量使用COM技術,擴展很是麻煩,使用非常不便,在後續的UI需求中開始大量使用SOUI的前身DuiEngine。

美觀方面,SOUI原生支持Alpha通道,可以實現各類半透明效果,包括主窗體半透明,DUI窗口半透明,DUI窗口模仿LayeredWindow(分層窗口)效果等,輕鬆實現各類異形效果。

可配置方面,SOUI中全部UI資源都採用XML描述,調整UI效果通常只須要修改XML資源便可完成。

說到代碼層的設計很難用語言描述,只有親自閱讀代碼方能理解。爲大部分須要在外部(APP層)常常引用的UI相關對象提供引用計數設計可以有效減小C++開發中常見的野指針問題,這一點仍是很好體會,同時系統中也重點解決了如消息分發的分層設計,窗口對象的消息重入等影響UI使用體驗的關鍵性問題。

  • SOUI亮點
寬泛的說SOUI多好你們並沒直觀的感受,下面從一些具體的點來介紹SOUI。
界面佈局
也許初學者對於SOUI的佈局還不太適應,特別是對於那些習慣了Duilib的佈局方式的朋友。事實上SOUI的佈局應該是最接近程序思惟的佈局方式。前段時間開發Android,僅僅是它的5大Layout就能讓人崩潰,並且不一樣的layout對應的佈局屬性還不同。
SOUI的佈局很是簡單,只有兩個佈局屬性:pos + offset,具體參考博客: http://www.cnblogs.com/setoutsoft/p/3925952.html
一般使用一個pos屬性就解決佈局問題了,pos在XML中使用"x1,y1,x2,y2"這樣的4個座標定義一個控件在父窗口中的相對位置,而offset則定義經過pos計算出來的位置後在X,Y兩個方向須要疊加的偏移,偏移值須要乘上窗口大小。
例以下面這個需求:
 
只知道窗口須要靠右下角,不知道窗口大小的狀況,在SOUI中只須要使用屬性pos=「-20,-30」 offset="-1,-1"便可。


渲染流程
 一個UI中的界面元素最後會經過各級子窗口造成一個樹狀結構。通常的渲染流程天然是從根節點一層一層的直到渲染完成全部葉結點。這個過程很簡單,可能不少UI庫也就作到這個層次(例如DuiLib)。可是對於一個高性能的UI庫僅作到這個層次是不夠的,舉例來講:一個畫筆程序須要在OnMouseMove裏面繪製新拾取的線條,本能的作法是獲取窗口畫布,繪製完成後再提交畫布(相似Windows API: GetDC and ReleaseDC),而不是每一次繪製只能請求宿主刷新(請求宿主當即刷新依賴於系統對UpdateWindow這個API的響應速度)。
所以一個成熟的UI引擎有必要實現GetDC及ReleaseDC這樣的接口。和基於HWND的窗口獲取HDC不一樣,在一套DirectUI系統中實現GetDC及ReleaseDC要更加複雜:最關鍵的問題在於獲取前繪製窗口的背景,以及提交後繪製窗口的前景,要實現窗口背景前景的分開繪製又須要系統提供繪製在指定Z-Order範圍內的窗口的能力,固然前提是系統中有Z-Order這樣的概念。
就算實現了窗口的背景與前景的分別繪製,對於一個高性能的UI引擎可能仍是不夠的。由於有些時候一個窗口中的內容是不須要和背景混合的,窗口刷新的時候繪製背景是沒有意義的(如視頻播放窗口),就是須要另外一種技術:窗口的跨層渲染(不知道這樣命名是否是合適)。當一個視頻窗口須要刷新的時候,它的刷新流程和基本的刷新流程是不同的,渲染時它會跳過它的全部父窗口直接到這個窗口層來,從而大大加速渲染過程。

分層窗口
Windows的分層窗口是Windows 2000提供的一項重要更新。蘋果系統的UI很漂亮,有了分層窗口,Windows系統上開發的應用也能夠一樣漂亮。
這裏說的分層窗口有兩個層次:一個是DirectUI的宿主窗口中使用分層窗口技術;另外一層是在DirectUI的DUI窗口系統內部實現分層窗口技術。
使用分層窗口技術聽起來比較簡單,不就是設計一個WS_EX_LAYEREDWINDOW屬性再使用UpdateLayeredWindow(EX)更新窗口嗎?!若是SOUI只達到這個層次,那和codeproject上隨便找一個demo也沒有什麼區別。
首先要搞清楚,SOUI是一套DirectUI系統,而不是Demo,所以它不能停留在加載一個32位PNG圖片並顯示出來這樣的層次上。它必需要可以讓用戶可以調用各類繪製圖形,圖像,文字的API來組合出一個最終須要呈現的32位位圖。這一點要求看起來簡單,在Windows系統上實現起來並不簡單,由於Windows上最基本的繪製API(GDI)都是不支持alpha通道的。有一個簡單的選擇:GDIPlus。然而GDIPlus有一個毛病就是速度太慢,這對於一個通用的UI引擎來講,所有依賴GDIPlus基本上就宣判了這個引擎的死刑。在SOUI中採用渲染引擎抽象的方法實現了兩種渲染引擎:Skia + GDI。前面不是說GDI不支持Alpha經過不能用嗎?沒錯,直接用GDI函數是不行的,咱們須要適當的改造(具體方法參見代碼)。
解決了繪製方法,要更新到窗口中顯示也仍是有技巧的。有人可能知道,使用UpdateLayeredWindow這個API更新的窗口將收不到WM_PAINT消息。因爲在半透明窗口中不能直接支持有窗口句柄的子窗口的顯示(如IE控件),SOUI還必須爲那些須要容納窗口句柄子窗口的狀況提供支持,即經過配置同時支持半透明窗口與不透明窗口。可是我不肯意爲兩種不一樣的最終位圖呈現模型提供兩套不一樣的機制。解決的辦法很簡單,經過爲半透明類型的窗口設計一個輔助窗口,使用它來接收WM_PAINT消息,收到該消息時調用UpdateLayeredWindow更新窗口。注: 這個技術是學習另外一套UI庫MetalBone實現的。
講完了使用宿主窗口分層窗口,下面講講DUI窗口的分層窗口技術的實現。 
使用分層窗口技術可以使UI效果更漂亮,關鍵技術就在這個層。層是什麼?層是一組窗口的繪製容器,它將該層下全部子窗口的繪製內容繪製到一個獨立的緩衝區上, 最後再一塊兒繪製到分層窗口的上一層繪製緩衝區中。以下圖:

A、B、B一、B二、C爲DUI系統中5個DUI窗口。其中,B、B一、B2是同一個渲染層。也就是說設計須要它們先繪製好後再和A,C作融合。 相似的需求對於一個漂亮的UI來講可能會很常見。若是在UI引擎中沒有層的概念是不可能實現的。若是不須要實現前面提到的背景和前景分別渲染的狀況,實現會層窗口其實也不難,只須要在渲染到B窗口時建立一個緩衝區,把從B開始的內容渲染到這個緩衝區,完成後再回到正常渲染流程,就像沒有B一、B2同樣。可是SOUI是支持背景前景分別渲染的,實現這個過程的代碼邏輯就可能很複雜了(能夠本身想象一下)。

非客戶區
HWND的非客戶區用來繪製滾動條及邊框及標題欄,菜單欄。客戶區是用戶繪製的常規區域,在設計上將窗口的顯示區域劃分爲客戶區和非客戶區,有利於用戶在重寫客戶區的繪製代碼時不被非客戶區干擾,也有利於代碼的複用。
在DuiLib中,一個控件如Richedit須要顯示滾動條,它須要給這個控件組合兩個滾動條控件。這種方式雖然看上去沒有什麼大的問題,若是因爲窗口中內容的變化須要動態顯示隱藏滾動條時可能會很麻煩,至少它會引發窗口布局系統的重排,由於滾動條顯示和隱藏時控件的客戶區大小是變化的。
而在SOUI系統中,滾動條和HWND同樣,用戶根本不須要關心,由於內部已經自動處理好了滾動條,也不會引發佈局系統的重排。

資源加載 
通常來講SOUI中引用的全部資源都在XML中描述。剛入門的朋友一般反映SOUI中使用資源的方式不如DuiLib直接,很難入門。可是一旦真正理解了SOUI的這種資源組織方式必定會更喜歡SOUI。
SOUI提供3種資源加載方式:文件,PE資源,ZIP包。
首先SOUI的資源包必須提供一個文件索引表,對於使用PE資源的資源包,索引表就是資源的類型及ID,而對於直接使用文件或者ZIP包的資源,索引表則是一個XML文件。在索引表中,定義每個資源的type及name兩個KEY,SOUI界面佈局中只能使用type和name兩個key來引用資源。
用戶只須要準備一套文件資源,若是須要將資源編譯到PE文件中,系統提供一個工具直接從文件資源的索引XML轉換成rc編譯器能夠識別的rc文件;而若是用戶須要使用ZIP資源包,則只須要使用一個ZIP工具如rar, 7z將資源文件夾打包便可(推薦使用7z打包資源,SOUI內自帶的zlib 1.2.5可以識別7z打包的帶密碼的zip包,但不能識別rar打包的帶密碼的zip包。
 
窗口動畫改進
通常狀況下咱們推薦使用窗口定時器來建立動畫。使用窗口定時器建立動畫的好處是定時器和UI是同一個線程,而SOUI不支持多線程同步更新UI(事實上通常的DirectUI庫都不推薦在工做線程中操做UI,如Android)。那麼問題來了,若是爲每個DUI窗口建立不少定時器,那麼系統的消息隊列中將充滿定時器消息,嚴重時可能大大下降UI性能。
解決方案:在主窗口中建立一個10ms間隔的定時器,須要處理動畫的窗口向系統註冊使用該定時器,動畫記錄下一次動畫須要等待的時間,使用該統一的定時器計數。
咱們看一下面DEMO中顯示大量動畫表情時SOUI的效率:

這一CPU佔用率甚至比QQ中一樣狀況下還低。

容器分層
什麼叫容器分層?在DirectUI中全部的DUI窗口都必須生存在一個容器中。DUI窗口的繪製請求等最終須要由這個容器來實現。在容器不分層的狀況下,全部DUI窗口在容器中的物理座標都是從(0,0)開始。這樣有什麼問題呢?若是要在列表控件的列表項中使用DUI控件就會變得很是麻煩,由於在窗口滾動時你可能不得不一樣時更新全部這些控件的座標。
有了窗口層的概念就不同了,每珍上列表項是一個新的容器,不管列表項顯示在哪,列表項中的控件(容器中的控件)的座標都不須要調整。由於有了容器分層,在SOUI中實現包含子控件的列表變得很是簡單(參考下節:高性能列表控件)。

高性能列表控件
Windows系統中提供的列表控件很是簡單,只能知足簡單的數據顯示需求。注意,是顯示。然而如今的UI需求中常常出現那種即時修改列表控件內容的狀況,你將不得不花大量的時間對列表控件進行自繪,而效果只能說勉強。
經過研究Android系統中提供的列表控件的代碼,借鑑Android中ListView的思想,SOUI實現了一套高性能的列表控件SListView及SMcListView。
SListView及SMcListView都是基於虛表技術,同時只建立當前正在顯示的及部分備用的列表項容器,將資源佔用縮小到最少。同時ListView在滾動時可以高效刷新,實現了海量數據的高性能顯示及更新。
實現這個高性能列表控件的關鍵有兩點:
首先是SOUI中實現的容器層的概念,使得列表位位置變化時,容器內部的控件不須要調整座標。
其次就是容器數據的充分重用。

注:上面列表中只測試了7W行數據,實際上listview中顯示的數據量多少徹底不影響UI性能,親測700W行數據和7W行效果同樣。

無窗口Richedit
Edit控件是UI中最經常使用的控件之一。在容許存在子窗口句柄的狀況下,系統Edit控件已經可以很好的知足咱們的需求。然而在不容許子窗口句柄的狀況下,實現一個Edit控件會很是麻煩。
固然,程序能夠選擇本身去從新實現一套edit,Edit也許還可行,通常狀況下要實現一個Richedit基本不可行。 好在實現Richedit的模塊riched20.dll中把UI和邏輯分離開來,便可以用它直接建立有窗口的Richedit,也能夠用它來建立提供無窗口Richedit的ITextServices接口。然而即便是這樣,程序員須要爲ITextServices實現一個ITextHost接口。儘管MSDN上有相關的文檔及示例,可是根據它們提供的這些資料實現的效果很不理想。必竟只是Demo,不是完整的代碼,它不能演示開發中可能遇到的每個細節。然而剛好是這些細節是影響UI用戶體驗的關鍵。
因此咱們須要另闢蹊徑來解決這個問題。我解決這些細節,關鍵在於理解它們的邏輯。SOUI的辦法是找到riched20.dll的源代碼。好在網絡上流傳着一份從WinCE源代碼中分離出來的Riched20.dll的源代碼,雖然用它編譯出來的Richedit有不少BUG,但利用它可讓咱們更好的理解各類細節。你們能夠測試SOUI中的Edit,效果應該是各類相似庫中最好的一個之一。

XML+LUA
部分模塊在SOUI中採用了接口化設計,如前面提到的渲染引擎,以及後面要說的多語言翻譯,以及這裏要說的腳本模塊。
腳本語言方便靈活,更新簡單,LUA腳本還有高效的特色。和WEB的HTML+JS相似,SOUI實現了XML+LUA的UI開發解決方案。XML實現UI佈局,LUA實現邏輯控制。
實現方法:
在XML中使用<script>標籤聲明UI中須要腳本支持。
經過XML建立UI時自動從腳本模塊爲該UI實例化腳本對象。
採用lua_tinker自動導出C++類到LUA腳本空間,包含控件對象,及控件事件對象。
在LUA腳本中處理事件響應。

多語言翻譯
多語言翻譯對於須要國際化的應用來講可能很是重要。SOUI經過一個語言翻譯接口來執行特定上下文的多語言翻譯而且實現了一個相似QT語言翻譯功能的基本XML的語言翻譯模塊。用戶只須要按照Demo中的語言翻譯文件的組織方式組織翻譯XML就能夠了。

String及其它基於模板的集合對象的參數傳遞
因爲String一般要同時支持char及wchar_t這兩種字符類型,一般String在一個類庫都都是以模板形式存在,好比WTL,ATL,(MFC過久不用,記不清了)。使用模板實現的對象有一個特色,那就是代碼會編譯到使用它的模塊中。如此一來,若是在這些模板類中直接調用malloc, new等內存分配函數時會在調用模塊的堆上分配內存,相應地,內存的翻譯也須要在調用者模塊中執行。
這有什麼問題呢?最大的問題莫過於這樣的對象不宜在不一樣的模塊之間比參數進行傳遞(固然,const參數是沒問題的)。若是一個這樣的對象在A模塊中分配的內存到B模塊中被翻譯,結果只有崩潰。(若是全部模塊採用MD方式動態連接VS的運行庫是沒有問題的)
不少小軟件是不但願採用MD編譯的,由於這樣的話爲了確保程序的正常運行,還須要帶上VS中相對應的運行庫,儘管體積不大,但也麻煩。
SOUI中採用了一點技巧,全部上述模板類都在一個獨立的模塊中實現,同時改寫了這些類中的內存分配及釋放代碼。將它們重位向到該模塊中的兩個內存分配釋放方法。通過這樣處理後,無論這些模板類在哪個模塊中實例化,它們要在堆上申請及翻譯內存時都是在這個獨立模塊中。經過這個簡單的技術有效的解決了這些模板對象不能在不一樣模塊之間傳遞參數的問題。

先進的事件處理模型
SOUI同時支持相似WTL消息映射表方式的事件映射表來響應事件,也支持新式事件訂閱的方式響應事件。事件映射表處理事件的優勢在於可以規範化的把全部事件處理方法在代碼水平集中到一塊兒,方便代碼的閱讀;而事件訂閱則提供了事件的動態處理能力,可以在任意時刻靈活的響應不一樣控件發出的事件。
 
 
  • 結束語
除上述亮點,我相信還有不少細節的處理都體現了SOUI的工匠精神,相信用心的朋友必定能夠在閱讀及使用代碼的過程當中更深的體會到。SOUI是啓程軟件歷時5年心血的結晶,重複一下我之前作《啓程輸入之星》時說的那句話:由於努力,因此美麗! 
但願可以爲您可以喜歡。 
相關文章
相關標籤/搜索