今天打開Windows 8的時候發現來自了Windows Essentials 2012的更新,發現里面已經包含了一個Windows Live Writer。以前每次打開cppblog直接寫博客的時候都很苦逼,又懶得下載這些軟件,結果今天發現既然已經裝了,那就來試試看。用著的時候還挺舒服的。
插個圖片試試:

posted @
2012-10-23 18:43 陳梓瀚(vczh) 閱讀(2054) |
評論 (4) |
編輯 收藏
摘要: 昨晚終于發布了GacUI 0.4.0.0,也就是第五個release了。現在GacUI的源代碼可以在三個地方找到,分別是:
Codeplex:http://gac.codeplex.com
Github:https://github.com/vczh/gac
主頁:http://www.gaclib.net
這兩個月在開發GacUI的對象的反射系統之外,一直在做Windows 8的皮膚。現在的GacUI已經擁有了兩個皮膚,一個是模仿Windows 7的,另一個是模仿Windows 8的。GacUI在啟動的時候,會根據Windows的版本來自己選擇要用哪個皮膚做缺省皮膚。使用g::NewXXX()函數創建的控件都會直接使用當前的皮膚(如果沒有用SetCurrentTheme替換掉的話,就是缺省皮膚)來創建控件,否則,直接new控件類的話,要傳入一個創建好的皮膚對象。
閱讀全文
posted @
2012-10-18 22:16 陳梓瀚(vczh) 閱讀(3773) |
評論 (7) |
編輯 收藏
所有關于渲染的部分的代碼可以在http://gac.codeplex.com下載下來之后,在\Libraries\GacUI\Source\GraphicsElement目錄下面找到。
整個渲染系統的主要思想就是,圖元(IGuiGraphicsElement)和渲染器(IGuiGraphicsRenderer)分開,而且粒度根據性能的要求粗細都有。為什么要這么設計呢?在前言里面說過,不同的渲染設備,譬如GDI和DirectX,需要的渲染策略和cache資源的方法都不太一樣。因此為了讓各個渲染設備的渲染器可以充分自定義渲染的策略,于是做出了這樣的設計。
但是具體是怎么做的呢?在GacUI里面,首先可以用GetGuiGraphicsResourceManager來獲取一個全局的資源管理器(GuiGraphicsResourceManager)對象。這個對象的主要作用就是注冊各種創建圖元和渲染器的工廠對象。為了讓整個渲染系統運行起來,首先我們要把各種圖元工廠(IGuiGraphicsElementFactory)注冊進去。每一個圖元工廠有自己的一個全局的名字。這樣當你把一個圖元工廠注冊金資源管理器之后,從此就可以用圖元的名字從資源管理器里面取出注冊進去的圖元工廠對象了。
其次,因為在運行的時候,每一個圖元對象都會在內部保存一個專門給這個圖元對象用的渲染器對象,具體的渲染設備的渲染器可以在這個渲染器對象里面cache一些資源,就可以達到為某個圖元cache特殊的資源的目的了。因此為了給圖元對象創建合適的渲染器對象,我們還需要將圖元工廠的名字和一個渲染器工廠(IGuiGraphicsRendererFactory)關聯起來。當這一步完成之后,我們就可以通過下面的代碼來給一個圖元關聯上正確的渲染器對象:
IGuiGraphicsElement* element = xxxx;
IGuiGraphicsElementFactory* elementFactory = element->GetFactory();
IGuiGraphicsRendererFactory* rendererFactory = GetGuiGraphicsResourceManager()
->GetRendererFactory(elementFactory->GetElementTypeName());
IGuiGraphicsRenderer* renderer = rendererFactory->Create();
renderer->Initialize(element);
這樣我們就從一個IGuiGraphicsElement對象構造出了對應的IGuiGraphicsRenderer對象,并且將這個渲染器對象和這個圖元對象關聯了起來。這一步完成之后,渲染器對象就會開始根據需要cache被關聯的圖元對象所需要的資源。然后我們只需要把渲染器對象的指針告訴圖元對象,那么圖元對象就可以在自己被更新的時候,通過調用renderer->OnELementStateChanged()適當通知一下渲染器對象,而且也可以用renderer->GetMinSize()來說的顯示這個圖元所需要的最小的矩形尺寸了。為什么尺寸要通過渲染器來計算呢?主要是因為具體怎么渲染是渲染器來控制的,所以尺寸當然也是需要讓渲染其計算的,其中一個例子就是文字渲染了。
接下來就是如何規劃圖元的問題了。目前GacUI所有的圖元如下所示:
Gui3DBorderElement
Gui3DSplitterElement
GuiGradientBackgroundElement
GuiImageFrameElement
GuiPolygonElement
GuiRoundBorderElement
GuiSolidBackgroundElement
GuiSolidBorderElement
GuiSolidLabelElement
GuiColorizedTextElement
我們可以看到,大部分的圖元都是很簡單的。GuiSolidLabelElement就稍微復雜一點,具有了一些諸如自動換行啊省略號這樣的設置。而最復雜的就是GuiColorizedTextElement了,里面按行保存了文本之后,還按行給每一個字符分配了存放顏色的緩沖區,然后實現了字符串修改的時候緩沖區的分配釋放更新等操作。為什么不設計一個GuiCharElement,而是做成了這兩個東西呢?因為在普遍情況下,渲染器都支持對復雜的文字一次性渲染完成,如果我們把每一個字符都設計成一個圖元,讓排版引擎去渲染字符串的話,性能低下不說,效果可能還不如渲染器自己渲染出來的好。關于這里的一個典型的例子就是Windows所支持的可以連筆的OpenType技術了。另一個原因就是,在開發著色文本框的時候,如果所有的渲染過程不包含在一個圖元,而是分散在各個字符圖元的話,那更新文字和顏色的時候,無疑十分浪費內存,并且操作起來非常的麻煩,為了靈活性犧牲了太多的性能,得不償失。
說完了圖元和渲染器,最后一個要介紹的就是渲染目標對象(IGuiGraphicsRenderTarget)了。盡管渲染目標可以指向很多種地方,但是在一般情況下,渲染目標所指向的都是一個窗口的客戶區域(client area)。盡管在設計上這樣看起來僅僅是很自然,但是實際上這么一個對象卻是必須的,因為Direct2D的一個render target創建出來的畫刷等資源不能直接用在另一個render target上面,而且當render target掛掉的時候,那些資源要全部干掉,重新創建render target,并且重新創建資源。這一步作為一個bug登記在了GacUI里面,還沒實現,所以現在Direct2D渲染的時候,把窗口最小化再打開,有時候會變黑。
渲染目標對象的另一個功能就是計算clipping了。在形成父子關系的排版對象綁定的圖元在渲染的時候,子圖元是不能超出父排版對象的矩形范圍的。而且鑒于大量的對象可能處于不可見的位置,所以外圍的驅動渲染的代碼要在渲染對象完全被clip沒了的時候(譬如說在一個具有滾動條的容器里面,一個因為滾動條的關系看不見的按鈕),停止渲染看不見的那顆子樹,加速渲染過程。而且各個渲染設備也需要處理類似于一個文字只有上半部分能看見這樣的情形。所以排版對象就可以通過提供他自己的矩形范圍給渲染目標對象,從而讓渲染目標對象自己計算可見的矩形范圍,從而配合整個渲染流程的進行。鑒于有一部分的渲染器需要的資源是從渲染目標對象來的,因此IGuiGraphicsRenderer還有一個叫做SetRenderTarget的函數,用于在渲染對象發生變化的時候,譬如說因為窗口最小化從而造成Direct2D的render target的時效,需要重新創建的時候,通知每一個圖元綁定的渲染器說,整個渲染目標對象已經換掉了,一些資源可能要重新創建。
當然在這里需要提出的就是,在GacUI的GDI和Direct2D渲染器的實現里面,是有一些依靠引用計數全局cache的資源。譬如說在同一個渲染目標對象里面渲染的兩個同樣顏色的矩形,他在內部使用的具體的畫刷就不會真的重復創建兩次。盡管GDI和Direct2D的策略不同,GDI的畫刷是全局的,而Direct2D的話刷只對一個render target有效,GacUI還是提供了一個通用的資源cache算法模板,讓實現類似的功能更加方便。
有關渲染系統的內容就說到這里了,下一篇文章將會具體講排版對象的內容。
posted @
2012-10-08 07:40 陳梓瀚(vczh) 閱讀(3755) |
評論 (5) |
編輯 收藏
說起GacUI(www.gaclib.net,gac.codeplex.com),其實這個想法在我還在上大三的時候就已經有了。但是由于經驗不足,在當時并沒能夠把這個東西給做出來,直到去年(2011)的國慶節為止。想想到現在也做了快一年了,GacUI也可以用來寫一些不是特別殘暴的C++GUI程序了。前幾天有人問道,為什么在PC都快完蛋了并且大部分GUI都已經用C#來做的時候,我還要做這個東西呢?其實,這有兩個原因:第一個我喜歡折騰C++;第二個C++好像也沒什么特別好的GUI,因此也想嘗試一下,如果做成了就維護下去,做不成了好歹還可以提高自己的水平,總之是不會浪費時間的。所以我就在想,GacUI寫到現在也快一年了,并且我最近也看到cppblog上面有幾個人也想搞搞GUI,因此我想把GacUI的一些設計思想,和我得到這些思想的過程寫出來,順便也介紹一下GacUI的架構,讓一些有興趣的人(特別是裝配腦袋)也可以來折騰折騰。
GacUI的架構的最重要一點就是要跨平臺。當然這不一定意味著我將來一定會把GacUI移植到別的什么操作系統去,但至少Windows的Classic Desktop和Metro的兩套API就毫無相似之處,同時搞定他們,也算是跨平臺了。而且就算是基于同一種API,上面還有不同的渲染器的API,譬如說GDI,譬如說Direct2D,他們也是截然不同。GacUI的設計至少要可以屏蔽掉他們的區別。當然,這在技術上有一個很好的方法來保證,就是GacUIIncludes.h里面不包含Windows.h的任何內容——因此至少在頭文件里面,所有的東西都是跟Windows無關的。當然在非GUI的部分,我們還是需要Windows.h的,并且有些人喜歡對GacUI做點hack的操作,因此我還是在GacUI.h里面提供了幾個額外的依賴于Windows.h的函數來暴露一些內部細節。那這樣如何跨Classic Desktop和Metro呢?有一個簡單的方法,就是可以在編譯的時候給些宏開關,譬如說GACUI_WINDOWS_CLASSIC_DESKTOP(缺省)或者GACUI_WINDOWS_METRO之類的東西,來屏蔽掉不需要的部分。當然這部分在移植到Metro之前我不會加進去。
基于這個想法,如果大家閱讀了GacUI的代碼的話,會發現在文件\Libraries\GacUI\Source\NativeWindow\GuiNativeWindow.h里面定義了一個INativeController接口,而且目前只有Windows Classic Desktop一個實現。INativeController的內容很多,提供了跟具體的平臺有關的操作,譬如說讀寫圖片文件啦、創建消滅窗口啦、顯示器操作啦、還有各種其他的輸入輸出等等。實現一個從頭INativeController還是比較繁瑣的,因為GUI這種對操作系統重度依賴的東西,想剝離開來,就會發現他依賴了一大坨API。這也解釋了為什么INativeController的各個XXXService函數返回的對象的方法的總和有上百個。不過從Classic Desktop移植到Metro還是相對比較簡單的,因為大部分內容還是可以共享的。
其次就是渲染器了。渲染器跟平臺是交叉依賴的。譬如說OpenGL在linux上和Classic Desktop上都可以用,Direct2D在Classic Desktop上和Metro上都可以用,GDI只能在Classic Desktop上面用。因此這就是為什么我最終沒有把渲染器也寫在INativeController里面,而是把渲染器整個給屏蔽掉了,根本沒有在GacUIIncludes.h里面給出他的接口。但是考慮到GacUI是一個支持換膚的GUI庫,因此肯定需要讓皮膚來自己決定如何繪圖。后來我就想了一個辦法,把渲染器的結構整個拿掉,替換成各種各樣的圖元(IGuiGraphicsElement)。所謂的圖元就是類似于方形啊,圓形啊,填充啊,漸變啊,文字之類的東西。皮膚自己把圖元按照一定的排版關系(在下文中有描述)拼裝好,然后GacUI內部的一個小系統會利用Bridge和Abstract Factory兩個模式的結合體(參考\Libraries\GacUI\Source\GraphicsElement\GuiGraphicsElement.h)來為這些圖元分配好渲染器對象(IGuiGraphicsRenderer)。然后圖元和渲染器之間用了Listener模式在交換信息。這樣的好處是,當圖元受到改動的時候,這個圖元對象的專用渲染器對象可以選擇cache一些信息,然后在窗口渲染的時候,只需要訪問所有的渲染器對象(在排版對象GuiGraphicsComposition的組合項形成了一棵樹),讓他們渲染自己就可以了。
圖元包含了所有需要渲染的數據,但是唯獨沒有把尺寸寫進去,因為尺寸這種東西不應該讓渲染器來負責,而應該讓排版對象來負責。排版對象自己是一棵樹,然后節點根節點之間有一些關系,這樣就可以實現堆棧排版、表格排版、對齊(到某一些邊上的)排版等等具體的排版算法。一個排版對象可以放置一個圖元對象并讓這個圖元充滿他,所以顯而易見,有一些排版對象僅僅是用來計算尺寸的中間結果,上面不一定有圖元對象的。當渲染開始的時候,排版對象首先跟圖元對象獲取數據,然后遞歸計算好整棵排版樹的尺寸,最后把尺寸交給附著在上面的圖元對象的專用渲染器對象來渲染。
大家可能會想,如果渲染一次都需要調用成千上萬個虛函數的話,會不會性能低下啊?當然編譯成Release運行會發現GacUI的性能還是相當高的。原因有兩個。第一個是我對排版對象做了一些優化。舉個例子,一個對象的尺寸至少要大于所有子對象的尺寸,這個事情計算起來是相當快的,不需要做cache。但是一個表格排版里面的所有小格子會互相擠來擠去,這個東西計算起來相當復雜(復雜度大越是平方,而且系數也不笑),所以結果要做cache。但是什么時候需要重新計算呢?度量方法很簡單,就是每一個格子的最小尺寸發生了變化的時候。而且事實上大部分皮膚都是用表格來排版的,所以等于說大部分結果都有cache。所以排版部分的尺寸在每一次渲染的時候只需要做一些小計算就可以了。復雜的排版每一個排版對象相互之間都是有關系的,一個排版對象發生了變化,有可能導致另一個排版對象的尺寸需要修改,所以最簡單的方法就是,不保存尺寸,每一次都直接重新算一次就可以了。在這個基礎上,表格排版做一下cache,整個計算過程就會變得飛快。所以盡管每一次拖動窗口,或者鼠標滑過一次窗口,都要進行相當多的計算,但是因為有一個智能的cache,使得不僅運算速度變快,而且在添加新的排版對象類型的時候也根本不需要考慮自己會不會被cache的問題,開發起來也相當愉悅。
所以上面的三大模塊(操作系統API隔離、渲染器、排版對象)已經足以讓我在系統里面開一個窗口然后在上面放各種各樣的東西了,譬如說組合成一個非常接近Windows7的按鈕外觀的一個矢量圖。那控件要怎么辦呢?其實一個控件,就是通過接收用戶的輸入,對一個排版對象上承載的一大堆圖元進行更改。用戶的輸入和控件(GuiControl)本身的狀態進行互動,然后控件把狀態的變更提交給控件的皮膚(GuiControl::IStyleController),最后皮膚通過修改圖元來把狀態變更最終展現給用戶。一個典型的例子就是,在使用Windows7皮膚的時候,鼠標移動到按鈕上面去,他會觸發一個動畫慢慢變成藍色。
GacUI的大體架構就是這個樣子了。在接下來的幾篇文章里面,我會詳細介紹每一個子系統的內部結構,順帶做以下代碼導讀,大家敬請期待。
posted @
2012-09-17 22:31 陳梓瀚(vczh) 閱讀(4497) |
評論 (24) |
編輯 收藏
Visual Studio 2012發布的那一天我就把它搞到手了。新的C++ IDE真的是勁爆了,寫代碼的感覺毫不亞于C#。我最喜歡的部分是智能補全和著色部分。如今C++的宏被渲染成屎紅色,類型被渲染成屎綠色,參數被渲染成屎灰色,這樣基本不需要要編譯,看著顏色都知道有沒有寫對。智能補全已經趕超VAX,而且還實現了“縮寫過濾”,譬如說輸入PNT就可以在彈出列表里面顯示所有大寫字母為PNT的對象(譬如說ParsingNodeTransition)等等。這樣做的好處是,我只要打有限幾個字符就可以補完一整句了,輸入速度大大提高。
所以我升級了幾乎所有工程。文檔生成部分由于還在使用DIA100,所以暫時沒有升級到2012,不過這是遲早的事情。不過這次升級遇到了幾個小問題。
第一個是,對于沒有capture任何外部變量的lambda expression,它可以隱式轉換成一個函數指針。這個功能VS2010是沒有的,結果升級了之后造成了我幾個重載函數的問題,不過解決這個東西還是很簡單的,只要把lambda表達式先保存在一個vl::Func變量里面就好了。
第二個是WICImagingFactory。在Windows SDK 7.0里面,CLSID_WICImagingFactory指向了WIC的唯一一個版本。在Windows SDK 8.0里面,出現了CLSID_WICImagingFactory1和CLSID_WICImagingFactory2,并且CLSID_WICImagingFactory等于CLSID_WICImagingFactory2。問題就來了,Windows 7里面并沒有WICImagingFactory2,結果我CoCreateInstance就是敗了。一開始覺得很奇怪,后來想了想,直接用VS那強大的Go To Definition功能跳到了定義CLSID_WICImagingFactory的地方,然后發現了這個事情。因此我就把代碼改成了,如果sdk用的是高級版本,就強制使用1.0的。
VS2012對模板語法的檢查更加嚴格了。以前還可以寫typename A<T>::B<T>,現在不行了,得寫成typename A<T>::template B<T>。其實后面那個才是標準的,而且VS2010也支持。只是VS2010也允許你省略template。
VS2012對于C++的改進已經跟C#幾乎一模一樣了,而且VS2012還支持C++的單元測試項目。總的來說,我十分喜歡。
posted @
2012-08-30 05:29 陳梓瀚(vczh) 閱讀(11025) |
評論 (25) |
編輯 收藏
為了給自己寫的C++ GUI庫做宣傳,在幾個月前我決定要給他做一個網站,經歷了這幾個月不斷的重構,現在網站的架構終于定型了。考慮到在這之前我幾乎沒有開發網站的經驗,所以在這里做點小總結來介紹自己一路走來發生的事情。
一開始為了制作這個網站購買了萬網的域名和一個很便宜的一年500塊錢的空間。這個空間支持低版本的asp.net,不過當時還沒學會這方面的東西。后來我就開始學習HTML和CSS,然后做出了幾個頁面。不同的瀏覽器有不同的bug,導致HTML和CSS搞起來相當復雜,特別是div和float的結合,更是焦頭爛額。后來索性整個網頁都用表格布局。雖然表格布局并不是什么流行的做法,但是鑒于GacUI的網站并沒有多么復雜的內容,因此最后這么做,避開了很多跨瀏覽器的問題。當然以后如果網頁內容變得太復雜的話總是要改回div的吧。對于常年寫圖形和編譯器方面的我來說,剛開始深入接觸HTM和CSS的時候就對他的雜亂無章所震驚。使用programming language領域的經驗和標準來看,HTML、CSS和Javascript作為編程的工具實在是爛到不能再爛了。不過事物的存在總是有其合理性的,縱觀這三樣東西的發展和歷史,我們會發現其過程充滿了各種巧合,而且當初這些東西在發明的時候就僅僅是為了解決一些簡單的問題。如今流行了,就跟COBOL、Java和C語言一樣,全世界的程序員都只能接受其不可忽視的弱點而就這么開發下去了。
在熟悉了簡單的HTML和CSS之后,我就開始給GacUI做網站了。這個網站的主要目的就是用來介紹GacUI的特點、展示一些Demo、告訴人們如何下載并使用,最后就是提供一個在線文檔。文檔就跟MSDN一樣,當然搜索功能還不存在。剛開始就遇到了美工問題。技能書都點在了系統軟件上,自己的美工自然是連自己都不能滿足的。后來找了幾個例子,看來看去覺得還是
http://www.codeplex.com最順眼。但是它的代碼復雜到吐血,我便只好依樣畫葫蘆,自己慢慢復刻。最后復刻的結果還是令我比較滿意的。在做完了導航條之后,我就發現了一個問題。如果不使用如今的框架來做網頁,而采用手寫的方法來生成HTML文件的話,勢必是無法DIY的。不過那個時候還沒有意識到這個問題的嚴重性,于是就采用了簡單粗暴的復制代碼的方法來做好那五個頁面。這也為我之后轉向Windows Azure埋下了伏筆。
之后做Demo的展示頁面比較順利。因為目前展示Demo的方法就是先貼效果圖,然后貼C++代碼,十分的簡單粗暴。于是立刻就遇到了一個新的障礙,要如何寫一個類似MSDN的HTML文檔。其實這件事情跟做網站本身是沒什么關系的,但是自從決定了要提供在線文檔之后,這個問題也就無法避免了。C++生成文檔地方法之前略有研究,畢竟關于這個功能的一個簡單的原型就是我跟幾個朋友在大一的時候參加學校的軟件競賽的作品。后來還考慮過諸如Doxygen這樣的工具。但是由于Doxygen生成的文檔很難調整風格,使其整體融入我的網站的樣式里,因此最終就放棄了。然后我便想起了Visual Studio的XML注釋功能。在略為研究之后,我便給我的整個GacUI的類庫的public class編寫了XML注釋。當我最終要執行生成文檔的這一步的時候,我才發現所有的工具都不支持原生C++程序和XML注釋的文檔生成功能。不過想來其實也有道理。Visual Studio在編譯了XML注釋之后提供的一個xml文件只包含符號和注釋的對應關系。至于符號究竟是什么內容,則完全沒有。因此.net的程序是怎么生成文檔的呢?自然是利用反射了。C++的苦逼就在這里啊,除了直接寫代碼,就沒有任何方法。但是寫了這么多的XML文檔要放棄實在是太可惜了,所以我就不斷的找呀找,然后發現Visual Studio在安裝的時候提供了一個叫做DIA的庫,可以讓我閱讀pdb文件!
這讓我欣喜若狂啊。既然Visual C++的調試器可以通過閱讀pdb就得到了一切的信息(看那個完美的調試器界面就知道了!),那我自然也可以從pdb里面找到所有東西的。抱著這個想法,我開始研究原生C++的pdb文件的符號的語義結構,后來就把我的經驗寫成了這兩篇博客:
http://www.shnenglu.com/vczh/archive/2012/03/10/167538.html和
http://www.shnenglu.com/vczh/archive/2012/03/10/167539.html。雖然PDB并沒有包含模板類的直接信息,不過這暫時不成問題,因為GacUI的大部分類也不是模板類。經過了這些研究,我就得到了一個相當于靜態反射的功能了。之所以說是靜態,是因為我沒辦法跟.net程序一樣通過反射來調用函數。但是這對于生成文檔來說已經足夠了。后來我做了一件事情,就是寫了個程序,讀pdb獲得所有的符號dump出一份xml,然后再寫一個程序把xml里面的符號和Visual Studio產生的那個XML總注釋文件的符號聯系起來(這個過程有點復雜,因為兩邊的表示方法不一樣……),得到了一份既包含符號的完整內容又包含對應的注釋的這么個幾十M的XML文件。之后我設計了一個簡單的文檔格式,寫了個程序把那個幾十M的XML文件轉換成用那個簡單的文檔格式表達的文檔。之所以這樣做是考慮到將來說不定除了HTML還要生成其他格式的文檔,于是就做了那么個程序可讀的中間格式。最后一步當然是讀這些文件產生HTML文件了。整個流程如下所示:
PDB(VC++編譯器提供) -> Symbols.XML (因為DIA是一個COM組件,所以這一步我用C++寫,下面所有的步驟都用C#寫。C#用起來還是更容易啊……)
Symbols.XML + Comment.XML(VC++編譯器提供) -> FullSymbols.XML
FullSymbols.XML -> *.docitem.txt
*.docitem.txt -> *.html
經過了這些步驟,我就得到了整整一個文件夾的一千五百多個HTML文件了。然后我把這些文件跟我的網站合并在一起上傳,就得到了第一個版本的GacUI網站了:
http://www.gaclib.net。當然現在已經看不到第一個版本的網站了。做完這些步驟之后,我就暫停了下來,繼續開發GacUI。一邊開發一邊產生Demo,添加Demo頁面,產生新的文檔,消滅一些沒用的函數的文檔。寫代碼的時候還比較容易,再把代碼的更改反映到這個網站的時候,就體現出了手寫純靜態網站的弱點:維護起來真TMD麻煩啊!雖然上面的這些步驟已經被我合并到了一個bat文件,每次雙擊就能自動完成,但是修改Demo頁面的時候還是人肉的。這讓我十分不爽。
一個偶然的機會,我用了信用卡注冊了一個Windows Azure的空間。這個空間其實并不powerful,只給了我相當于六顆CPU的計算能力。不過用來做這個網站已經足夠了。想到每次修改網站都要復制HTML代碼,修改了結構的話還要動所有的HTML文件,煩了大概一個月之后我就下定決心要把HTML做成動態生成的。在看了Windows Azure的一些介紹之后,我覺得ASP.NET MVC3加上Windows Azure的簡單存儲功能十分適合用來作這種東西。
剛開始接觸MVC3還是讓我覺得比較困難,不過最大的困難還是在于理解router的機制那里。GacUI的網站內容簡單,所以并不需要ASP.NET的其他高級技術。結果所有的困難都出現在router機制里面。在經過了兩天的學習之后,我初步的掌握了它的使用方法。MVC3的router基本上就是一個pattern matching的過程,把你的url映射到一個對controller的調用上面。你不僅可以映射controller的類名和函數名,還可以從url抽取一些參數。在掌握了它的原理之后,操作router的感覺就跟寫haskell一樣,又直接又清爽。接下來就是razor模板的事情。從programming language的觀點上看,razor是一個設計的相當出色地模板語言。第一個特點就是和宿主語言C#融合的十分緊密,第二個特點就是幾乎一點語法噪音都沒有。用過原始asp和php的人都知道代碼里面充滿了<%%>是一件十分令人討厭的事情。每一處代碼和HTML的切換都要<%%>,整個文件一眼望去就是一坨屎。razor很好地解決了這個問題。他采用了復雜的判斷方法來分辨哪些東西是C#,哪些東西是HTML。小部分C#和HTML的切換至需要一個@符號就搞定了,大部分的切換都是自動的。雖然偶爾razor會有分析錯誤的情況,但是他仍然提供了@:操作符來讓我們workaround這個分析過程。整個模板語言下來毫無語法噪音,寫起來十分直接,十分干凈。
在網站差不多做完之后,我往生成HTML文檔的程序添加了一個功能:生成一個包含HTML文檔內容和元數據的XML。然后我寫了一個程序把這一大堆XML灌入了Windows Azure的Blob Storage里面。Blob Storage就跟一個硬盤一樣,可以用來存放大量的不需要計算(SQL數據庫就屬于那種需要計算的)數據。然后我給每一個文檔頁面建立了一個統一的Model,Model里面包含了“讀取和分析這些XML文件”的功能,controller則做一個簡單的轉發,最后在model里面把所有被Model標記出來的需要改寫的URL都用@Url.Action來處理。在這個過程中我學到了一個razor的小技巧:雖然不是很安全,但是在razor里面使用MvcHtmlString可以繞過html encoding的功能,把存儲在變量里面的HTML代碼直接嵌進頁面。這么用的時候需要有清醒的意識。
如今GacUI終于有了鏡像網站:
http://asia.gaclib.net和
http://us.gaclib.net了。萬網的破爛空間自然不可能直接訪問Windows Azure Storage了。所以我采用了一個看起來比較傻逼的方法。首先我修改好網站之后,上傳到EastAsia和West US兩個服務器,然后我寫了一個程序再把每一個生成好的HTML頁面下載下來。下載的過程其實就和寫爬蟲差不多,每獲得一個新的HTML文件就去分析里面的鏈接,然后繼續下載。寫完了之后我發現這個小程序還有了發現死鏈的功能,直接找出了網站代碼的幾個bug。下載完之后FTP到萬網的空間里面。這個服務器在杭州。因此網站就有三個服務器了。
整個過程零零碎碎耗費了大約半年的時間,都是利用每天下班后的時間完成的。自己又點了技能樹的一個新子樹,漲了點經驗值,覺得這些經驗對于某些人來說可能還有參考的價值,于是就寫了這篇博客,大家共勉。
posted @
2012-07-09 10:27 陳梓瀚(vczh) 閱讀(4689) |
評論 (9) |
編輯 收藏
博客光寫GacUI(
www.gaclib.net)的Demo更新也好無聊啊。所以今天先換換口味,胡扯點別的。
一年一度的高中畢業生填簡歷的日子即將就要到了,又有很多人問計算機專業的事情。其實我從心底里覺得,高考后才來問這個,已經完了一大半了。當然另一小半十分有前途的人可以在大學四年趕上來,不過估計他們還是要讀個研究生,才能把自己訓練成能用的碼農。
編程是一件很難的事情。當然我的意思跟那篇著名的《編程是一件很難的事情》不一樣。想把代碼寫好,本來就是一件非常困難的事情。我大三的時候訓練一個大一的老鄉,就光是C++,長達四年后她還搞不清楚模板元編程究竟是什么。而且還有C語言學會了轉C++會把壞習慣帶進來啦,C++的人轉做C#之后發現很多C++的好技巧到了C#都只會讓程序變得更慢啦,很多寫動態語言的人不理解類型的好處還在那里胡扯啦,還有C#和javascript明明放著大好的函數式風格不用,非要把代碼寫的超長(本來光是這樣沒什么問題的,只是有某些人不肯學習新知識)。可見,就算把自己訓練了好多年,最終進入了工作崗位,想把代碼寫好,也是一件非常困難的事情。
當然有些人說,如今只有產品做得好才能賺錢,代碼寫的好有個屁用。這只能是人各有志,有些人就不喜歡鉆研代碼,這本來也沒什么。但是這些人老是跳出來忽悠別人,也只會讓編程變得更難。只是幸好,我的單位并不會跟某些單位一樣說一些“把代碼寫得那么好有什么用,搞到我們還非得學東西才能看你的代碼,趕緊做點新feature啦”的這種話,我已經覺得很好了。
寫得好這個東西還是比較抽象。我認為其中一條就是代碼要好維護。我一直以為,只有代碼寫得好維護,好改,清晰易懂,這樣加新的功能才會容易,不出事情,順利發布軟件。后來我發現我錯了,騰訊不也是QQ一版一版的發嗎,原來加班也是一種方法,啊哈哈哈。如果在一個單位里面,不加班別人就會找你麻煩的話,我相信你也不會花心思把代碼寫好的,反正都要加班。
不過對于志向就是寫代碼的那一些人,最好還是不要受到這些外來信息的干擾。最近跟我們組里的一個test manager聊天,他是一個菲律賓人,說是從紙帶時代開始就寫代碼了(不過看起來好年輕……),工作的時候還覺得C語言是一個嶄新的語言。后來他跟我說,如果一個人有志向與,代碼一條路走到黑,最好就去學習一下怎么當architect。他說道,Architect的知識架構是由各種pattern組成的,然后就說了自己年輕的時候的很多故事來作證這個道理。然后還講了微軟的其中一個創始人到現在還堅持一線寫代碼的事情,不過沒告訴我是誰。
在這之前,剛好MSR的Daan Leijen因為來北京參加programming language相關的conference,就來我們這里參觀了一下。后來我看他做過GUI,做過parser combinator,發明實現過語言,就前去搭訕,結果發現他讀書的時候的導師竟然是Erik Meijer。按照他的話說,“then we are connected”,如果說成中文,就是有緣分吧。接著就跟他討論了一些parser combinator和類型系統之類的東西。我說我之前也搞過這些東西,最后還貢獻了一部分給公司,換了個組之后還開了講座什么的。他講到他讀書的時候,也是學校沒教自己自學的這些東西,后來周圍也沒什么人做,但是并沒有讓他喪失動力。然后就說了一句話讓我印象很深刻:“原來你也做這些東西啊,我應該可以看到為什么你要從產品組跳到MSRA來了。”他直到今天,頭發都基本上掉光了,還在那里繼續研究programming language的東西,還給了我幾篇論文。我覺得很好,人就該像他那樣。
有些時候,人就得有那個信念,才能把可行但是難度大的東西,也最終搞出來。我自己寫了11年的程序,其實并沒有接觸過十分廣泛的東西,因為很多時間都花在重寫我的一些idea上面了。譬如說編譯器就寫了五六個,GUI庫就寫了八遍,還有些雜七雜八的。不過從這個過程之中,可以明顯感覺到自己什么時候比以前更進一步。這種signal有很多,譬如說當你決定要添加一個比較復雜的功能,也可以迅速知道怎么做而不用動到架構啦;譬如說你覺得你的代碼越來越順眼啦;譬如說你因為架構不行決定重寫的時候,你發現前一個版本的代碼可以撿起來繼續用的部分越來越多啦。
寫到這里,我想起很多人都問過我,程序要怎么寫才能寫得好,或者說設計模式要怎么寫,之類的問題。如果把學習編程花費的精神代價做標準的話,捷徑是沒有的。但是如果僅僅把時間作為標準的話,捷徑顯然是有的。怎樣才能加速你學習的過程呢?答案就是,先寫再看書。對于像編譯原理這種略微高深的知識,總要自己寫過幾遍,吃了一些苦頭,才能知道為什么書里非要把算法那么設計結構那么安排。對于像設計模式這種需要大量經驗才可以領悟到的知識,如果你從來沒獨立寫過一個上萬行的程序,你覺得你能理解設計模式在講什么嗎?我覺得這種時候能做的也就是背下來,理解什么的都是扯淡。諸如此類,學習程序,如果要加速那個過程,肯定要花大量的時間寫代碼。當你把項目做得越大、越復雜、算法越扭曲、界面越華麗、尺寸已經大到你覺得不學習新的方法論就肯定會讓代碼失控的時候,這個時候你來看設計模式的書,保證是每看到一個模式都覺得人家說到你心坎里去了。那你不僅可以迅速理解,而且以后還可以不由自主的想起來使用它。
當然,如果你不是一個喜歡寫代碼的人,那這個方法肯定沒有用,因為中途放棄什么的太多了。這種時候,只能怪你沒緣分,設計模式不渡你了。如果你最后撐下來了,雖然你自己覺得你也花費了相當的努力,但是別人反正是看不到你的努力的,就會開始覺得你有捷徑了。為什么呢?因為效率高啊,時間花得短啊。
光寫代碼也是沒用的。同人于野一篇講成年人還能不能進步的博客說得很好,知識分為舒適區,學習區和恐慌區。舒適區的意思就是,你很容易就可以做完。學習區的意思就是,你需要花費大量的智力才可以做完。恐慌區的意思就是,你根本不知道如何下手。當你在為了練習編寫大量的代碼的時候,你要盡量把題目都安排在學習區這里,這樣才能讓你進步快的同時,還不會被問題打倒,可以繼續積累成就感了。
學生做這個最方便了,工作之后,如果剛好遇上個黑心公司要你天天加班,你反而沒時間做學習區的內容了,公司給你的肯定是舒適區的苦力活。
說到這里,如果你還有時間練習的話,千萬不要去想:“我每一個程序都要跨平臺”,“我只做這個語言”等等。反正將來,語言你都要會,平臺的差異你都要知道,為什么要斷送自己了解這些東西的機會呢?你真的以為不知道垃圾收集的原理,和一些底層的可以通過C++的練習而得到的的操作,你真的可以在某些關鍵時刻操縱好C#嗎?當然有些人會覺得,我估計一輩子不會遇到這些問題的,所以我還是不管他了。人各有志嘛,C#不渡你,也是你自己的事情。如果你真的可以一輩子都在一個平臺上用一種語言做同一種程序做到退休,那真是幸福的生活啊。
胡扯到這里也差不多了,這就是月經貼,時不時,總是要發一下的。
posted @
2012-06-21 09:59 陳梓瀚(vczh) 閱讀(10359) |
評論 (16) |
編輯 收藏
摘要: GacUI的ListView支持Windows 7資源管理器的六種View,并且在默認的皮膚下表現的跟資源管理器十分類似。這個Demo也使用了一些Shell API來獲得資源管理器使用的文件的圖標、文件類型的字符串等等。完整的代碼可以在http://www.gaclib.net/Demos/Controls.ListView.ViewSwitching/Dem...
閱讀全文
posted @
2012-06-04 09:15 陳梓瀚(vczh) 閱讀(7738) |
評論 (8) |
編輯 收藏
GacUI的所有列表控件都支持虛擬模式。虛擬模式是一種不需要為每一個列表項分配內存的一種顯示方法。在開始的時候,需要高速列表一共有多少個列表項。之后,列表控件在渲染的時候,會跟數據源要求獲取某一個下標所包含的數據,并且在這個數據一直處于屏幕上的時候,只會跟數據源獲取一次。完整的代碼可以在
http://www.gaclib.net/Demos/Controls.ListBox.VirtualMode/Demo.html看到。先上圖:

先看創建界面的代碼。一般來說,所有可以隨著窗口的變化自動排版的控件組織方法,都是使用一個或多個GuiTableComposition來實現的。
class VirtualModeWindow : public GuiWindow
{
private:
GuiVirtualTextList* listBox;
GuiButton* buttonIncrease;
GuiButton* buttonDecrease;
DataSource* dataSource;
void buttonIncrease_Clicked(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
dataSource->SetCount(dataSource->Count()+100000);
}
void buttonDecrease_Clicked(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
dataSource->SetCount(dataSource->Count()-100000);
}
public:
VirtualModeWindow()
:GuiWindow(GetCurrentTheme()->CreateWindowStyle())
{
this->SetText(L"Controls.ListBox.VirtualMode");
GuiTableComposition* table=new GuiTableComposition;
table->SetRowsAndColumns(3, 2);
table->SetCellPadding(3);
table->SetAlignmentToParent(Margin(0, 0, 0, 0));
table->SetRowOption(0, GuiCellOption::MinSizeOption());
table->SetRowOption(1, GuiCellOption::MinSizeOption());
table->SetRowOption(2, GuiCellOption::PercentageOption(1.0));
table->SetColumnOption(0, GuiCellOption::PercentageOption(1.0));
table->SetColumnOption(1, GuiCellOption::MinSizeOption());
this->GetContainerComposition()->AddChild(table);
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(0, 0, 3, 1);
dataSource=new DataSource;
listBox=new GuiVirtualTextList(GetCurrentTheme()->CreateTextListStyle(), GetCurrentTheme()->CreateTextListItemStyle(), dataSource);
listBox->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
listBox->SetHorizontalAlwaysVisible(false);
cell->AddChild(listBox->GetBoundsComposition());
}
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(0, 1, 1, 1);
buttonIncrease=g::NewButton();
buttonIncrease->SetText(L"Increase 100000 Items");
buttonIncrease->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
buttonIncrease->Clicked.AttachMethod(this, &VirtualModeWindow::buttonIncrease_Clicked);
cell->AddChild(buttonIncrease->GetBoundsComposition());
}
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(1, 1, 1, 1);
buttonDecrease=g::NewButton();
buttonDecrease->SetText(L"Decrease 100000 Items");
buttonDecrease->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
buttonDecrease->Clicked.AttachMethod(this, &VirtualModeWindow::buttonDecrease_Clicked);
cell->AddChild(buttonDecrease->GetBoundsComposition());
}
// set the preferred minimum client size
this->GetBoundsComposition()->SetPreferredMinSize(Size(480, 480));
// call this to calculate the size immediately if any indirect content in the table changes
// so that the window can calcaulte its correct size before calling the MoveToScreenCenter()
this->ForceCalculateSizeImmediately();
// move to the screen center
this->MoveToScreenCenter();
}
};
GuiVirtualTextList就是只有虛擬模式的GuiTextList。事實上GuiVirtualTextList是GuiTextList的基類,而GuiTextList.GetItems()返回的對象也是一個數據源。因此非虛擬模式其實也是通過虛擬模式來實現的。在數據比較少的時候,非虛擬模式操作起來十分的簡單,而在數據比較多的時候,虛擬模式可以帶來很好的性能。上面的代碼創建了一個DataSource類來做數據源,并且有一個SetCount的函數用來更改列表里面的數量的總量,然后每一個列表項的內容都是Item xxx。這是怎么做到的呢?我們來看數據源的代碼:
class DataSource : public list::ItemProviderBase, private list::TextItemStyleProvider::ITextItemView
{
protected:
int count;
public:
DataSource()
:count(100000)
{
}
void SetCount(int newCount)
{
if(0<=newCount)
{
int oldCount=count;
count=newCount;
// this->InvokeOnItemModified(affected-items-start, affected-items-count, new-items-count);
// this function notifies the list control to update it's content and scroll bars
if(oldCount<newCount)
{
// insert
this->InvokeOnItemModified(oldCount, 0, newCount-oldCount);
}
else if(oldCount>newCount)
{
// delete
this->InvokeOnItemModified(newCount, oldCount-newCount, 0);
}
}
}
// GuiListControl::IItemProvider
int Count()
{
return count;
}
IDescriptable* RequestView(const WString& identifier)
{
if(identifier==list::TextItemStyleProvider::ITextItemView::Identifier)
{
return this;
}
else if(identifier==GuiListControl::IItemPrimaryTextView::Identifier)
{
return this;
}
else
{
return 0;
}
}
void ReleaseView(IDescriptable* view)
{
}
// list::TextItemStyleProvider::ITextItemView
WString GetText(int itemIndex)
{
return L"Item "+itow(itemIndex+1);
}
bool GetChecked(int itemIndex)
{
// DataSource don't support check state
return false;
}
void SetCheckedSilently(int itemIndex, bool value)
{
// DataSource don't support check state
}
// GuiListControl::IItemPrimaryTextView
WString GetPrimaryTextViewText(int itemIndex)
{
return GetText(itemIndex+1);
}
bool ContainsPrimaryText(int itemIndex)
{
return true;
}
};
對于GuiVirtualTextList來說,只需要實現vl::presentation::controls::list::TextItemStyleProvider::ITextItemView就可以了。GacUIIncludes.h里面已經有了using namespace vl::presentation::controls,所以在這里只需要從list::開始寫。list::TextItemStyleProvider::ITextItemView還要求實現GuiListControl::IItemPrimaryTextView。在目前的GacUI里面,IItemPrimaryTextView是專門為下拉框準備的。因為下拉框允許接受任何一種列表對象當做下拉內容,所以GacUI的列表數據源默認都要求實現IItemPrimaryTextView。
實現數據源的時候,其實并不要求數據源類繼承自ITextItemView和IItemPrimaryTextView。因為GacUI都是通過RequestView來獲取一個View的接口指針的,代碼如上。實現這兩個View也很簡單,在這里就不贅述了。
GuiTextList就介紹到這里了,接下來的幾個Demo都將是關于ListView的。下一個Demo是ListView山寨Windows 7的資源管理器界面,可以在
http://www.gaclib.net/Demos/Controls.ListView.ViewSwitching/Demo.html看到。具體內容將在下一篇博客中闡述。
posted @
2012-05-30 07:19 陳梓瀚(vczh) 閱讀(2477) |
評論 (1) |
編輯 收藏
摘要: 趁此機會做個廣告,http://www.gaclib.net終于上線啦!
GacUI的列表控件的第二個Demo是關于列表項的多選的。跟Windows一樣,我們可以通過鼠標和方向鍵,配合CTRL和SHIFT選擇列表的多個內容。因此這次我實現了一個簡單的“名字選擇窗口”,就跟QQ邀請好友入群的界面一樣...
閱讀全文
posted @
2012-05-25 21:54 陳梓瀚(vczh) 閱讀(3633) |
評論 (12) |
編輯 收藏