日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

ATL的GUI程序设计(3)

發(fā)布時間:2023/11/27 生活经验 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ATL的GUI程序设计(3) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

第三章 ATL的窗口類

CWindowImpl、CWindow、CWinTraits,ATL窗口類的奧秘盡在此三者之中。在本章里,李馬將為你詳細解說它們的使用方法。另外,本章的內(nèi)容也可以算是本書的核心部分——如果你要進行ATL的GUI程序設(shè)計的話,就必須將ATL的窗口類設(shè)計理念了然于心。

窗口的組成

把ATL的窗口類撇開不談先。我在上一章中提到:窗口類并非任何一種OOP語言中的類——它所包括的并不是通稱的屬性和方法(在C++中稱作成員變量和成員函數(shù)),而是屬性和響應。現(xiàn)在是解釋這句話的時候了。
所謂窗口的屬性,無非是窗口的樣式(style)、背景畫刷(brush)、圖標(icon)、光標(cursor)……等元素。你可以從WNDCLASS及WNDCLASSEX中找到它們。需要特別指出的是,窗口的樣式事實上包括窗口類的樣式和窗口實例的樣式,窗口類的樣式在注冊窗口類之前經(jīng)由WNDCLASS::style或WNDCLASSEX::style指定,而窗口實例的樣式則是在創(chuàng)建窗口(CreateWindow/CreateWindowEx)的時候指定的。
對于窗口的響應,即是指窗口收到某消息后的處理。(在VB、Delphi等RAD環(huán)境中,處理窗口的響應亦稱作窗口的事件處理。)對于SDK而言,為窗口提供響應也就是為窗口類提供一個回調(diào)函數(shù),在回調(diào)函數(shù)中對我們感興趣的窗口消息進行特殊處理,譬如上一章中針對WM_DESTROY和WM_PAINT的處理。
另外,我們在進行Win32程序設(shè)計的時候,往往還需要對窗口進行操作,譬如ShowWindow和UpdateWindow——姑且讓我稱之為“方法”。
屬性、方法、事件,這回這哥仨算齊了。我們在對窗口進行C++封裝時,需要考慮的也正是這三者。自然,依據(jù)OO的理念,我們可以很簡單地將句柄作為成員變量,將方法作為成員函數(shù),然后將事件經(jīng)由某種特定的消息分流手段移交給各個成員函數(shù)進行響應處理,加之對不同種類的窗口使用繼承進行區(qū)分——這就是MFC的封裝做法。大家如果有興趣的話,可以打開MFC的afxwin.h看一看CWnd類的代碼。

ATL窗口類的活版封裝

MFC的CWnd是一個冗長得有些過分的類。究其原因,窗口類的封裝理念決定了窗口類的消息分流,而消息分流則決定了類的代碼篇幅。如果你已經(jīng)打開了afxwin.h文件,就可以發(fā)現(xiàn)CWnd花了很大的篇幅在“On”開頭的事件響應函數(shù)上。其實在我們進行Win32程序設(shè)計的時候,真正感興趣的事件沒有幾個,所以說“萬能”勢必造就冗長。
另外,考慮MFC的誕生年代,所以對于窗口的封裝只是采用了C++的低端特性——例如薄層的封裝和單向繼承。(題外話:而且MFC中還存在著一些諸如CString、CArray、CList之類的工具,蓋因其時STL還未標準化之故。)隨著MFC的發(fā)展,任憑它做出任何優(yōu)化,也無法避免當初架構(gòu)理念帶來的效率陰影和偏差。
ATL的誕生年代晚于MFC,使之能夠有機會使用C++的高端特性,也就是模板和多重繼承。于是,它使用了一種全新的封裝理念:將屬性、方法、事件分別獨立出來,然后利用模板和多重繼承的特性將這三者根據(jù)需要而組合在一起——打個比方來說,如果MFC的窗口封裝是雕版印刷術(shù),那么ATL的窗口封裝就是活版印刷術(shù)。以上一章的CHelloATLWnd類為例,它的繼承層次如下圖:

這是一個稍顯冗長的繼承鏈,不過我并不打算對它進行詳細的解說。在此,我只請你看這個繼承層次的最底層和最上層。從最底層來看,CHelloATLWnd繼承自CWindowImpl,CWindowImpl有三個模板參數(shù):T、TBase、TWinTraits。再看最上層,CWindowImplRoot繼承自TBase和CMessageMap。T參數(shù)即是你所繼承下來的子類名,通常用于編譯期的虛函數(shù)機制(后邊我會對這一機制進行介紹);TBase參數(shù)為對窗口方法和句柄的封裝;TWinTraits是窗口樣式的類封裝;CMessageMap是對窗口事件響應的封裝。
下面,就讓李馬來逐一將這些組成部分介紹給你吧。

窗口樣式的封裝

窗口樣式通常由CWinTraits類封裝,這個類很簡單,如下:

C++代碼
  1. /???
  2. //?CWinTraits?–?Defines?various?default?values?for?a?window???
  3. ??
  4. template?<DWORD?t_dwStyle?=?0,?DWORD?t_dwExStyle?=?0> ??
  5. class?CWinTraits ??
  6. { ??
  7. public: ??
  8. ????static?DWORD?GetWndStyle(DWORD?dwStyle) ??
  9. ????{ ??
  10. ????????return?dwStyle?==?0???t_dwStyle?:?dwStyle; ??
  11. ????} ??
  12. ????static?DWORD?GetWndExStyle(DWORD?dwExStyle) ??
  13. ????{ ??
  14. ????????return?dwExStyle?==?0???t_dwExStyle?:?dwExStyle; ??
  15. ????} ??
  16. };??

這個類有兩個模板參數(shù):dwStyle和dwExStyle,也就是CreateWindowEx中要用到的那兩個樣式參數(shù)。在CHelloATLWnd::Create(其實也就是CWindowImpl::Create)調(diào)用的時候,窗口的樣式就是由CWinTraits::GetWndStyle/CWinTraits::GetWndExStyle決定的。
另外,ATL還為常用的窗口樣式提供了幾個typedef,如CControlWinTraits、CFrameWinTraits、CMDIChildWinTraits。在你需要它們這些特定樣式或者需要對它們進行擴展的時候,可以直接進行使用或者使用CWinTraitsOR類來進行進一步的樣式組合,這里我就不多介紹了。

窗口方法的封裝

說白了,窗口方法的封裝其實就是把窗口句柄和常用的窗口操作API函數(shù)(也就是那些第一個參數(shù)為HWND類型的API函數(shù))進行一層薄薄的綁定。這樣做的好處有二:第一,使代碼更有邏輯性,符合OO的設(shè)計理念;第二,在對SendMessage進行封裝后,可以增加對消息參數(shù)的類型檢查。
CWindow類的內(nèi)容我就不列出了,因為它同樣十分冗長,大家可以參看atlwin.h的相關(guān)內(nèi)容。在這里我僅對其中的幾個地方進行解說:

  • 它只有一個非static的成員變量,也就是窗口的句柄m_hWnd。這樣做的好處是使得CWindow類的對象占用最小的資源,同時給程序員提供最大的自由度。與MFC的CWnd類相比,CWindow的優(yōu)點體現(xiàn)得尤為明顯。CWnd之中還存在著一些MFC Framework要用到的東西,比如RTTI信息等等。此外,MFC內(nèi)部還會為每個窗口句柄維護一個相對應的CWnd對象,形成一個對象鏈,這樣程序員可以通過GetDlgItem獲取CWnd類的指針,但是這同時也為系統(tǒng)增加了很多額外的負擔。
  • CWindow提供了對operator=操作符的重載,這樣程序員可以直接將一個HWND賦給一個CWindow對象。
  • CWindow::Attach/CWindow::Detach提供了CWindow對象與HWND的綁定/解除綁定功能。
  • CWindow提供了對operator HWND類型轉(zhuǎn)換操作符的重載,這樣在用到HWND類型變量的時候,可以直接使用CWindow對象來代替。

有了CWindow類之后,如果你需要對窗口進行更多的操作,就可以對其進行繼承,例如CButton、CListBox、CEdit等等。這樣一來,代碼的復用性就大大提高了。

窗口事件響應的封裝

窗口事件響應的封裝,也就是這個類如何對窗口消息進行分流。你應該還記得,CHelloATLWnd類是通過BEGIN_MSG_MAP、END_MSG_MAP和MESSAGE_HANDLER宏實現(xiàn)的。如果你參閱了atlwin.h中它們的定義,你就會發(fā)現(xiàn)其實它們會組成一個ProcessWindowMessage函數(shù)。是的,CMessageMap就是由這個函數(shù)組成的:

C++代碼
  1. /???
  2. //?CMessageMap?–?abstract?class?that?provides?an?interface?for?message?maps???
  3. ??
  4. class?ATL_NO_VTABLE?CMessageMap ??
  5. { ??
  6. public: ??
  7. ????virtual?BOOL?ProcessWindowMessage(HWND?hWnd,?UINT?uMsg,?WPARAM?wParam,?LPARAM?lParam, ??
  8. ????????LRESULT&?lResult,?DWORD?dwMsgMapID)?=?0; ??
  9. };??

CWindowImplRoot派生自CMessageMap,所以CWindowImplRoot及至CWindowImpl都需要實現(xiàn)ProcessWindowMessage以完成窗口消息的分流。大家可以看到,這個函數(shù)的前四個參數(shù)是在SDK程序設(shè)計中窗口回調(diào)的原班人馬,在此不多介紹。lResult用來接收各消息處理函數(shù)的返回值,然后返回給最初的WndProc作為返回值。dwMsgMapID是一個神秘參數(shù),且待李馬留到以后再進行講解。
“等等!”也許你會突然打斷我,“——ATL是如何將WndProc封裝到類的成員函數(shù)中的?”的確,在編譯器的處理下,C++類中非static成員函數(shù)的參數(shù)尾部會被加入一個隱藏的this指針,這就使得它實際與回調(diào)函數(shù)的規(guī)格不合,所以非static成員函數(shù)是不能作為Win32的回調(diào)函數(shù)的。
先看MFC是如何做的吧。它采用一張龐大的消息映射表避開了這個敏感的地方,對此感興趣的朋友們可參見JJHou先生的《深入淺出MFC》。也正因此,CWnd不得不為大部分消息各實現(xiàn)一個消息處理函數(shù)。還好這些消息處理函數(shù)不是虛函數(shù),否則CWnd會維護多么龐大的一張?zhí)摵瘮?shù)表!
而ATL的奇妙之處也正是在此。它采用了thunk機制,即是在執(zhí)行真正的WndProc回調(diào)之前刷改了內(nèi)存中的機器碼,將HWND參數(shù)用本窗口類的this指針替換了,然后在執(zhí)行真正的代碼之前再將這個指針轉(zhuǎn)換回來。這樣,就將this指針的矛盾巧妙化解了。由于本書講解的是關(guān)于如何使用ATL進行GUI程序設(shè)計方面的內(nèi)容,所以李馬不在此進行過多探討了就,感興趣的朋友們可以自己研究atlwin.h中CWindowImplBaseT的代碼,或者參考Zeeshan Amjad先生的《ATL Under the Hook Part 5》一文。
在thunk機制的幫助下,ATL的窗口類就可以直接將不感興趣的消息交由DefWindowProc進行處理,而不用像MFC一樣實現(xiàn)那么多消息處理函數(shù)。對于我們感興趣的消息,可以使用ATL中的BEGIN_MSG_MAP/END_MSG_MAP宏來在窗口類的成員函數(shù)ProcessWindowMessage中完成。此外對于消息的分流,除了MESSAGE_HANDLER宏,我們還可以使用其它的幾個宏進行各種消息(命令消息、普通控件通知消息、公共控件通知消息)的分流,我將在后邊專門的一章中對ATL的CMessageMap的使用方法來進行講解。

組合

葫蘆兄弟單打獨斗都不是蛇精的對手,所以葫蘆山神就會派仙鶴攜帶七色彩蓮找到他們,最后七個葫蘆娃合體成為威力無比的葫蘆小金剛,消滅了妖精,人世間重獲太平……
這自然是一個非常老套的故事,但想必如我一樣的80s生人看到后仍然會感慨不已。在那個少兒的精神食糧異常匱乏的年代,這部有些程式化臉譜化的動畫片告訴了我們一個簡單的道理:只有團結(jié)起來,才能發(fā)揮最大的力量。
ATL的窗口類也是如此,單憑CWinTraits、CWindow、CMessageMap這哥仨單打獨斗是不可能成就大氣候的。我們需要做的,就是使用某種方法來將它們組合起來。感謝C++為我們帶來的多重繼承和模板——多重繼承讓我們能夠?qū)⑺鼈兘M合,模板讓我們能夠?qū)⑺鼈冹`活地組合(所謂“靈活地組合”,即是在CWindowImpl層通過填入模板參數(shù)來決定繼承鏈的頂層CWindowImplRoot的多重繼承情況)。那么,再回到上一章的窗口類CHelloATLWnd:

C++代碼
  1. class?CHelloATLWnd?:?public?CWindowImpl<?CHelloATLWnd,?CWindow,?CWinTraits<?WS_OVERLAPPEDWINDOW?>?> ??
  2. { ??
  3. public: ??
  4. ????CHelloATLWnd() ??
  5. ????{ ??
  6. ????????CWndClassInfo&?wci?????=?GetWndClassInfo(); ??
  7. ????????wci.m_bSystemCursor????=?TRUE; ??
  8. ????????wci.m_lpszCursorID?????=?IDC_ARROW; ??
  9. ????????wci.m_wc.hbrBackground?=?(HBRUSH)GetStockObject(?WHITE_BRUSH?); ??
  10. ????????wci.m_wc.hIcon?????????=?LoadIcon(?NULL,?IDI_APPLICATION?); ??
  11. ????} ??
  12. public: ??
  13. ????DECLARE_WND_CLASS(?_T("HelloATL")?) ??
  14. public: ??
  15. ????BEGIN_MSG_MAP(?CHelloATLWnd?) ??
  16. ????????MESSAGE_HANDLER(?WM_DESTROY,?OnDestroy?) ??
  17. ????????MESSAGE_HANDLER(?WM_PAINT,?OnPaint?) ??
  18. ????END_MSG_MAP() ??
  19. public: ??
  20. ????LRESULT?OnDestroy(?UINT?uMsg,?WPARAM?wParam,?LPARAM?lParam,?BOOL&?hHandled?) ??
  21. ????{ ??
  22. ????????::PostQuitMessage(?0?); ??
  23. ????????return?0; ??
  24. ????} ??
  25. ????LRESULT?OnPaint(?UINT?uMsg,?WPARAM?wParam,?LPARAM?lParam,?BOOL&?hHandled?) ??
  26. ????{ ??
  27. ????????HDC?hdc; ??
  28. ????????PAINTSTRUCT?ps; ??
  29. ??
  30. ????????hdc?=?BeginPaint(?&ps?); ??
  31. ????????DrawText(?hdc,?_T("Hello,?ATL!"),?-1,?&ps.rcPaint,?DT_CENTER?|?DT_VCENTER?|?DT_SINGLELINE?); ??
  32. ????????EndPaint(?&ps?); ??
  33. ????????return?0; ??
  34. ????} ??
  35. };??

不知道你現(xiàn)在再看到這個類是否會少幾分生疏?在這里,CWindowImpl就擔任了“七色彩蓮”的角色——BEGIN_MSG_MAP/END_MSG_MAP是CMessageMap由繼承帶來的,BeginPaint/EndPaint是CWindow由模板和多重繼承帶來的,以及控制窗口樣式的CWinTraits(在這里要提醒一點,在將CWinTraits作為CWindowImpl的模板參數(shù)時,一定要將CWinTraits的模板參數(shù)右尖括號與CWindowImpl的模板參數(shù)右尖括號用空格分隔開,否則湊在一起的兩個右尖括號“>>”將會被編譯器判斷為右移操作符)是由模板帶來的。
當然,我還要回答上一章遺留下來的問題:WNDCLASSEX窗口類是如何注冊的?
如果你是前已經(jīng)偷偷看過CWindowImpl::Create的代碼,那么相信這個問題你已經(jīng)知道答案了。不過我還是要把相關(guān)代碼列出來:

C++代碼
  1. //?from?CWindowImpl::Create???
  2. if?(T::GetWndClassInfo().m_lpszOrigName?==?NULL) ??
  3. ????T::GetWndClassInfo().m_lpszOrigName?=?GetWndClassName(); ??
  4. ATOM?atom?=?T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);??

也就是說,窗口類的注冊是在窗口創(chuàng)建前完成的。
下面,李馬請你注意上面代碼中GetWndClassInfo的部分。這個函數(shù)是由窗口類的編寫者——也就是我們,ATL的GUI開發(fā)者——完成的,它的主要功能是用來獲取窗口類的屬性。在通常的情況下,GetWndClassInfo使用DECLARE_WND_CLASS/DECLARE_WND_CLASS_EX的形式來實現(xiàn)。參看DECLARE_WND_CLASS宏的定義:

C++代碼
  1. #define?DECLARE_WND_CLASS(WndClassName)????
  2. static?CWndClassInfo&?GetWndClassInfo()? ??
  3. {? ??
  4. ????static?CWndClassInfo?wc?=? ??
  5. ????{? ??
  6. ????????{?sizeof(WNDCLASSEX),?CS_HREDRAW?|?CS_VREDRAW?|?CS_DBLCLKS,?StartWindowProc,? ??
  7. ??????????0,?0,?NULL,?NULL,?NULL,?(HBRUSH)(COLOR_WINDOW?+?1),?NULL,?WndClassName,?NULL?},? ??
  8. ????????NULL,?NULL,?IDC_ARROW,?TRUE,?0,?_T("")? ??
  9. ????};? ??
  10. ????return?wc;? ??
  11. }??

這里已經(jīng)為要注冊的窗口類設(shè)置好了絕大多數(shù)的常用屬性,當然,如果你仍然覺得自己需要更改更多的屬性的話,可以像CHelloATLWnd的構(gòu)造函數(shù)里那么做。特別要指出的一點是,ATL對窗口類的光標(cursor)屬性是進行特殊處理的,對CWndClassInfo::m_wc.hCursor直接賦值是不行的。

編譯期的虛函數(shù)機制

ATL的效率遠遠高于MFC,其中一方面的原因就是它把很多的工作都通過模板來交給編譯器了,比如我上文提到的編譯期的虛函數(shù)機制。這個機制可以避免虛函數(shù)帶來的一切開銷而靜態(tài)實現(xiàn)虛函數(shù)的特性。考慮以下代碼:

C++代碼
  1. template?<?typename?T?> ??
  2. class?Parent ??
  3. { ??
  4. public: ??
  5. ????void?f() ??
  6. ????{ ??
  7. ????????cout?<<?"f?from?Parent."?<<?endl; ??
  8. ????} ??
  9. ????void?g() ??
  10. ????{ ??
  11. ????????T*?pT?=?(T*)this; ??
  12. ????????pT->f(); ??
  13. ????} ??
  14. }; ??
  15. ??
  16. class?Child1?:?public?Parent<?Child1?> ??
  17. { ??
  18. public: ??
  19. ????void?f() ??
  20. ????{ ??
  21. ????????cout?<<?"f?from?Child1."?<<?endl; ??
  22. ????} ??
  23. }; ??
  24. ??
  25. class?Child2?:?public?Parent<?Child2?> ??
  26. { ??
  27. };??

然后,這樣進行調(diào)用:

C++代碼
  1. Child1?c1; ??
  2. Child2?c2; ??
  3. c1.g();?//?f?from?Child1.???
  4. c2.g();?//?f?from?Parent.??

所有的奧秘盡在Parent::g之中,它通過一個類型轉(zhuǎn)換在編譯期就決定了調(diào)用哪個函數(shù),頗有些多態(tài)性的味道。ATL就是借助這樣的機制來保證效率的,如果你深入到atlwin.h的源代碼之中,肯定會發(fā)現(xiàn)更多諸如此類的例子。

轉(zhuǎn)載于:https://www.cnblogs.com/lgh1992314/p/6616354.html

總結(jié)

以上是生活随笔為你收集整理的ATL的GUI程序设计(3)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。