GUI庫可大可小,大可以是Qt WPF這種數以百萬行計的代碼,小的可以是WTL這種只有幾個頭文件。
對一般人來說,不要奢望能做出大GUI庫,寫一個小一點的,滿足自己的需求,針對某類應用就好了。
我曾經遇到一個需求,需要一個小型的GUI庫來寫個安裝程序。
安裝程序是比較特別的,對於互聯網下載安裝的軟件,要滿足以下要求:
1. 不能帶DLL,必須是靜態鏈接,對系統的依賴越小約好。
2. 可執行代碼必須足夠小,一般來說要500KB左右最好。
3. 有比較好看的圖形效果,比如安裝過程的過場動畫,窗口要有個半透明陰影光圈什么的。
用Qt寫顯然不合適,雖然我在知乎多次說過Qt庫其實不大,但是對於寫安裝程序還是真的有點大了,Qt的靜態鏈接出的Exe有2MB左右。
用MFC也不合適,MFC靜態鏈接出來有400KB左右,算上安裝程序自身的代碼和資源肯定突破500KB了。
用VC++ 6.0的MFC去寫,可以小很多,但是用這種古董不符合我的品味。
用WTL寫,這個肯定很小,只創建一個窗口的程序靜態鏈接只有50KB左右,但是什么功能都沒有啊,只能創建使用基礎的標准悾件,做個透明窗口都要自己再用其他API實現。
還是自己寫一個吧,小一點實用一點,就用來做安裝程序好了,不追求有多么高大上的能力。
跨平台就不追求了,只解決Windows問題就好了。
實現GUI庫,有幾個基本的子系統:
1. 窗口管理系統,這個代碼就是封裝Win API,但是這個工作很無聊,又很麻煩,我索性用WTL實現了,把自己的窗口類去聚合WTL的 CWindow,拿WTL做后端幫我創建管理窗口,對外是看不到WTL的,我沒有用派生是因為不想讓WTL污染我的接口設計。
我用私有類的方法,把WTL封裝起來了,外面看不到
2. 事件系統,WTL的消息映射宏太丑了,我喜歡Qt的signal/slot,但是實現一個Qt那樣的signal/slot可不容易,相當於發明一種C++擴展語法,還要自己實現一個MOC這種編譯預處理器,工作量太大了。用Boost::signal 也太笨重了,boost會引入一個很大的依賴庫,我還希望這個GUI庫可以用默認的VC++就能編譯呢,不想依賴太多其他庫,而且boost的function會帶來編譯困難。
我選擇了用一個輕量級的sigslot庫, http://sigslot.sourceforge.net/
基於C++ template實現的,功能簡單,實現也很簡單,只有一個頭文件,很符合我的要求,本來就不需要那么復雜的功能。
3. 圖形系統
既然是GUI庫,總不能還用GDI函數往hDC上繪制吧,好歹要弄個FrameBuffer,支持RGBA,渲染好了可以通過UpdateLayeredWindow更新到窗口上,以實現半透明異形窗口圖形效果,比如實現個陰影邊緣什么的。
自己寫一個還是很麻煩的,光基本的點線圓繪制,基本的Alpha混合就要寫上萬行,更別提文字輸出了。用第三方庫的話,2D圖形庫就沒有小的,光圖形庫就突破500KB的限制了,用Direct2D是不是有點小題大做了?還是用GDI+吧,雖然這個函數庫不太受待見,但好歹是標准庫,所有Windows都內置,而且我要求的基本圖形功能都是有的。
自己寫個 RPainter 包裝GDI+的函數,順便把 PNG JPG的編解碼也解決了。
4. 布局系統
GUI庫總不能讓用戶自己一個一個創建控件然后用絕對坐標擺放吧。基本的UI描述文件,Layout支持還是要有的。
但是我沒有用XML,而是用了JSON,這兩種格式描述能力是差不多了,僅是我個人偏好JSON,另外JSON庫比較小,我用的是這個
http://sourceforge.net/projects/mjson/
根據JSON的描述來構建窗口控件的對象樹。
我沒有去實現復雜的布局,只實現了Anchor Layout,基本可以保證夠用了。
給每個控件設定好object name,在C++里提供
搞自動綁定可不容易,開發者自己手工綁定吧,好在小程序控件也不多。
5. 基本數據類型和容器類型
身為一個Qt粉當然要自己實現一套string類和泛型容器,向Qt致敬啦。
我沒覺得自己有能力重寫一遍STL,就是用系統的STL做后端,聚合STL的類,實現COW(copy-on-write),實現統一內存池。后來我把vc++的STL換成了 eastl
paulhodge/EASTL · GitHub
這個STL的實現非常好,解決了代碼膨脹問題,編譯出來的代碼比用VC STL小得多。
RString是我自己寫的,但是很多代碼是照抄QString的。
但是實現string和數據容器不是GUI庫必須做的,只是我個人偏好。
6 一些雜項 utility:
基本算法,MD5 SHA1 ZIP 7Z
網絡支持,TCP UDP HTTP,沒搞太復雜網絡模型就是簡單的select,HTTP是封裝的WinHTTP。
IO支持,RFile RStream
7. 至於基本控件,早期只提供了RButton RLabel RTextEdit,其他的按需求用到哪個就實現哪個。
好了,差不多了吧,有這些做個安裝程序基本算夠了。
這個GUI庫寫大程序還是不行,格局太小,只能做小玩意兒,而且GUI庫要有配套的工具鏈,這個很麻煩工作量又大,所以開發大工程還是推薦用Qt。
這個庫早期的基礎版本寫下來也就兩萬行左右代碼,基本只有自己用,想到如果要給別人用的話還要寫文檔,腦袋瞬間大了一圈圈。
后來有個同事把Lua集成進去了,做了腳本綁定,支持拿Lua腳本寫程序,有點QML的感覺了,不過沒有在真正的產品里用到。
對一般人來說,不要奢望能做出大GUI庫,寫一個小一點的,滿足自己的需求,針對某類應用就好了。
我曾經遇到一個需求,需要一個小型的GUI庫來寫個安裝程序。
安裝程序是比較特別的,對於互聯網下載安裝的軟件,要滿足以下要求:
1. 不能帶DLL,必須是靜態鏈接,對系統的依賴越小約好。
2. 可執行代碼必須足夠小,一般來說要500KB左右最好。
3. 有比較好看的圖形效果,比如安裝過程的過場動畫,窗口要有個半透明陰影光圈什么的。
用Qt寫顯然不合適,雖然我在知乎多次說過Qt庫其實不大,但是對於寫安裝程序還是真的有點大了,Qt的靜態鏈接出的Exe有2MB左右。
用MFC也不合適,MFC靜態鏈接出來有400KB左右,算上安裝程序自身的代碼和資源肯定突破500KB了。
用VC++ 6.0的MFC去寫,可以小很多,但是用這種古董不符合我的品味。
用WTL寫,這個肯定很小,只創建一個窗口的程序靜態鏈接只有50KB左右,但是什么功能都沒有啊,只能創建使用基礎的標准悾件,做個透明窗口都要自己再用其他API實現。
還是自己寫一個吧,小一點實用一點,就用來做安裝程序好了,不追求有多么高大上的能力。
跨平台就不追求了,只解決Windows問題就好了。
實現GUI庫,有幾個基本的子系統:
1. 窗口管理系統,這個代碼就是封裝Win API,但是這個工作很無聊,又很麻煩,我索性用WTL實現了,把自己的窗口類去聚合WTL的 CWindow,拿WTL做后端幫我創建管理窗口,對外是看不到WTL的,我沒有用派生是因為不想讓WTL污染我的接口設計。
我用私有類的方法,把WTL封裝起來了,外面看不到
//偽代碼
class RWindow : public RObject
{
private:
RWindowPrivate *d;
}
class RWindowPrivate
{
public:
CWindow m_wnd;
}
2. 事件系統,WTL的消息映射宏太丑了,我喜歡Qt的signal/slot,但是實現一個Qt那樣的signal/slot可不容易,相當於發明一種C++擴展語法,還要自己實現一個MOC這種編譯預處理器,工作量太大了。用Boost::signal 也太笨重了,boost會引入一個很大的依賴庫,我還希望這個GUI庫可以用默認的VC++就能編譯呢,不想依賴太多其他庫,而且boost的function會帶來編譯困難。
我選擇了用一個輕量級的sigslot庫, http://sigslot.sourceforge.net/
基於C++ template實現的,功能簡單,實現也很簡單,只有一個頭文件,很符合我的要求,本來就不需要那么復雜的功能。
class RWindow : public RObject
{
sigslot::signal0<> Clicked
}
class MyApp
{
void on_clicked()
{
}
void init()
{
m_win.Clicked.connect(this, &MyApp::on_clicked);
}
RWindow m_win;
}
3. 圖形系統
既然是GUI庫,總不能還用GDI函數往hDC上繪制吧,好歹要弄個FrameBuffer,支持RGBA,渲染好了可以通過UpdateLayeredWindow更新到窗口上,以實現半透明異形窗口圖形效果,比如實現個陰影邊緣什么的。
自己寫一個還是很麻煩的,光基本的點線圓繪制,基本的Alpha混合就要寫上萬行,更別提文字輸出了。用第三方庫的話,2D圖形庫就沒有小的,光圖形庫就突破500KB的限制了,用Direct2D是不是有點小題大做了?還是用GDI+吧,雖然這個函數庫不太受待見,但好歹是標准庫,所有Windows都內置,而且我要求的基本圖形功能都是有的。
自己寫個 RPainter 包裝GDI+的函數,順便把 PNG JPG的編解碼也解決了。
4. 布局系統
GUI庫總不能讓用戶自己一個一個創建控件然后用絕對坐標擺放吧。基本的UI描述文件,Layout支持還是要有的。
但是我沒有用XML,而是用了JSON,這兩種格式描述能力是差不多了,僅是我個人偏好JSON,另外JSON庫比較小,我用的是這個
http://sourceforge.net/projects/mjson/
根據JSON的描述來構建窗口控件的對象樹。
我沒有去實現復雜的布局,只實現了Anchor Layout,基本可以保證夠用了。
給每個控件設定好object name,在C++里提供
template<typename T>
T *findObject(const RString &name)
5. 基本數據類型和容器類型
身為一個Qt粉當然要自己實現一套string類和泛型容器,向Qt致敬啦。
我沒覺得自己有能力重寫一遍STL,就是用系統的STL做后端,聚合STL的類,實現COW(copy-on-write),實現統一內存池。后來我把vc++的STL換成了 eastl
paulhodge/EASTL · GitHub
這個STL的實現非常好,解決了代碼膨脹問題,編譯出來的代碼比用VC STL小得多。
RString是我自己寫的,但是很多代碼是照抄QString的。
但是實現string和數據容器不是GUI庫必須做的,只是我個人偏好。
6 一些雜項 utility:
基本算法,MD5 SHA1 ZIP 7Z
網絡支持,TCP UDP HTTP,沒搞太復雜網絡模型就是簡單的select,HTTP是封裝的WinHTTP。
IO支持,RFile RStream
7. 至於基本控件,早期只提供了RButton RLabel RTextEdit,其他的按需求用到哪個就實現哪個。
好了,差不多了吧,有這些做個安裝程序基本算夠了。
這個GUI庫寫大程序還是不行,格局太小,只能做小玩意兒,而且GUI庫要有配套的工具鏈,這個很麻煩工作量又大,所以開發大工程還是推薦用Qt。
這個庫早期的基礎版本寫下來也就兩萬行左右代碼,基本只有自己用,想到如果要給別人用的話還要寫文檔,腦袋瞬間大了一圈圈。
后來有個同事把Lua集成進去了,做了腳本綁定,支持拿Lua腳本寫程序,有點QML的感覺了,不過沒有在真正的產品里用到。
王斌,數學愛好者,喜歡平面設計、戶外運動
大概2009年的時候,我就沉醉在編寫一款能跨時代的GUI上,從哪時候開始,我開始大量研究國內外所有開源非開源的GUI。
最開始的時候,每個寫GUI的人都會經歷那么幾個階段(windows下),
使用MFC,windows控件 ->發現windows控件不足,開始改裝->發現改的多了,重復造輪子,開始思考從頭完全自己實現控件->GUI的雛形->總結各種gui,寫出自己的DirectUI->發現光是DirectUI還不夠滿足日益增長的界面需求,開始思考更合理的設計理念
所以你會發現,要從頭實現一款自己的界面庫,你需要了解界面庫是如何運作的,這就牽扯到消息機制、繪圖機制,這算界面庫的最底層。在這層其實就有大量細節。比如在windows上,在用GDI的過程中,會發現GDI有各種各樣極其不爽的地方,不支持alpha通道、抗鋸齒效果不好、矢量功能太弱、圖像處理功能太弱等等。所以在這一步,你需要一個好用的渲染框架。個人非常推薦skia,效果強大,速度極快。
有了渲染層,你要設計一套消息循環機制,你需要設計你的各個控件是如何接受消息,並做出反應。然后之上到了控件層,你需要設計你的控件體系。比如你要怎么分類你的控件,各種控件之間怎么協作。控件層之上,你又需要布局,就是說你需要有一種方式,讓你的控件方便的放置在應該在的位置。
寫完了這些,你會需要一個更方便的配置系統。這時候你會想到xml。這種結構化的語言對你來說很適合描述控件的各種信息。於是你又加入xml配置控件。
等等等等,寫完這些,你基本上已經算半個界面專家了。但其實這一階段,才是真正界面高手和界面熟手的分界點。到這一階段,如果你對界面有更高的要求,你會發現用xml配置那些寫好的控件,到底還是太麻煩。產品會給你提無數亂七八糟的需求,這種需求不是一個通用控件可以搞定的。這時候,你會有所思,想怎么才能更加方便的開發。當年,在這個階段,我苦思了很久,最后直到在公司的某個公共目錄,發現一份對迅雷界面庫的分析,我才恍然大悟。原來你少了一種打破控件系統的勇氣和創意。這個時候,你會發現,市面上許許多多的js庫,其實就代表未來界面庫的發展方向:不再使用傳統的控件體系,而是用比控件更小粒度的原子控件---就比如迅雷界面庫里的各種原子控件,又如js庫里所使用的那些html元素。你會發現,只使用基本的幾個元素,然后強化他們的拼裝方式,能更方便快捷的實現產品提出的那些奇怪需求。
有了原子控件,你拼裝出異性控件更加方便,你要實現控件各種動畫也更加簡潔。當你還是覺得有點不足,你會發現C++寫這種異步邏輯的工作,太tm麻煩了。你會發現寫網頁的那群人永遠寫出炫酷界面的時間比你短。這時候你會再次醒悟,用腳本來實現這一切,用腳本去黏合這些邏輯。你會發現,用腳本去寫動畫,寫消息響應,由於腳本天生自帶閉包,寫這種異步邏輯簡直爽到爆…………
寫完這些,我的庫基本也被公司所用。小小得瑟一下,現在毒霸的加速球懸浮窗就是用這套邏輯寫的,使用的庫叫kdgui :-)
未完待續,有時間還想講下kdgui的設計思路,當時可是把10多m的webkit,給完全解剖了,變成2m的kdgui:-)
Gdier,就是一死寫界面的
從0開始學GUI編程,但是不用庫,這件事本身是個偽命題。GUI編程是個復雜的知識體系,並不像大多數人想像中那么簡單。所以如果題主真的想對Win32下(我猜的)的GUI編程有比較深刻的理解,建議還是從使用庫開始。
先簡單的說下,一個能用的界面程序員需要儲備哪些知識:
1.各種系統內置控件的使用方法及特性
2.界面相關的消息機制。比如Windows下的消息循環,iOS中的RunLoop機制等
3.消息及事件派發機制
4.簡單的繪圖方法
優秀的界面程序員需要具備的進階技能包括但不限於:
1.熟悉進程、線程調度機制,各種內核對象的應用
2.基本的圖形學和圖像處理技能,位圖基礎
3.圖像編輯工具的使用,熟練使用PhotoShop、AI、mspaint等至少一種
4.有過多個平台的界面開發經驗
回到題目,從0開始,當然先要成為能用的界面程序員。熟悉系統內置控件的最佳方式莫過於使用各種成熟framework來實現需求。至於是MFC還是WTL還是別的什么,倒不是什么大不了的事。
還是簡單說下MFC和WTL(ATL)的區別。以前經常會看到有人說MFC如何如何渣,如何如何誤導觀眾,其實這也是一個誤解。
MFC
優點:易上手,對於界面訂制不高的需求更容易做到快速實現
缺點:不夠靈活,效率略低,運行庫體積較大且版本太多
WTL(ATL)
優點:靈活,代碼執行效率高(其實就是Win32 API的簡單封裝,當然快),運行庫小,老版本甚至有個minicrt版本,一個簡單的helloworld只有80k且不依賴任何運行庫
缺點:封裝的不夠完全,很多功能需要自己實現;細節暴露太多,控制能力較差的程序員很容易把代碼寫的亂七八糟;WTL的支持者大多對范型這種東西有着階段性盲目崇拜(順便懷念一下當年那青蔥的自己_(:зゝ∠)_)
OK,我的意圖已經很明顯了,如果完全沒有基礎,先看看MFC吧。如果能夠做到熟練運用(如果悟性好,這個時間段可以非常非常短,所以不要着急),並且開始接觸Win32原生API,再開始試着用下WTL,你頓時會發現世界美妙多了。
當你已經可以熟練使用框架進行界面開發之后,就可以開始探討一些原理性質的東西了,這個時候可以試着讀一下界面框架的源代碼,然后你就能了解為什么同樣是API封裝,不同的框架使用起來會有那么多的區別。
舉個例子,比如消息機制,以上兩個框架的消息循環封裝都寫的非常漂亮,MFC的消息注冊機制、WTL的thunk機制,都是非常值得一讀的。
如果你已經走到這一步,恭喜你,你已經是個不錯的界面程序員了,如果這個時候你仍然覺得自己非常喜歡寫界面,你就有機會成為一個很優秀的界面程序員。那么,接下來會發生些什么呢?
1.你會嫌棄各種系統控件,什么都想自己畫。你手上會有一套常用的自繪控件代碼
2.你會開始崇拜DirectUI,對spy++抓不到的窗口懷有深深的敬意
3.你開始嘗試自己寫一個簡單的界面庫
4.你會后悔高中沒學好解析幾何,大學沒學好線性代數
5.覺得寫界面的程序員好不受重視啊,要不要也去做幾個牛逼的功能?
於是那年我辭職出去創業了,為了公司的產品,我花了一個多星期寫了bkwin的原型,基於WTL實現了一套基本的DirectUI體系,用XML描述界面布局,並實現了一個RelativeLayout和一個FrameLayout(以至於一年后當我看到AndroidSDK里描述界面的方法時,各種即視感,然后感慨自己道行尚淺)。后來又斷斷續續的增加了一些復雜控件實現,以及緩存優化、換膚支持等等。現在這個庫的早期版本已經被公司開源,並且應用在有上億用戶量的產品中。(好久沒得瑟了,一下沒忍住 ´▽`)
越往后寫,越發現自己知識的匱乏。當GDI+解碼PNG的效率成為瓶頸的時候,我開始學習自己處理DIB;當C++代碼處理DIB的效率已經不能再提升的時候,我開始看SSE的流水線指令...當我覺得我已經實在改不動的時候,有幸讀到了WebKit的代碼,被WebCore徹底擊潰;然后我轉行做了iOS程序員,於是三觀又被毀了...
話題扯的有點遠,其實我的根本意思還是想說,學無止境,既然打算從0開始,就認認真真打好基礎,每向前多走一步,都要夯實,嘗試去了解一些原理性的東西,不能淺嘗輒止。 不求甚解是成為一個優秀程序員的大忌諱。
最后推薦兩本書:《Windows核心編程》、《Windows圖形編程》,其中《Windows圖形編程》早已絕版,不過網上可以買到影印版的,非常值得細讀。希望題主以后有機會成為一個牛逼的界面程序員。