如何用 C++ 從零編寫 GUI?

姚冬中老年程序員

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封裝起來了,外面看不到
//僞代碼
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庫,
基於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庫比較小,我用的是這個

根據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的感受了,不過沒有在真正的產品裏用到。

編輯於 2014-07-12 70 條評論 
 

王斌數學愛好者,喜歡平面設計、戶外運動

僞專業人士過來怒答一記。因爲是想到哪寫到哪,因此可能寫的有點亂。
大概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就是一死寫界面的

其實不多逛知乎,今天被同事在QQ上邀請,做爲一個工做十年的一個死寫界面的程序員,我想我應該仍是有些能夠分享的。固然,有不少觀點或許和我經歷的時代有關,如今的程序員不見得百分之百適合,姑且看成拋磚引玉吧。

從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圖形編程》早已絕版,不過網上能夠買到影印版的,很是值得細讀。但願題主之後有機會成爲一個牛逼的界面程序員。
相關文章
相關標籤/搜索