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