WTL for MFC Programmers, Part I - ATL GUI Classes

WTL for MFC Programmers

本文章总结自 这篇文章

本章内容

  • ATL 背景知识
  • ATL 窗口类
  • ATL 窗口实现
  • ATL 对话框实现

ATL 背景知识

WTL 是构建于 ATL 之上的一系列附加类。要学习 WTL 首先得对 ATL 进行一些介绍。

ATL 和 WTL 的发展历史

Active Template Library(活动模板库), 是为了方便进行 COM 组件和 ActiveX 控件开发而诞生的。由于 ATL 是为了开发 COM 而存在的,所以只提供了非常简单的界面类。直接用 ATL 开发界面程序是比较繁琐的。所以才会在此之上封装 WTL 来方便开发界面程序。

ATL 风格的模版

class CMyWnd : public CWindowImpl<CMyWnd>
{
    // do something ...
};

上面的代码初看可能觉得很奇怪,为啥 CMyWnd 继承了 CWindowImpl, CWindowImpl 又拿 CMyWnd 当模版?这么做不会报错吗?这么做有什么作用?

首先,这样做不会报错,因为 C++ 的语法解释说即使 CMyWnd 类只是被部分定义,类名 CMyWnd 已经被列入递归继承列表,是可以使用的。

下面的例子解释了这种写法如何工作:

template <class T>
class B1
{
public:
    void SayHi()
    {
        T* pT = static_cast<T*>(this);
        pT->PrintClassName();
    }
    void PrintClassName() { printf("This is B1\n"); }
};

class D1 : public B1<D1>
{
    // 没有覆写任何函数
};

class D2 : public B1<D2>
{
public:
    void PrintClassName() { printf("This is D2\n"); }
};

int main()
{
    D1 d1;
    D2 d2;

    d1.SayHi();    // This is B1
    d2.SayHi();    // This is D2

    return 0;
}

上述代码实现了类似于“虚函数”的多态功能。

通过这种模版写法, D2 继承的 B1.SayHi 函数,实际上被解释成:

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

SayHi 调用的是 D2 的 PrintClassName 方法。

如果不使用这种模版写法,那么 B1 的 SayHi 函数在调用 PrintClassName 的时候,只能去调用 B1 自己的 PrintClassName 函数,无法做到调用 D2 覆写后的 PrintClassName 函数。

这样做的好处如下:

  1. 不需要使用指向对象的指针,可以直接使用对象来调用多态接口;
  2. 节省内存,因为不需要虚函数表;
  3. 因为没有虚函数表所以不会发生在运行时调用空指针指向的虚函数;
  4. 所有的函数在编译时确定(区别于 C++ 的虚函数机制,在运行时确定调用哪个函数)。有利于编译程序对代码的优化;

回到最初的代码:

class CMyWnd : public CWindowImpl<CMyWnd>
{
    // do something ...
};

这种写法的作用也就可以理解了。 CMyWnd 中覆写的函数,将能够以类似多态的方式被 CWindowImpl 正确调用。并且节省了虚函数表带来的内存开销。

ATL 窗口类

CWindow:

封装了所有对 HWND 的操作,几乎所有以 HWND 为第一个参数的窗口 API 都经过了 CWindow 的封装。 CWindow 类有一个公有成员 m_hWnd 使你可以直接对窗口进行操作。

CWindowImpl:

继承自 CWindow, 使用它可以对窗口消息进行处理,从而使窗口具有不同通过的功能和表现。另外它还封装了 窗口类的注册,窗口的子类化 等功能。

CAxWindow:

继承自 CWindow, 用于实现含有 ActiveX 控件的窗口;

CDialogImpl:

继承自 CWindow, 用于实现普通的对话框;

CAxDialogImpl:

继承自 CWindow, 用于实现含有 ActiveX 控件的对话框;

ATL 窗口实现:

要实现一个 ATL 窗口,要按照如下的步骤:

  1. 在 stdafx.h 中添加 ATL 相关的头文件:

    #include <atlbase.h>       // 基本 ATL 类
    extern CComModule _Module; // 全局 _Module
    #include <atlwin.h>        // 窗口 ATL 类
    
  2. 在 main.cpp 中定义 CComModule _Module 并初始化它:

    #include "stdafx.h"
    
    CComModule _Module;
    
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                         _In_opt_ HINSTANCE hPrevInstance,
                         _In_ LPWSTR    lpCmdLine,
                         _In_ int       nCmdShow)
    {
        _Module.Init(NULL, hInstance); // 初始化 _Module
    
        // 在这里进行 ATL 窗口的创建、消息泵的创建 ...
    
        _Module.Term();                // 结束 _Module
        return 0;
    }
    

    一个 ATL 程序包含一个 CComModule 类型的全局变量 _Module, 这和 MFC 程序都有一个 CWinApp 类型的全局变量 theApp 有点儿类似,唯一不同的是在 ATL 中这个变量必须被命名为 _Module.
    _Module 在 main.cpp 中定义并初始化,并通过 extern 关键字在 stdafx.h 文件中声明,其他 #include "stdafx.h" 的模块就可以使用 _Module 来进行一些操作。

  3. 在 MyWindow.h 中定义自己的窗口 CMyWindow:

    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;
        }
    };
    

    注意第一行代码 class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
    模板参数中的第一个,这样写的原因之前已经解释过,是为了实现类似“多态”的效果;
    模板参数中的第二个,目前不知道原因;
    模板参数中的第三个,用于指定窗口类型,如 WS_OVERLAPPEDWINDOW, WS_EX_APPWINDOW 等, CFrameWinTraits 是 ATL 预先定义的特殊类型,你也可以自己定义:

    typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,WS_EX_APPWINDOW> CMyWindowTraits;
    class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>
    
  4. 在 main.cpp 中使用 CMyWindow 类创建主窗口:

    #include "stdafx.h"
    #include "MyWindow.h"
    
    CComModule _Module;
    
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                         _In_opt_ HINSTANCE hPrevInstance,
                         _In_ LPWSTR    lpCmdLine,
                         _In_ int       nCmdShow)
    {
        _Module.Init(NULL, hInstance);
    
        // 声明 CMyWindow 对象
        CMyWindow wndMain;
    
        // 创建窗口
        if (NULL == wndMain.Create(NULL, CWindow::rcDefault, _T("My First ATL Window")))
        {
            return 1;
        }
        wndMain.ShowWindow(nCmdShow);
        wndMain.UpdateWindow();
    
        // 消息泵
        MSG msg;
        while (GetMessage(&msg, NULL, 0, 0) > 0)
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    
        _Module.Term();
        return msg.wParam;
    }
    

ATL 对话框实现:

要实现一个 ATL 对话框,和生成 ATL 窗口的方式差不多,只有两点不同:

  1. 窗口的基类是 CDialogImpl 而不是 CWindowImpl;

  2. 你需要在对话框类中定义名称为 IDD 的公有成员用来保存对话框资源的 ID;

    #include "resource.h"
    
    class CAboutDlg : public CDialogImpl<CAboutDlg>
    {
    public:
        enum { IDD = IDD_ABOUT };
    
        BEGIN_MSG_MAP(CAboutDlg)
            MESSAGE_HANDLER(WM_INITDIALOG, OnInitControl)
            MESSAGE_HANDLER(WM_CLOSE, OnClose)
            COMMAND_ID_HANDLER(IDOK, OnOkCancel)
            COMMAND_ID_HANDLER(IDCANCEL, OnOkCancel)
        END_MSG_MAP()
    
        LRESULT OnInitControl(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
        {
            CenterWindow();
            return TRUE;
        }
    
        LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
        {
            EndDialog(0);
            return 0;
        }
    
        LRESULT OnOkCancel(WORD wNotifyCode, WORD wID, HWND hWndCtrl, BOOL& bHandled)
        {
            EndDialog(wID);
            return 0;
        }
    };
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • Windows 95中文输入法编辑器(IME) 微软 翻译:TBsoft Software Studio ...
    returntrue阅读 2,486评论 0 3
  • 实验一 unresolved external symbol __endthreadex错误解决,是因为没有引用M...
    yueyue_projects阅读 2,627评论 0 5
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 对,我爱他,但是那晚我也拒绝了他。 这个故事我讲了三遍,内容不同,互相补充,唯一相同的是隐藏了我爱他的事实。 1....
    heikf阅读 378评论 0 0
  • 认识自己的能力,清楚自己的优缺点,并以此来选择专业,是一种办法。 结合自己的兴趣爱好和能力优势 序号 学科兴趣类型...
    燕妮老师说阅读 417评论 0 0