程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> MFC程序員WTL指南(2)ATL界面類

MFC程序員WTL指南(2)ATL界面類

編輯:關於VC++

在你開始使用WTL或著在本文章的討論區張貼消息之前,我想請你先閱讀下面的材料。

你需要開發平台SDK(Platform SDK)。你要使用WTL不能沒有它,你可以使用在線升級安裝開發平台SDK,也可以下載全部文件後在本地安裝。在使用之前要將SDK的包含文件(.h頭文件)和庫文件(.Lib文件)路徑添加到VC的搜索目錄,SDK有現成的工具完成這個工作,這個工具位於開發平台SDK程序組的“Visual Studio Registration”文件夾裡。

你需要安裝 WTL。你可以從微軟的網站上下載WTL的7.0版,(*編輯注:WTL也可以到VC知識庫http://www.vckbase.com/tools下載) 在安裝之前可以先查看“Introduction to WTL - Part 1”和“Easy installation of WTL”這兩篇文章,了解一下所要安裝的文件的信息,雖然現在這些文章有些過時,但還是可以提供很多有用的信息。有一件我認為不該在本篇文章中提到的事是告訴VC如何搜索WTL的包含文件路徑,如果你用的VC6,用鼠標點擊 Tools\Options,轉到Directories標簽頁,在顯示路徑的列表框中選擇Include Files,然後將WTL的包含文件的存放路徑添加到包含文件搜索路徑列表中。

你需要了解MFC。很好地了解MFC將有助於你理解後面提到的有關消息映射的宏並能夠編輯那些標有“不要編輯(DO NOT EDIT)”的代碼而不會出現問題。

你需要清楚地知道如何使用Win32 API編程。如果你是直接從MFC開始學習Windows編程,沒有學過API級別的消息處理方式,那很不幸你會在使用WTL時遇到麻煩。如果不了解Windows消息中WPARAM參數和LPARAM參數的意義,應該明白需要讀一些這方面的文章(在CodeProject有大量的此類文章)。

你需要知道 C++ 模板的語法,你可以到VC Forum FAQ 相關的連接尋求答案。

我只是討論了一些涵蓋VC 6的特點,不過據我了解所有的程序都可以在VC 7上使用。由於我不使用VC 7,我無法對那些在VC 7中出現的問題提供幫助,不過你還是可以放心的在此張貼你的問題,因為其他的人可能會幫助你。

對本系列文章的總體介紹

WTL 具有兩面性,確實是這樣的。它沒有MFC的界面(GUI)類庫那樣功能強大,但是能夠生成很小的可執行文件。如果你象我一樣使用MFC進行界面編程,你會覺得MFC提供的界面控件封裝使用起來非常舒服,更不用說MFC內置的消息處理機制。當然,如果你也象我一樣不希望自己的程序僅僅因為使用了MFC的框架就增加幾百K的大小的話,WTL就是你的選擇。當然,我們還要克服一些障礙:

  • ATL樣式的模板類初看起來有點怪異
  • 沒有類向導的支持,所以要手工處理所有的消息映射。
  • MSDN沒有正式的文檔支持,你需要到處去收集有關的文檔,甚至是查看WTL的源代碼。
  • 買不到參考書籍
  • 沒有微軟的官方支持
  • ATL/WTL的窗口與MFC的窗口有很大的不同,你所了解的有關MFC的知識並不全部適用與WTL。

從另一方面講,WTL也有它自身的優勢:

  • 不需要學習或掌握復雜的文檔/視圖框架。
  • 具有MFC的基本的界面特色,比如DDX/DDV和命令狀態的自動更新功能(譯者加:比如菜單的Check標記和Enable標記)。
  • 增強了一些MFC的特性(比如更加易用的分隔窗口)。
  • 可生成比靜態鏈接的MFC程序更小的可執行文件(譯者加:WTL的所有源代碼都是靜態鏈接到你的程序中的)。
  • 你可以修正自己使用的WTL中的錯誤(BUG)而不會影響其他的應用程序(相比之下,如果你修正了有BUG的MFC/CRT動態庫就可能會引起其它應用程序的崩潰。

  • 如果你仍然需要使用MFC,MFC的窗口和ATL/WTL的窗口可以“和平共處”。(例如我工作中的一個原型就使用了了MFC的CFrameWnd,並在其內包含了WTL的CSplitterWindow,在CSplitterWindow中又使用了MFC的CDialogs -- 我並不是為了炫耀什麼,只是修改了MFC的代碼使之能夠使用WTL的分割窗口,它比MFC的分割窗口好的多)。

    在這一系列文章中,我將首先介紹ATL的窗口類,畢竟WTL是構建與ATL之上的一系列附加類,所以需要很好的了解ATL的窗口類。介紹完ATL之後我將介紹WTL的特性以並展示它是如何使界面編程變得輕而易舉。

    對第一章的簡單介紹

    WTL是個很酷的工具,在理解這一點之前需要首先介紹ATL。WTL是構建與ATL之上的一系列附加類,如果你是個嚴格使用MFC的程序員那麼你可能沒有機會接觸到ATL的界面類,所以請容忍我在開始WTL之前先羅索一些別的東西,繞道來介紹一下ATL是很有必要地。

    在本文的第一部分,我將給出一點ATL的背景知識,包括一些編寫ATL代碼必須知道的基本知識,快速的解釋一些令人不知所措的ATL模板類和基本的ATL窗口類。

    ATL 背景知識 ATL 和 WTL 的發展歷史

    “活動模板庫”(Active Template Library)是一個很古怪的名字,不是嗎?那些年紀大的人可能還記得它最初被稱為“網絡組件模板庫”,這可能是它更准確的稱呼,因為ATL的目的就是使編寫組件對象和ActiveX控件更容易一些(ATL是在微軟開發新產品ActiveX-某某的過程中開發的,那些ActiveX-某某現在被稱為某某.NET)。由於ATL是為了便於編寫組件對象而存在的,所以只提供了簡單的界面類,相當於MFC的窗口類(CWnd)和對話框類(CDialog)。幸運的是這些類非常的靈活,能夠在其基礎上構建象WTL這樣的附加類。

    WTL現在已經是第二次修正了,最初的版本是3.1,現在的版本是7(WTL的版本號之所以這樣選擇是為了與ATL的版本匹配,所以不存在1和2這樣的版本號)。WTL 3.1可以與VC 6和VC 7一起使用,但是在VC 7下需要定義幾個預處理標號。WTL 7向下兼容WTL 3.1,並且不作任何修改就可以與VC 7一起使用,現在看來沒有任何理由還使用3.1來進行新的開發工作。

    ATL-style 模板

    即使你能夠毫不費力地閱讀C++的模板類代碼,仍然有兩件事可能會使你有些頭暈,以下面這個類的定義為例:

    class CMyWnd : public CWindowImpl<CMyWnd>
    {
       ...
    }; 

    這樣作是合法的,因為C++的語法解釋說即使CMyWnd類只是被部分定義,類名CMyWnd已經被列入遞歸繼承列表,是可以使用的。將類名作為模板類的參數是因為ATL要做另一件詭秘的事情,那就是編譯期間的虛函數調用機制。

    如果你想要了解它是如何工作地,請看下面的例子:

    template <class T>
    class B1
    {
    public:
       void SayHi()
       {
         T* pT = static_cast<T*>(this);  // HUH?? 我將在下面解釋
         pT->PrintClassName();
       }
    protected:
       void PrintClassName() { cout << "This is B1"; }
    };
    class D1 : public B1<D1>
    {
       // No overridden functions at all
    };
    class D2 : public B1<D2>
    {
    protected:
       void PrintClassName() { cout << "This is D2"; }
    };
    main()
    {
       D1 d1;
       D2 d2;
       d1.SayHi();  // prints "This is B1"
       d2.SayHi();  // prints "This is D2"
    }

    這句代碼static_cast<T*>(this) 就是竅門所在。它根據函數調用時的特殊處理將指向B1類型的指針this指派為D1或D2類型的指針,因為模板代碼是在編譯其間生成的,所以只要編譯器生成正確的繼承列表,這樣指派就是安全的。(如果你寫成:

    class D3 : public B1<D2>

    就會有麻煩) 之所以安全是因為this對象只可能是指向D1或D2(在某些情況下)類型的對象,不會是其他的東西。注意這很像C++的多態性(polymorphism),只是SayHi()方法不是虛函數。

    要解釋這是如何工作的,首先看對每個SayHi()函數的調用,在第一個函數調用,對象B1被指派為D1,所以代碼被解釋成:

    void B1<D1>::SayHi()
    {
       D1* pT = static_cast<D1*>(this);
       pT->PrintClassName();
    }

    由於D1沒有重載PrintClassName(),所以查看基類B1,B1有PrintClassName(),所以B1的PrintClassName()被調用。

    現在看第二個函數調用SayHi(),這一次對象被指派為D2類型,SayHi()被解釋成:

    void B1<D2>::SayHi()
    {
       D2* pT = static_cast<D2*>(this);
       pT->PrintClassName();
    }

    這一次,D2含有PrintClassName()方法,所以D2的PrintClassName()方法被調用。

    這種技術的有利之處在於:

    • 不需要使用指向對象的指針。
    • 節省內存,因為不需要虛函數表。
    • 因為沒有虛函數表所以不會發生在運行時調用空指針指向的虛函數。
    • 所有的函數調用在編譯時確定(譯者加:區別於C++的虛函數機制使用的動態編連),有利於編譯程序對代碼的優化。

    節省虛函數表在這個例子中看起來無足輕重(每個虛函數只有4個字節),但是設想一下如果有15個基類,每個類含有20個方法,加起來就相當可觀了。

    ATL 窗口類

    好了,關於ATL的背景知識已經講的構多了,到了該正式講ATL的時候了。ATL在設計時接口定義和實現是嚴格區分開的,這在窗口類的設計中是最明顯的,這一點類似於COM,COM的接口定義和實現是完全分開的(或者可能有多個實現)。

    ATL有一個專門為窗口設計的接口,可以做全部的窗口操作,這就是CWindow。它實際上就是對HWND操作的包裝類,對幾乎所有以HWND句柄為第一個參數的窗口API的進行了封裝,例如:SetWindowText() 和 DestroyWindow()。CWindow類有一個公有成員m_hWnd,使你可以直接對窗口的句柄操作,CWindow還有一個操作符HWND,你可以講CWindow對象傳遞給以HWND為參數的函數,但這與CWnd::GetSafeHwnd()(譯者加:MFC的方法)沒有任何等同之處。

    CWindow 與 MFC 的CWnd類有很大的不同,創建一個CWindow對象占用很少的資源,因為只有一個數據成員,沒有MFC窗口中的對象鏈,MFC內部維持這一個對象鏈,此對象鏈將HWND映射到CWnd對象。還有一點與MFC的CWnd類不同的是當一個CWindow對象超出了作用域,它關聯的窗口並不被銷毀掉,這意味著你並不需要隨時記得分離你所創建的臨時CWindow對象。

    在ATL類中對窗口過程的實現是CWindowImpl。CWindowImpl 含有所有窗口實現代碼,例如:窗口類的注冊,窗口的子類化,消息映射以及基本的WindowProc()函數,可以看出這與MFC的設計有很大的不同,MFC將所有的代碼都放在一個CWnd類中。

    還有兩個獨立的類包含對話框的實現,它們分別是CDialogImpl 和 CAxDialogImpl,CDialogImpl 用於實現普通的對話框而CAxDialogImpl實現含有ActiveX控件的對話框。

    定義一個窗口的實現

    任何非對話框窗口都是從CWindowImpl 派生的,你的新類需要包含三件事情:

    1. 一個窗口類的定義
    2. 一個消息映射鏈
    3. 窗口使用的默認窗口類型,稱為window traits

    窗口類的定義通過DECLARE_WND_CLASS宏或DECLARE_WND_CLASS_EX宏來實現。這輛個宏定義了一個CWndClassInfo結構,這個結構封裝了WNDCLASSEX結構。DECLARE_WND_CLASS宏讓你指定窗口類的類名,其他參數使用默認設置,而DECLARE_WND_CLASS_EX宏還允許你指定窗口類的類型和窗口的背景顏色,你也可以用NULL作為類名,ATL會自動為你生成一個類名。

    讓我們開始定義一個新類,在後面的章節我會逐步的完成這個類的定義。

    class CMyWindow : public CWindowImpl<CMyWindow>
    {
    public:
       DECLARE_WND_CLASS(_T("My Window Class"))
    };

    接下來是消息映射鏈,ATL的消息映射鏈比MFC的簡單的多,ATL的消息映射鏈被展開為switch語句,switch語句正確的消息處理者並調用相應的函數。使用消息映射鏈的宏是BEGIN_MSG_MAP 和 END_MSG_MAP,讓我們為我們的窗口添加一個空的消息映射鏈。

    class CMyWindow : public CWindowImpl<CMyWindow>
    {
    public:
       DECLARE_WND_CLASS(_T("My Window Class"))
       BEGIN_MSG_MAP(CMyWindow)
       END_MSG_MAP()
    };

    我將在下一節展開講如何如何添加消息處理到消息映射鏈。最後,我們需要為我們的窗口類定義窗口的特征,窗口的特征就是窗口類型和擴展窗口類型的聯合體,用於創建窗口時指定窗口的類型。窗口類型被指定為參數模板,所以窗口的調用者不需要為指定窗口的正確類型而煩心,下面是是同ATL類CWinTraits定義窗口類型的例子:

    typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,WS_EX_APPWINDOW> CMyWindowTraits;
    class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>
    {
    public:
       DECLARE_WND_CLASS(_T("My Window Class"))
       BEGIN_MSG_MAP(CMyWindow)
       END_MSG_MAP()
    };

    調用者可以重載CMyWindowTraits的類型定義,但是一般情況下這是沒有必要的,ATL提供了幾個預先定義的特殊的類型,其中之一就是CFrameWinTraits,一個非常棒的框架窗口:

    typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                         WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> CFrameWinTraits;
    填寫消息映射鏈

    ATL的消息映射鏈是對開發者不太友好的部分,也是WTL對其改進最大的部分。類向導至少可以讓你添加消息響應,然而ATL沒有消息相關的宏和象MFC那樣的參數自動展開功能,在ATL中只有三種類型的消息處理,一個是WM_NOTIFY,一個是WM_COMMAND,第三類是其他窗口消息,讓我們開始為我們的窗口添加WM_CLOSE 和 WM_DESTROY的消息相應函數。

    class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
    {
    public:
       DECLARE_WND_CLASS(_T("My Window Class"))
       BEGIN_MSG_MAP(CMyWindow)
         MESSAGE_HANDLER(WM_CLOSE, OnClose)
         MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
       END_MSG_MAP()
       LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
       {
         DestroyWindow();
         return 0;
       }
       LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
       {
         PostQuitMessage(0);
         return 0;
       }
    };

    你可能注意到消息響應函數的到的是原始的WPARAM 和 LPARAM值,你需要自己將其展開為相應的消息所需要的參數。還有第四個參數bHandled,這個參數在消息相應函數調用被ATL設置為TRUE,如果在你的消息響應處理完之後需要ATL調用默認的WindowProc()處理該消息,你可以講bHandled設置為FALSE。這與MFC不同,MFC是顯示的調用基類的響應函數來實現的默認的消息處理的。

    讓我們也添加一個對WM_COMMAND消息的處理,假設我們的窗口有一個ID為IDC_ABOUT的About菜單:

    class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
    {
    public:
       DECLARE_WND_CLASS(_T("My Window Class"))
       BEGIN_MSG_MAP(CMyWindow)
         MESSAGE_HANDLER(WM_CLOSE, OnClose)
         MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
         COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)
       END_MSG_MAP()
       LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
       {
         DestroyWindow();
         return 0;
       }
       LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
       {
         PostQuitMessage(0);
         return 0;
       }
       LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
       {
         MessageBox ( _T("Sample ATL window"), _T("About MyWindow") );
         return 0;
       }
    };

    需要注意得是COMMAND_HANDLER宏已經將消息的參數展開了,同樣,NOTIFY_HANDLER宏也將WM_NOTIFY消息的參數展開了。

    高級消息映射鏈和嵌入類

    ATL的另一個顯著不同之處就是任何一個C++類都可以響應消息,而MFC只是將消息響應任務分給了CWnd類和CCmdTarget類,外加幾個有PreTranslateMessage()方法的類。ATL的這種特性允許我們編寫所謂的“嵌入類”,為我們的窗口添加特性只需將該類添加到繼承列表中就行了,就這麼簡單!

    一個基本的帶有消息映射鏈的類通常是模板類,將派生類的類名作為模板的參數,這樣它就可以訪問派生類中的成員,比如m_hWnd(CWindow類中的HWND成員)。讓我們來看一個嵌入類的例子,這個嵌入類通過響應WM_ERASEBKGND消息來畫窗口的背景。

    template <class T, COLORREF t_crBrushColor>
    class CPaintBkgnd : public CMessageMap
    {
    public:
       CPaintBkgnd() { m_hbrBkgnd = CreateSolidBrush(t_crBrushColor); }
       ~CPaintBkgnd() { DeleteObject ( m_hbrBkgnd ); }
       BEGIN_MSG_MAP(CPaintBkgnd)
         MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
       END_MSG_MAP()
       LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
       {
         T*  pT = static_cast<T*>(this);
         HDC dc = (HDC) wParam;
         RECT rcClient;
         pT->GetClientRect ( &rcClient );
         FillRect ( dc, &rcClient, m_hbrBkgnd );
         return 1;  // we painted the background
       }
    protected:
       HBRUSH m_hbrBkgnd;
    };

    讓我們來研究一下這個新類。首先,CPaintBkgnd有兩個模板參數:使用CPaintBkgnd的派生類的名字和用來畫窗口背景的顏色。(t_ 前綴通常用來作為模板類的模板參數的前綴)CPaintBkgnd也是從CMessageMap派生的,這並不是必須的,因為所有需要響應消息的類只需使用BEGIN_MSG_MAP宏就足夠了,所以你可能看到其他的一些嵌入類的例子代碼,它們並不是從該基類派生的。

    構造函數和析構函數都相當簡單,只是創建和銷毀Windows畫刷,這個畫刷由參數t_crBrushColor決定顏色。接著是消息映射鏈,它響應WM_ERASEBKGND消息,最後由響應函數OnEraseBkgnd()用構造函數創建的畫刷填充窗口的背景。在OnEraseBkgnd()中有兩件事需要注意,一個是它使用了一個派生的窗口類的方法(即GetClientRect()),我們如何知道派生類中有GetClientRect()方法呢?如果派生類中沒有這個方法我們的代碼也不會有任何抱怨,由編譯器確認派生類T是從CWindow派生的。另一個是OnEraseBkgnd()沒有將消息參數wParam展開為設備上下文(DC)。(WTL最終會解決這個問題,我們很快就可以看到,我保證)

    要在我們的窗口中使用這個嵌入類需要做兩件事:首先,將它加入到繼承列表:

    class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,
              public CPaintBkgnd<CMyWindow, RGB(0,0,255)>

    其次,需要CMyWindow將消息傳遞給CPaintBkgnd,就是將其鏈入到消息映射鏈,在CMyWindow的消息映射鏈中加入CHAIN_MSG_MAP宏:

    class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,
              public CPaintBkgnd<CMyWindow, RGB(0,0,255)>
    {
    ...
    typedef CPaintBkgnd<CMyWindow, RGB(0,0,255)> CPaintBkgndBase;
       BEGIN_MSG_MAP(CMyWindow)
         MESSAGE_HANDLER(WM_CLOSE, OnClose)
         MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
         COMMAND_HANDLER(IDC_ABOUT, OnAbout)
         CHAIN_MSG_MAP(CPaintBkgndBase)
       END_MSG_MAP()
    ...
    };

    任何CMyWindow沒有處理的消息都被傳遞給CPaintBkgnd。應該注意的是WM_CLOSE,WM_DESTROY和IDC_ABOUT消息將不會傳遞,因為這些消息一旦被處理消息映射鏈的查找就會中止。使用typedef是必要地,因為宏是預處理宏,只能有一個參數,如果我們將CPaintBkgnd<CMyWindow, RGB(0,0,255)>作為參數傳遞,那個“,”會使預處理器認為我們使用了多個參數。

    你可以在繼承列表中使用多個嵌入類,每一個嵌入類使用一個CHAIN_MSG_MAP宏,這樣消息映射鏈就會將消息傳遞給它。這與MFC不同,MFC地CWnd派生類只能有一個基類,MFC自動將消息傳遞給基類。

    ATL程序的結構

    到目前為止我們已經有了一個完整地主窗口類(即使不完全有用),讓我們看看如何在程序中使用它。一個ATL程序包含一個CComModule類型的全局變量_Module,這和MFC的程序都有一個CWinApp類型的全局變量theApp有些類似,唯一不同的是在ATL中這個變量必須命名為_Module。

    下面是stdafx.h文件的開始部分:

    // stdafx.h:
    #define STRICT
    #define VC_EXTRALEAN
    #include <atlbase.h>    // 基本的ATL類
    extern CComModule _Module; // 全局_Module
    #include <atlwin.h>     // ATL窗口類

    atlbase.h已經包含最基本的Window編程的頭文件,所以我們不需要在包含windows.h,tchar.h之類的頭文件。在CPP文件中聲明了_Module變量:

    // main.cpp:
    CComModule _Module;

    CComModule含有程序的初始化和關閉函數,需要在WinMain()中顯示的調用,讓我們從這裡開始:

    // main.cpp:
    CComModule _Module;
    int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,
              LPSTR szCmdLine, int nCmdShow)
    {
       _Module.Init(NULL, hInst);
       _Module.Term();
    }

    Init()的第一個參數只有COM的服務程序才有用,由於我們的EXE不含有COM對象,我們只需將NULL傳遞給Init()就行了。ATL不提供自己的WinMain()和類似MFC的消息泵,所以我們需要創建CMyWindow對象並添加消息泵才能使我們的程序運行。

    // main.cpp:
    #include "MyWindow.h"
    CComModule _Module;
    int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,
              LPSTR szCmdLine, int nCmdShow)
    {
       _Module.Init(NULL, hInst);
       CMyWindow wndMain;
       MSG msg;
       // Create & show our main window
       if ( NULL == wndMain.Create ( NULL, CWindow::rcDefault,
                     _T("My First ATL Window") ))
       {
         // Bad news, window creation failed
         return 1;
       }
       wndMain.ShowWindow(nCmdShow);
       wndMain.UpdateWindow();
       // Run the message loop
       while ( GetMessage(&msg, NULL, 0, 0) > 0 )
       {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
       }
       _Module.Term();
       return msg.wParam;
    }

    上面的代碼唯一需要說明的是CWindow::rcDefault,這是CWindow中的成員(靜態數據成員),數據類型是RECT。和調用CreateWindow() API時使用CW_USEDEFAULT指定窗口的寬度和高度一樣,ATL使用rcDefault作為窗口的最初大小。

    在ATL代碼內部,ATL使用了一些類似匯編語言的魔法將主窗口的句柄與相應的CMyWindow對象聯系起來,在外部看來就是可以毫無問題的在線程之間傳遞CWindow對象,而MFC的CWnd卻不能這樣作。

    這就是我們的窗口:

    我得承認這確實沒有什麼激動人心的地方。我們將添加一個About菜單並顯示一個對話框,主要是為它增加一些情趣。

    ATL中的對話框

    我們前面提到過,ATL有兩個對話框類,我們的About對話框使用CDialogImpl。生成一個新對話框和生成一個主窗口幾乎一樣,只有兩點不同:

    1. 窗口的基類是CDialogImpl而不是CWindowImpl。
    2. 你需要定義名稱為IDD的公有成員用來保存對話框資源的ID。

    現在開始為About對話框定義一個新類:

    class CAboutDlg : public CDialogImpl<CAboutDlg>
    {
    public:
       enum { IDD = IDD_ABOUT };
       BEGIN_MSG_MAP(CAboutDlg)
       END_MSG_MAP()
    };

    ATL沒有在內部實現對“OK”和“Cancel”兩個按鈕的響應處理,所以我們需要自己添加這些代碼,如果用戶用鼠標點擊標題欄的關閉按鈕,WM_CLOSE的響應函數就會被調用。我們還需要處理WM_INITDIALOG消息,這樣我們就能夠在對話框出現時正確的設置鍵盤焦點,下面是完整的類定義和消息響應函數。

    class CAboutDlg : public CDialogImpl<CAboutDlg>
    {
    public:
       enum { IDD = IDD_ABOUT };
       BEGIN_MSG_MAP(CAboutDlg)
         MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
         MESSAGE_HANDLER(WM_CLOSE, OnClose)
         COMMAND_ID_HANDLER(IDOK, OnOKCancel)
         COMMAND_ID_HANDLER(IDCANCEL, OnOKCancel)
       END_MSG_MAP()
       LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
       {
         CenterWindow();
         return TRUE;  // let the system set the focus
       }
       LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
       {
         EndDialog(IDCANCEL);
         return 0;
       }
       LRESULT OnOKCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
       {
         EndDialog(wID);
         return 0;
       }
    };

    我使用一個消息響應函數同時處理“OK”和“Cancel”兩個按鈕的WM_COMMAND消息,因為命令響應函數的wID參數就已經指明了消息是來自“OK”按鈕還是來自“Cancel”按鈕。

    顯示對話框的方法與MFC相似,創建一個新對話框類的實例,然後調用DoModal()方法。現在我們返回主窗口,添加一個帶有About菜單項的菜單用來顯示我們的對話框,這需要再添加兩個消息響應函數,一個是響應WM_CREATE,另一個是響應菜單的IDC_ABOUT命令。

    class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,
              public CPaintBkgnd<CMyWindow,RGB(0,0,255)>
    {
    public:
       BEGIN_MSG_MAP(CMyWindow)
         MESSAGE_HANDLER(WM_CREATE, OnCreate)
         COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)
         // ...
         CHAIN_MSG_MAP(CPaintBkgndBase)
       END_MSG_MAP()
       LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
       {
       HMENU hmenu = LoadMenu ( _Module.GetResourceInstance(),
                   MAKEINTRESOURCE(IDR_MENU1) );
         SetMenu ( hmenu );
         return 0;
       }
       LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
       {
       CAboutDlg dlg;
         dlg.DoModal();
         return 0;
       }
       // ...
    };

    在指定對話框的父窗口的方式上有些不同,MFC是通過構造函數將父窗口的指針傳遞給對話框而在ATL中是將父窗口的指針作為DoModal()方法的第一個參數傳遞給對話框的,如果象上面的代碼一樣沒有指定父窗口,ATL會使用GetActiveWindow()得到的窗口(也就是我們的主框架窗口)作為對話框的父窗口。

    對LoadMenu()方法的調用展示了CComModule的另一個方法-GetResourceInstance(),它返回你的EXE的HINSTANCE實例,和MFC的AfxGetResourceHandle()方法相似。(當然還有CComModule::GetModuleInstance(),它相當於MFC的AfxGetInstanceHandle()。)

    這就是主窗口和對話框的顯示效果:

  • 我會繼續講WTL,我保證!

    我會繼續講WTL的,只是會在第二部分。我覺得既然這些文章是寫給使用MFC的開發者的,所以有必要在投入WTL之前先介紹一些ATL。如果你是第一次接觸到ATL,那現在你就可以嘗試寫一些小程序,處理消息和使用嵌入類,你也可以嘗試用類向導支持消息映射鏈,使它能夠自動添加消息響應。現在就開始,右鍵單擊CMyWindow項,在彈出的上下文菜單中單擊“Add Windows Message Handler”菜單項。

    在第二部分,我將全面介紹基本的WTL窗口類和一個更好的消息映射宏。

    修改記錄

    2003年3月22日,本文第一次發表。

    1. 上一頁:
    2. 下一頁:
    Copyright © 程式師世界 All Rights Reserved