青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

笑看風云淡

寵辱不驚,看庭前花開花落;去留無意,望天空云卷云舒
posts - 96, comments - 48, trackbacks - 0, articles - 0
  C++博客 :: 首頁 :: 新隨筆 ::  :: 聚合  :: 管理

ATL的GUI程序設計(三)

Posted on 2007-10-17 10:40 天之驕子 閱讀(583) 評論(0)  編輯 收藏 引用

第三章 ATL的窗口類

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

窗口的組成

把ATL的窗口類撇開不談先。我在上一章中提到:窗口類并非任何一種OOP語言中的類——它所包括的并不是通稱的屬性和方法(在C++中稱作成員變量和成員函數),而是屬性和響應。現在是解釋這句話的時候了。

所謂窗口的屬性,無非是窗口的樣式(style)、背景畫刷(brush)、圖標(icon)、光標(cursor)……等元素。你可以從WNDCLASS及WNDCLASSEX中找到它們。需要特別指出的是,窗口的樣式事實上包括窗口類的樣式和窗口實例的樣式,窗口類的樣式在注冊窗口類之前經由WNDCLASS::style或WNDCLASSEX::style指定,而窗口實例的樣式則是在創建窗口(CreateWindow/CreateWindowEx)的時候指定的。

對于窗口的響應,即是指窗口收到某消息后的處理。(在VB、Delphi等RAD環境中,處理窗口的響應亦稱作窗口的事件處理。)對于SDK而言,為窗口提供響應也就是為窗口類提供一個回調函數,在回調函數中對我們感興趣的窗口消息進行特殊處理,譬如上一章中針對WM_DESTROY和WM_PAINT的處理。

另外,我們在進行Win32程序設計的時候,往往還需要對窗口進行操作,譬如ShowWindow和UpdateWindow——姑且讓我稱之為“方法”。

屬性、方法、事件,這回這哥仨算齊了。我們在對窗口進行C++封裝時,需要考慮的也正是這三者。自然,依據OO的理念,我們可以很簡單地將句柄作為成員變量,將方法作為成員函數,然后將事件經由某種特定的消息分流手段移交給各個成員函數進行響應處理,加之對不同種類的窗口使用繼承進行區分——這就是MFC的封裝做法。大家如果有興趣的話,可以打開MFC的afxwin.h看一看CWnd類的代碼。

ATL窗口類的活版封裝

MFC的CWnd是一個冗長得有些過分的類。究其原因,窗口類的封裝理念決定了窗口類的消息分流,而消息分流則決定了類的代碼篇幅。如果你已經打開了afxwin.h文件,就可以發現CWnd花了很大的篇幅在“On”開頭的事件響應函數上。其實在我們進行Win32程序設計的時候,真正感興趣的事件沒有幾個,所以說“萬能”勢必造就冗長。

另外,考慮MFC的誕生年代,所以對于窗口的封裝只是采用了C++的低端特性——例如薄層的封裝和單向繼承。(題外話:而且MFC中還存在著一些諸如CString、CArray、CList之類的工具,蓋因其時STL還未標準化之故。)隨著MFC的發展,任憑它做出任何優化,也無法避免當初架構理念帶來的效率陰影和偏差。

ATL的誕生年代晚于MFC,使之能夠有機會使用C++的高端特性,也就是模板和多重繼承。于是,它使用了一種全新的封裝理念:將屬性、方法、事件分別獨立出來,然后利用模板和多重繼承的特性將這三者根據需要而組合在一起——打個比方來說,如果MFC的窗口封裝是雕版印刷術,那么ATL的窗口封裝就是活版印刷術。以上一章的CHelloATLWnd類為例,它的繼承層次如下圖:

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

下面,就讓李馬來逐一將這些組成部分介紹給你吧。

窗口樣式的封裝

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

/////////////////////////////////////////////////////////////////////////////
// CWinTraits - Defines various default values for a window

template <DWORD t_dwStyle = 0, DWORD t_dwExStyle = 0>
class CWinTraits
{
public:
    static DWORD GetWndStyle(DWORD dwStyle)
    {
        return dwStyle == 0 ? t_dwStyle : dwStyle;
    }
    static DWORD GetWndExStyle(DWORD dwExStyle)
    {
        return dwExStyle == 0 ? t_dwExStyle : dwExStyle;
    }
};

這個類有兩個模板參數:dwStyle和dwExStyle,也就是CreateWindowEx中要用到的那兩個樣式參數。在CHelloATLWnd::Create(其實也就是CWindowImpl::Create)調用的時候,窗口的樣式就是由CWinTraits::GetWndStyle/CWinTraits::GetWndExStyle決定的。

另外,ATL還為常用的窗口樣式提供了幾個typedef,如CControlWinTraits、CFrameWinTraits、CMDIChildWinTraits。在你需要它們這些特定樣式或者需要對它們進行擴展的時候,可以直接進行使用或者使用CWinTraitsOR類來進行進一步的樣式組合,這里我就不多介紹了。

窗口方法的封裝

說白了,窗口方法的封裝其實就是把窗口句柄和常用的窗口操作API函數(也就是那些第一個參數為HWND類型的API函數)進行一層薄薄的綁定。這樣做的好處有二:第一,使代碼更有邏輯性,符合OO的設計理念;第二,在對SendMessage進行封裝后,可以增加對消息參數的類型檢查。

CWindow類的內容我就不列出了,因為它同樣十分冗長,大家可以參看atlwin.h的相關內容。在這里我僅對其中的幾個地方進行解說:

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

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


窗口事件響應的封裝

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

/////////////////////////////////////////////////////////////////////////////
// CMessageMap - abstract class that provides an interface for message maps

class ATL_NO_VTABLE CMessageMap
{
public:
    virtual BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
        LRESULT& lResult, DWORD dwMsgMapID) = 0;
};

CWindowImplRoot派生自CMessageMap,所以CWindowImplRoot及至CWindowImpl都需要實現ProcessWindowMessage以完成窗口消息的分流。大家可以看到,這個函數的前四個參數是在SDK程序設計中窗口回調的原班人馬,在此不多介紹。lResult用來接收各消息處理函數的返回值,然后返回給最初的WndProc作為返回值。dwMsgMapID是一個神秘參數,且待李馬留到以后再進行講解。

“等等!”也許你會突然打斷我,“——ATL是如何將WndProc封裝到類的成員函數中的?”的確,在編譯器的處理下,C++類中非static成員函數的參數尾部會被加入一個隱藏的this指針,這就使得它實際與回調函數的規格不合,所以非static成員函數是不能作為Win32的回調函數的。

先看MFC是如何做的吧。它采用一張龐大的消息映射表避開了這個敏感的地方,對此感興趣的朋友們可參見JJHou先生的《深入淺出MFC》。也正因此,CWnd不得不為大部分消息各實現一個消息處理函數。還好這些消息處理函數不是虛函數,否則CWnd會維護多么龐大的一張虛函數表!

而ATL的奇妙之處也正是在此。它采用了thunk機制,即是在執行真正的WndProc回調之前刷改了內存中的機器碼,將HWND參數用本窗口類的this指針替換了,然后在執行真正的代碼之前再將這個指針轉換回來。這樣,就將this指針的矛盾巧妙化解了。由于本書講解的是關于如何使用ATL進行GUI程序設計方面的內容,所以李馬不在此進行過多探討了就,感興趣的朋友們可以自己研究atlwin.h中CWindowImplBaseT的代碼,或者參考Zeeshan Amjad先生的《ATL Under the Hook Part 5》一文。

在thunk機制的幫助下,ATL的窗口類就可以直接將不感興趣的消息交由DefWindowProc進行處理,而不用像MFC一樣實現那么多消息處理函數。對于我們感興趣的消息,可以使用ATL中的BEGIN_MSG_MAP/END_MSG_MAP宏來在窗口類的成員函數ProcessWindowMessage中完成。此外對于消息的分流,除了MESSAGE_HANDLER宏,我們還可以使用其它的幾個宏進行各種消息(命令消息、普通控件通知消息、公共控件通知消息)的分流,我將在后邊專門的一章中對ATL的CMessageMap的使用方法來進行講解。

組合

葫蘆兄弟單打獨斗都不是蛇精的對手,所以葫蘆山神就會派仙鶴攜帶七色彩蓮找到他們,最后七個葫蘆娃合體成為威力無比的葫蘆小金剛,消滅了妖精,人世間重獲太平……

這自然是一個非常老套的故事,但想必如我一樣的80s生人看到后仍然會感慨不已。在那個少兒的精神食糧異常匱乏的年代,這部有些程式化臉譜化的動畫片告訴了我們一個簡單的道理:只有團結起來,才能發揮最大的力量。

ATL的窗口類也是如此,單憑CWinTraits、CWindow、CMessageMap這哥仨單打獨斗是不可能成就大氣候的。我們需要做的,就是使用某種方法來將它們組合起來。感謝C++為我們帶來的多重繼承和模板——多重繼承讓我們能夠將它們組合,模板讓我們能夠將它們靈活地組合(所謂“靈活地組合”,即是在CWindowImpl層通過填入模板參數來決定繼承鏈的頂層CWindowImplRoot的多重繼承情況)。那么,再回到上一章的窗口類CHelloATLWnd:

class CHelloATLWnd : public CWindowImpl< CHelloATLWnd, CWindow, CWinTraits< WS_OVERLAPPEDWINDOW > >
{
public:
    CHelloATLWnd()
    
public:
    DECLARE_WND_CLASS( _T("HelloATL") )
public:
    BEGIN_MSG_MAP( CHelloATLWnd )
        MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
        MESSAGE_HANDLER( WM_PAINT, OnPaint )
    END_MSG_MAP()
public:
    LRESULT OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& hHandled )
    {
        ::PostQuitMessage( 0 );
        return 0;
    }
    LRESULT OnPaint( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& hHandled )
    {
        HDC hdc;
        PAINTSTRUCT ps;

        hdc = BeginPaint( &ps );
        DrawText( hdc, _T("Hello, ATL!"), -1, &ps.rcPaint, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
        EndPaint( &ps );
        return 0;
    }
};

不知道你現在再看到這個類是否會少幾分生疏?在這里,CWindowImpl就擔任了“七色彩蓮”的角色——BEGIN_MSG_MAP/END_MSG_MAP是CMessageMap由繼承帶來的,BeginPaint/EndPaint是CWindow由模板和多重繼承帶來的,以及控制窗口樣式的CWinTraits(在這里要提醒一點,在將CWinTraits作為CWindowImpl的模板參數時,一定要將CWinTraits的模板參數右尖括號與CWindowImpl的模板參數右尖括號用空格分隔開,否則湊在一起的兩個右尖括號“>>”將會被編譯器判斷為右移操作符)是由模板帶來的。

當然,我還要回答上一章遺留下來的問題:WNDCLASSEX窗口類是如何注冊的?

如果你是前已經偷偷看過CWindowImpl::Create的代碼,那么相信這個問題你已經知道答案了。不過我還是要把相關代碼列出來:

// from CWindowImpl::Create
if (T::GetWndClassInfo().m_lpszOrigName == NULL)
    T::GetWndClassInfo().m_lpszOrigName = GetWndClassName();
ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);

也就是說,窗口類的注冊是在窗口創建前完成的。

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

#define DECLARE_WND_CLASS(WndClassName) 
static CWndClassInfo& GetWndClassInfo() 

    static CWndClassInfo wc = 
    { 
        { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc, 
          0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL }, 
        NULL, NULL, IDC_ARROW, TRUE, 0, _T("") 
    }; 
    return wc; 
}

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

編譯期的虛函數機制

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

template < typename T >
class Parent
{
public:
    void f()
    
    void g()
    {
        T* pT = (T*)this;
        pT->f();
    }
};

class Child1 : public Parent< Child1 >
{
public:
    void f()
    
};

class Child2 : public Parent< Child2 >
;

然后,這樣進行調用:

Child1 c1;
Child2 c2;
c1.g(); // f from Child1.
c2.g(); // f from Parent.

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


只有注冊用戶登錄后才能發表評論。
網站導航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲国产经典视频| 尤物九九久久国产精品的特点| 1024亚洲| 欧美亚洲在线播放| 久久久www成人免费毛片麻豆| 欧美激情久久久久久| 亚洲国产黄色片| 欧美综合激情网| 亚洲天堂成人| 欧美精品日韩精品| 一区二区在线看| 国产精品综合不卡av| 亚洲国产精品久久久| 久久人人九九| 久久er精品视频| 国产亚洲一区在线| 久久国产毛片| 亚洲制服少妇| 亚洲欧洲日产国码二区| 久久久视频精品| 久久精品国产77777蜜臀| 国产日韩三区| 久久久久久免费| 亚洲国产成人tv| 国产精品日本一区二区| 国产精品美女主播在线观看纯欲| 亚洲国产精品一区二区第一页| 久久―日本道色综合久久| 欧美亚洲一区三区| 国内精品一区二区| 欧美69wwwcom| 国产女优一区| 亚洲视频精选| 亚洲先锋成人| 国产日韩综合一区二区性色av| 欧美亚洲专区| 久久久久国产精品一区三寸| 亚洲淫片在线视频| 国产欧美一区二区三区在线老狼| 欧美在线视频不卡| 久久精品国产成人| 亚洲区在线播放| 一区二区日韩欧美| 国产亚洲精品成人av久久ww| 猛男gaygay欧美视频| 一区二区三区精品在线| 国产精品二区三区四区| 久久精品国产久精国产爱| 久久久久综合| 一区二区欧美激情| 性色av一区二区三区红粉影视| 永久555www成人免费| 一区二区三区国产在线| 在线视频欧美日韩精品| 国模私拍视频一区| 久久国产精品99国产精| 老司机久久99久久精品播放免费| 亚洲精品欧美精品| 亚洲免费视频网站| 亚洲国内自拍| 亚洲欧美国产精品桃花| 亚洲国产1区| 欧美一级电影久久| 久久九九99| 亚洲视频精选在线| 快she精品国产999| 欧美在线精品免播放器视频| 麻豆乱码国产一区二区三区| 亚洲一区二区三区在线| 欧美激情一区二区三级高清视频 | 在线看无码的免费网站| 香蕉久久夜色精品国产使用方法| 亚洲一区观看| 欧美亚洲专区| 亚洲欧美久久久| 国产精品va| 午夜视频在线观看一区| 亚洲国产成人精品久久| 欧美成人在线影院| 亚洲电影一级黄| 99国内精品久久| 老司机午夜精品视频在线观看| 免费成人黄色| 久久久www成人免费精品| 欧美另类视频| 欧美激情精品久久久六区热门 | 欧美一区二粉嫩精品国产一线天| 狼人天天伊人久久| 久久久久九九视频| 国产欧美日韩免费看aⅴ视频| 亚洲精品久久久久中文字幕欢迎你 | 久久女同互慰一区二区三区| 亚洲欧美www| 欧美日韩国产系列| 91久久黄色| 亚洲清纯自拍| 欧美电影免费| 欧美电影美腿模特1979在线看| 韩国成人精品a∨在线观看| 亚洲欧美视频在线| 久久成人免费| 国产一级久久| 欧美在线资源| 麻豆精品传媒视频| 伊人夜夜躁av伊人久久| 久久精品夜夜夜夜久久| 久久综合给合久久狠狠色| 国内精品久久久久伊人av| 亚洲欧美在线看| 久久精品视频在线| 国产一区高清视频| 久久久久久亚洲综合影院红桃 | 久久成人资源| 韩国三级在线一区| 久久久中精品2020中文| 欧美国产欧美亚洲国产日韩mv天天看完整 | 亚洲免费婷婷| 国产日韩精品一区二区| 久久爱www| 欧美成人一区二区三区在线观看| 亚洲二区在线视频| 欧美日韩国产小视频在线观看| 一片黄亚洲嫩模| 久久精品免费观看| 亚洲国内自拍| 欧美婷婷久久| 久久av资源网站| 最新国产乱人伦偷精品免费网站| 亚洲视频免费在线| 国产伊人精品| 欧美岛国在线观看| 国户精品久久久久久久久久久不卡 | 欧美亚洲一区| 1024欧美极品| 欧美日韩亚洲一区二区三区在线观看 | 欧美国产免费| 亚洲专区在线| 在线精品一区| 国产精品你懂的在线欣赏| 久久久一二三| 亚洲一区二区三区在线播放| 美日韩丰满少妇在线观看| 中文有码久久| 在线观看亚洲视频| 国产精品成人一区二区| 久久久噜噜噜久久人人看| 亚洲精品国产视频| 久久久免费观看视频| 99国产精品国产精品久久| 国产亚洲精品高潮| 欧美四级电影网站| 裸体丰满少妇做受久久99精品| 午夜精品久久久久久久99热浪潮| 欧美日本亚洲韩国国产| 亚洲欧美中文日韩在线| 亚洲国产精品成人综合色在线婷婷| 一区二区三区你懂的| 狠狠色丁香久久综合频道 | 狠狠入ady亚洲精品| 欧美午夜精品伦理| 女人色偷偷aa久久天堂| 91久久综合| 欧美不卡视频一区发布| 亚洲无限乱码一二三四麻| 亚洲午夜国产成人av电影男同| 国产精品久久久久999| 美玉足脚交一区二区三区图片| 欧美激情黄色片| 99日韩精品| 日韩亚洲精品视频| 性欧美8khd高清极品| 亚洲一区二区黄色| 国产精品入口麻豆原神| 亚洲欧美国产一区二区三区| 国产精品99久久久久久久久| 亚洲老板91色精品久久| 国产九色精品成人porny| 亚洲一级网站| 午夜久久久久久| 久久激情五月丁香伊人| 国内精品久久久久国产盗摄免费观看完整版 | 亚洲欧美三级伦理| 亚洲私人黄色宅男| 亚洲国产精品成人| 久久频这里精品99香蕉| 久久国产精品一区二区三区| 亚洲一区二区三区在线看| 欧美小视频在线| 欧美伦理一区二区| 欧美高清视频在线| 久久久久久一区二区| 欧美性开放视频| 欧美成人免费在线| 久久久91精品国产一区二区三区| 欧美一区二区国产| 亚洲欧美日韩中文视频| 国产最新精品精品你懂的| 国产情侣一区| 欧美一区综合| 久久久欧美一区二区|