以金山界面库(openkui)为例思考和分析界面库的设计和实现——问题
? ? ? ? 隨著物質生活的豐富,人們的精神生活也越來越豐富。人們閑暇的時間也相對變多,于是很多人就開始尋找打發時間的方法。其中電視便是其中一種非常重要的消遣方式。假如我們打開電視機,看到了一個電視臺正在播一部我們之前沒看過的,正在一招一式進行打斗的武俠片;另一個電視臺正在播一部之前也沒看過的,正在重復太極推手的教學片。我想大部分人會選擇那部武俠片。為什么?因為那是一個動作體系,不會讓人厭煩。而那個推手教學片,可能非常高端,可能非常有內涵,但是總是讓人缺乏點新鮮感。我之前更關注技術的細節,而今天開始,我將開始分析一款開源的軟件界面庫。這系列文章不再拘泥于一些技術細節,而從一個思路的方向去分析這個庫。
? ? ? ? 我介紹這套界面庫是目前開源的金山衛士開源計劃中的一部分。具體的訪問地址是 http://code.ijinshan.com/。其中代碼的下載SVN地址是https://openkui.googlecode.com/svn/trunk。我將分析的是版本號為54的版本。(轉載請指明出于breaksoftware的csdn博客)
? ? ? ? 在分析這個庫之前,我們可以閉上眼睛,清空大腦,思考一下:如果自己要設計和編寫一個界面庫,將如何規劃和設計?將會遇到什么技術問題?將如何做出一些選擇?
? ? ? ? 首先,我們要做出一個抉擇,我們采用窗口控件方式還是采用直接繪制的方式?我們知道windows系統又可稱為“視窗系統”,正如其義,我們可以發現,windows系統就是通過一個個窗口展現給我們的。如果使用過SPY++的同學應該發現,windows系統中大部分窗口下的子控件其實也是一些小窗口,只是他們的父窗口被指向了我們看到的那個最最大的最最外層的那個窗口。
? ? ? ? 如上圖中,各個用粗線框起來的部分,就是一個個窗口。這就是問題中所提到的用窗口控件方式。
? ? ? ? 還有一種方式就是直接繪制,又稱為DirectUI。顧名思義,它就是直接在父窗口中繪制各個部分,而不是通過子窗口的形式將各種窗口組成成一個可以協同工作的窗口。最最常見的一個例子便是IE的最最里層那個窗口,它通過其渲染引擎將網頁內容繪制在窗口上。這樣做有什么好處呢?我們知道,如果我們用控件方式組織網頁的話,每個控件都會保存一個句柄,如果一個稍微復雜點的網頁,可能有成千上萬個元素,也就意味著有成千上萬個句柄。這些子窗口還要依賴消息進行窗口管理和繪制。可以想象,這將導致整個網頁展現和管理變得非常復雜和龐大。它的執行效率可能連最最差版本的IE都無法比。
? ? ? ? 那我們選擇DirectUI?不,如果我們選擇DirectUI,那我在此寫這系列文章就沒有意義了。而且客戶端界面,一般不會有太過于復雜的渲染問題,所以選擇窗口控件方式還是可以接受的。如果對內嵌IE式的DirectUI技術趕興趣的同學可以看兩篇相關的博文《如何定制一款12306搶票瀏覽器——完結篇》和《內嵌IE網頁窗口中消除IE默認腳本設置影響的方法》。我這兒就不再贅述。
? ? ? ? 現在我們確認了使用窗口控件的方式。那我們再拋出第二個問題:使用什么框架?? ? ? ??
? ? ? ? 使用WTL還是MFC?
? ? ? ? 我相信做windows開發的同學,對MFC很熟悉。我剛畢業的時候,也是看了遍侯捷的《深入淺出MFC》才開始踏上windows開發之路的。但是,之后一直耳聞MFC的種種弊端,其中人們提到最多的一點就是MFC框架復雜容余,編譯出來的文件相對較大。于是WTL就進入我們的視野,我曾記得有人給我推薦WTL時,說WTL是微軟內部開發用的,從可靠性上來說是沒有問題的。但是WTL相對于MFC則要復雜很多,因為你可以發現到處都是模板泛型技術,如果沒有一定的C++功底,使用WTL就像云里霧里,非常難受。綜合以上分析,我們似乎可以覺得WTL更可以適合我們的開發,因為我們要設計的是一套界面庫,我們要設計自己的框架,所以越基礎對我們來說是越合適的。
? ? ? ? 選擇好了WTL后,我們來思考下我們這個界面庫如何構成?
? ? ? ? 如何選擇描述文件的格式?
? ? ? ? 自定義一種格式?個人覺得沒有必要,畢竟這不是我們界面庫的中心問題,我們應該選擇一個穩定的,易于表達的格式。可能你會想到HTML,是的,我覺得可以。但是是否我們還可以再精簡一點呢?那就是XML了,而且目前已經有開源的XML解析庫。我們這樣就可以不拘泥XML的細節,專心于其他業務邏輯。
? ? ? ? 但是有些東西我們還是要考慮的,就是XML內部的屬性定義和組織形式。
? ? ? ? 我們先討論下組織形式。為了在一開始表述的清晰,我并不準備以XML來講解,因為其中我們似乎還要探討我們自定義的XML屬性名等問題。為了簡化,同時為了貼近我們日常中能遇到的場景,我將使用大家比較熟悉的HTML作為例子。HTML已經為我們定義好了屬性和語法,我們將主要從組織形式來思考,并且可以在已有的HTML技術中吸取其發展中產生的優化點。
? ? ? ? 我們先看一個例子 ?
<html><head> </head><body> <img id="id1" src="http://xxxx.xxx.xx/xx.xx" height="200" width="200" atl="AAAAAA"/><img id="id2" src="http://yyyy.yyy.yy/yy.yy" height="200" width="200" atl="BBBBBB"/><img id="id3" src="http://xxxx.xxx.xx/xx.xx" height="300" width="400" atl="CCCCCC"/><img id="id4" src="http://yyyy.yyy.yy/yy.yy" height="300" width="400" atl="DDDDDD"/><body>
</html>? ? ? ? 上面這段包含四張圖片的網頁,經過我們觀察發現,這段代碼是非常容余的,可以精簡之。比如我們可以將height="200" width="200" 表示為一個class屬性,height="300" width="400" 表示為一個class的屬性。這樣網頁就修改為
<html><head> <style type="text/css">class small{heigth:200;width:200;};class big{height:300;width:400};</style></head><body> <img id="id1" src="http://xxxx.xxx.xx/xx.xx" class="small" atl="AAAAAA"/><img id="id2" src="http://yyyy.yyy.yy/yy.yy" class="small" atl="BBBBBB"/><img id="id3" src="http://xxxx.xxx.xx/xx.xx" class="big" atl="CCCCCC"/><img id="id4" src="http://yyyy.yyy.yy/yy.yy" class="big" atl="DDDDDD"/><body>
</html>? ? ? ? 我們還可以發現有些容余,就是src和atl字段。我們有沒有辦法將這兩個東西簡化呢?我對HTML不熟悉,我知識范圍內不知道該如何解決這個問題。但是記得曾經做MFC時,在資源文件RC中,有個字符串表(string table),其中保存的是多個字符串鍵值對。這也是種思路,當然HTML可能不支持這種形式。如此,HTML已經不能滿足我們的描述了。我們回到XML來。對于以上的情況,我們可以分為3個XML文件,其中一個用于描述字符串,一個用于描述類型,一個用于描述界面。
? ? ? ? 字符串描述表
<string id=" xx">http://xxxx.xxx.xx/xx.xx<string/>
<string id= "yy">http://yyyy.yyy.yy/yy.yy<string/>
<string id= "A">AAAAAA<string/>
<string id= "B">BBBBBB<string/>
<string id= "C">CCCCCC<string/>
<string id= "D">DDDDDD<string/>? ? ? ? 類型描述
<class name="small" height=200 width=200></class>
<class name="big" height=300 width=400></class>? ? ? ? 界面描述
<body><img id="id1" src="xx" class="small" atl="A"/><img id="id2" src="yy" class="small" atl="B"/><img id="id3" src="xx" class="big" atl="C"/><img id="id4" src="yy" class="big" atl="D"/>
</body>? ? ? ? 這樣就清爽很多了。KUI對我之上的設計做了更細的劃分,我們將在之后介紹。
? ? ? ? 如何讀取保存界面元素屬性?
? ? ? ? 有了界面描述文件,下一步就是讀取這個文件了。我們大致想象一下這個過程,我們可能需要新建一個結構體,用于描述子控件的屬性,舉個簡單的例子,以下是一個子控件A的描述結構體:
struct StControl{int x;int y;int width;int heght;
};
? ? ? ? 因為子控件B內部可能包含多個其他子控件A。于是這種關系可以使用如下結構體表示
struct StControlEx{StControl stParant;list<StControl> ListChildrenControl;
};? ? ? ? 而多個子控件B可能又同時組成了另一個窗口控件C,那么就該表示為
struct StControlExEx{StControl stParant;list<StControlEx> ListChildrenControlEx;
};? ? ? ? 那么多個子控件C可能又同時組成另一個窗口控件D,那么就該表示為
struct StControlExExEx{StControl stParant;list<StControlExEx> ListChildrenControlExEx;
};? ? ? ? ……
? ? ? ? 如此將子子孫孫無窮無盡矣。因為控件有多少層,我們要有多少個控件描述結構體與其對應。很明顯這樣的設計非常不好。那么我們將如何設計呢?對這個問題,我們將在之后對KUI源碼進行分析時,給出它的解決方案。
? ? ? ??如何通過界面元素屬性設置控件?
? ? ? ? 一般來說,窗口必然會存在以下的屬性:
? ? ? ? 位置:X,Y,Width,Height或者LeftTopX, LeftTopY,RightBottonX,RightBottonY
? ? ? ? 那么是否我們可以定義如下的結構體和XML
struct StWindow{int nLeftTopX;int nLeftTopY;int nRightBottomX;int nRightBottomY;
};
<window pos=''10,10,30,40' ></windows>? ? ? ? 對應的,現在我們可以設想下我們可以定義一個基礎類CBaseWindow
class CBaseWindow{public:void SetCommonAttribute(const StWindow& );
}? ? ? ? 繼承于該類的類都將具有SetCommonAttribute函數以用于設置這些基礎屬性。
? ? ? ? 如果只有這些屬性,該控件可能就是一個有底色的窗口。但是我們的控件是豐富多彩的,這意味著它們也會有豐富多彩的屬性。以按鈕為例,我們可能要新增文字內容屬性。于是我們要擴展我們的按鈕類為
class CButton: public CBaseWindow{public:void SetTextAttribute(const CString& cstrText);}? ? ? ? 再假設我們有個特殊的按鈕,那個按鈕的文字顏色要是可以指定的,于是我們又要擴展個按鈕類出來
class CSpecialButton: public Cbutton{public:void SetTextColor(const RGB& );}? ? ? ? 再假設……
? ? ? ? 可以想象,如果我們這么設計將會導致非常的繁瑣。而上層的調用也將非常復雜,比如我們這個CSpecialButton類,它實例化時將執行如下
SetCommonAttribute(stwindow);
SetTextAttribute(cstrText);
SetTextColor(rgb);? ? ? ? 這個還算好的,如果還有更多的屬性,那這個調用將非常的沒有復雜。怎么解決這樣的問題呢?我們將分析KUI庫,看看它是如何解決這個問題的。
? ? ? ? 界面描述文件的放置位置
? ? ? ? 如果以上問題解決了,我們之后將不會出現構架上的問題。因為我們已經拿到了界面描述信息了,下步就是在合適的地方,讓子控件接收并設置這些屬性即可。
? ? ? ? 現在我們再拋出一個問題:如果我們將我們界面描述文件作為獨立的文件放在用戶的電腦上,可能存在被惡意篡改的可能。還有就是,作為獨立的文件,如果其中任何一個文件被破壞了(比如下載失敗了),將導致整個界面出現異常。可以見得這樣做存在比較大的風險。那么如何解決呢?目前市面上很多軟件都將界面描述文件作為資源文件保存在PE文件中,這樣PE完好,則界面完好;PE受損,可能程序就不能執行了。而且從技術角度說,修改PE文件的難度比修改XML文件門檻要高些。假如你也認為這是一個好方法,那么壞的問題就來了。一款軟件的界面可能需要很多界面描述文件以及圖片資源,我們總不能讓使用我們界面庫的同學,在編譯工程時將這些資源文件一個一個加入到工程中吧!想想這個也是一個繁瑣的問題。程序員最最討厭重復無聊的工作!那怎么辦呢?我們可以讓他們將這些資源文件合并成一個文件,一個簡單的方法就是將這些文件變成一個壓縮包。然后將這個壓縮包放到資源文件中。如果你認為這也是個好辦法,那么壞的問題又來了。我們如何通過資源文件來使用壓縮包中的文件呢?我們可以初步設想下過程:
? ? ? ? 讀取指定資源,將其保存到硬盤(內存)中。
? ? ? ? 將保存到硬盤(內存)中的壓縮包文件解壓。
? ? ? ? 遍歷讀取解壓包中的文件。
? ? ? ? 那KUI是不是這么做的呢?我們拭目以待。
? ? ? ? 帶著以上這么多選擇和問題,我們將在之后的章節中,一一介紹KUI是如何解決問題的,并從中盡量吸取其思想的精髓。
總結
以上是生活随笔為你收集整理的以金山界面库(openkui)为例思考和分析界面库的设计和实现——问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WMI技术介绍和应用——查询本地用户和组
- 下一篇: 以金山界面库(openkui)为例思考和