from:http://blog.titilima.com/atlgui-2.html

第二章 一个最简单窗口程序的转型

我知道,可能会有很多朋友对上一章的“Hello, World!”ATL版不以为然,因为它并不能算是什么ATL程序——毕竟它只不过是有了个CComModule而已。不过不管怎样我还是要说,它几乎仍然拥有了一个ATL GUI程序的所有组成部分:入口、初始化、程序体、卸载……

“等等!”也许你会突然打断我,“——还有注册窗口类、消息循环呢?”

当然,对于一个完整的GUI程序来讲,这也是必要的。

貌似废话

不清楚你是否已经为本章的内容做好了准备,因为下面我们就要动真格的了。不过考虑到本书的读者群中可能会存在着相当一部分了解MFC却对Win32 GUI的基本原理和流程不甚熟悉的朋友,所以李马特别为你们准备了这一节的内容。SDK的粉丝们可以跳过这一节,如果你们觉得李马讲的有些拖沓冗长的话。

那么,我还是先以一个标准的Win32 SDK程序开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//////////////////////////////////////////////////////////////////////////
// ATL的GUI程序设计配套源代码
// 第二章 一个最简单窗口程序的转型
// 工程名称:HelloSDK
// 作者:李马
// http://www.titilima.cn
//////////////////////////////////////////////////////////////////////////
 
#include <windows.h>
#include <tchar.h>
 
LRESULT CALLBACK HelloWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
switch ( uMsg )
{
case WM_DESTROY:
{
PostQuitMessage( 0 );
}
break;
case WM_PAINT:
{
HDC hdc;
PAINTSTRUCT ps;
 
hdc = BeginPaint( hWnd, &ps );
DrawText( hdc, _T("Hello, SDK!"), -1, &ps.rcPaint, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
EndPaint( hWnd, &ps );
}
break;
default:
return DefWindowProc( hWnd, uMsg, wParam, lParam );
}
return 0;
}
 
BOOL InitApplication( HINSTANCE hInstance )
{
WNDCLASS wc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
wc.hInstance = hInstance;
wc.lpfnWndProc = HelloWndProc;
wc.lpszClassName = _T("HelloSDK");
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;
 
return RegisterClass( &wc );
}
 
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd )
{
// 注册窗口类
InitApplication( hInstance );
 
// 创建窗口
HWND hWnd = CreateWindow( _T("HelloSDK"), _T("Hello SDK"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
ShowWindow( hWnd, nShowCmd );
UpdateWindow( hWnd );
 
// 消息循环
MSG msg;
while ( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
 
return msg.wParam;
}

不知道你是否会觉得这段代码有些冗长?事实上,这个程序已经体现了Win32 GUI程序运行的所有流程(请注意,我并不会对这些代码进行详细的解释,因为我已经假设你已经了解了这些代码具体行为的必要细节。如果不是这样的话,请参考相关的书籍或者MSDN):

  1. 注册窗口类的部分。在这个程序中,InitApplication函数完成了这一工作。窗口类的概念类似于OO(面向对象)中的类,所有你在Windows中能看到的窗口都是某个特定窗口类的一份实例。但是,窗口类并非任何一种OOP语言中的类——它所包括的并不是通称的属性和方法(在C++中称作成员变量和成员函数),而是属性和响应。这个区别可能会使你感到费解,我会在下一章中为你详细介绍——因为ATL中对窗口的封装类将这一点体现得十分淋漓尽致。
  2. 创建窗口的部分。在通常的SDK代码里,这些代码被封装在一个名为InitInstance的函数中。这段代码所做的工作一般是创建窗口并将其显示出来。
  3. 消息循环。Windows是一个基于消息机制的操作系统,各个窗口之间的通信也主要是靠Windows消息来完成的。而程序中的消息循环也就是将本程序UI线程中的消息队列中提取各种消息,进行处理(如果有必要的话)之后分发给各个消息的属主窗口(或者说是目标窗口)。

在这里需要指出的是,HelloWndProc是我们自己定义的一个函数,我们需要用它来控制我们对特定窗口消息的特定响应。我们只需要在注册窗口类之前,将这个函数的地址(也就是函数名)赋值给WNDCLASS::lpfnWndProc成员就可以了。这个函数我们自己不需要进行调用,它的调用是当我们的窗口收到窗口消息后,由Windows完成的。在这个回调函数中,我们的处理是这样的:

  • WM_DESTROY。在窗口被销毁的时候,窗口会收到此消息。在这里,我们会调用PostQuitMessage,用以向当前UI线程的消息队列之中发送一条WM_QUIT消息,GetMessage在收到这条消息后,会返回FALSE,也就结束了消息循环,WinMain也就结束了。
  • WM_PAINT。在窗口需要绘制的时候,窗口会收到此消息。在这里我们只是简单的在窗口的中间绘制了一行文字“Hello, SDK!”。
  • 其它消息。这些消息都是我们不关心的,所以我们将其交由系统默认的窗口过程DefWindowProc来处理。

这段代码貌似冗长,但实际上还是很有条理的,你可以根据它以及我以上的解说来对照这个程序的ATL版本。

ATL等同品

在写作这本书的时候,我总是希望我每次都能够能使用让你不太陌生的代码来循序渐进地引导你。考虑再三,对于“Hello, ATL!”的这个程序,我决定先把它的WinMain展现给你:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd )
{
_Module.Init( NULL, hInstance );
 
// 创建窗口
CHelloATLWnd wnd;
wnd.Create( NULL, CHelloATLWnd::rcDefault, _T("Hello ATL") );
wnd.ShowWindow( nShowCmd );
wnd.UpdateWindow();
 
// 消息循环
MSG msg;
while ( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
 
_Module.Term();
return msg.wParam;
}

OK,上一章介绍过的_Module又出现在你的眼前了——不过还是没有什么特别的变化,仍然是那熟悉的Init和Term。而且,正如“山哟还是那座山”一样,消息循环哟也仍然是那个消息循环。当然,你肯定也发现了那寥寥的变化:CHelloATLWnd是什么?在我将它的代码展现给你之前,你可能会做出这样的猜想:

  • 这是一个C++类,它对Win32窗口类进行了封装。
  • 这个类封装了大多数窗口操作的API函数,诸如CreateWindow、ShowWindow、UpdateWindow。
  • 窗口类的注册可能也是在这个C++类中完成的。

好,打住,这就够了。让我们来撩开CHelloATLWnd那貌似神秘的面纱吧,赶紧着。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class CHelloATLWnd : public CWindowImpl< CHelloATLWnd, CWindow, CWinTraits< WS_OVERLAPPEDWINDOW > >
{
public:
CHelloATLWnd()
{
CWndClassInfo& wci = GetWndClassInfo();
wci.m_bSystemCursor = TRUE;
wci.m_lpszCursorID = IDC_ARROW;
wci.m_wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
wci.m_wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
}
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;
}
};

猜想,还是猜想!

请允许我在本章中不为你解释这个类的任何具体细节,取而代之的是继续的猜想。因为,这个类中需要解释的东西太多了,以至于我必须为它单独开辟一章。

  • 窗口类的注册是由这个C++类的构造函数与DECLARE_WND_CLASS宏一起完成的。
  • 对于BEGIN_MSG_MAP与END_MSG_MAP这一部分,想必使用过MFC的朋友们应该更容易理解。是的,这一对宏可以算作ATL的消息映射,在其中由MESSAGE_HANDLER作为消息分流器,将各种窗口消息分配给各个处理函数。
  • 创建窗口时指定的样式貌似和模板参数CWinTraits有关。

当然,除了这些猜想之外,你可能还会同时存在以下疑问:

  • CWindowImpl、CWindow、CWinTraits究竟是什么?
  • 窗口类是在何时注册的?
  • 消息分流器是如何实现的?

也许你还会有更多的疑问,那么就让我一并将它们留到下一章再解决吧。如果你实在等不及的话,atlwin.h的代码也会告诉你一切的。

补叙CComModule

由于这本书主要针对的是ATL 3.0/Visual C++ 6.0,所以我疏忽了对CComModule的研究。在此感谢老李老刀兄提出的一点,就是CComModule在ATL 7.0中已经不建议使用了。于是我将MSDN中的相关章节摘抄下来,权作借花献佛之用。

不过,出于代码的兼容性以及WTL的内容考虑,本系列后续文章仍然将使用ATL 3.0中的CComModule。

CComModule 替换类

ATL 的早期版本使用 CComModule。在 ATL 7.0 中,CComModule 功能被若干个类所取代:

  • CAtlBaseModule 包含大多数使用 ATL 的应用程序所需的信息。包含模块和资源实例的 HINSTANCE。
  • CAtlComModule 包含 ATL 中的 COM 类所需的信息。
  • CAtlWinModule 包含 ATL 中的窗口化类所需的信息。
  • CAtlDebugInterfacesModule 包含接口调试支持。
  • CAtlModule 下列 CAtlModule 派生的类被自定义为包含特定应用程序类型中所需的信息。这些类中的大部分成员都可以被重写:

    CAtlDllModuleT 在 DLL 应用程序中使用。为标准导出提供代码。

    CAtlExeModuleT 在 EXE 应用程序中使用。提供 EXE 中所需的代码。

    CAtlServiceModuleT 为创建 Windows NT 和 Windows 2000 服务提供支持。

    CComModule 仍然可用以便向后兼容。

分布 CComModule 功能的原因

由于以下原因,CComModule 的功能分布到了几个新类中:

  • 使 CComModule 中的功能呈粒状分割。

    对 COM、窗口化、接口调试和应用程序特定的(DLL 或 EXE)功能的支持现在在不同的类中。
  • 自动为这些模块的每一个声明全局实例。

    所需模块类的全局实例链接到项目中。
  • 消除了调用 Init 和 Term 方法的必要性。

    Init 和 Term 方法已移动到模块类的构造函数和析构函数中;不再需要调用 Init 和 Term。

附件:atlgui02.zip

ATL的GUI程序设计(2)的更多相关文章

  1. ATL的GUI程序设计(4)

    第四章 对话框和控件 对于Win32 GUI的程序设计来说,其实大部分的情况下我们都不需要自己进行窗口类的设计,而是可以使用Win32中与用户交互的标准方式--对话框(Dialog Box).我们可以 ...

  2. ATL的GUI程序设计(3)

    第三章 ATL的窗口类 CWindowImpl.CWindow.CWinTraits,ATL窗口类的奥秘尽在此三者之中.在本章里,李马将为你详细解说它们的使用方法.另外,本章的内容也可以算是本书的核心 ...

  3. ATL的GUI程序设计(前言)

    前言 也许,你是一个顽固的SDK簇拥者: 也许,你对MFC抱着无比排斥的态度,甚至像我一样对它几乎一无所知: 也许,你符合上面两条,而且正在寻求着一种出路: 也许,你找到了一条出路--WTL,但是仍然 ...

  4. ATL的GUI程序设计(1)

    from:http://blog.titilima.com/atlgui-1.html 第一章 不能免俗的"Hello, World!" 在这一章里,就像所有的入门级教程一样,我也 ...

  5. Java GUI程序设计

    在实际应用中,我们见到的许多应用界面都属于GUI图形型用户界面.如:我们点击QQ图标,就会弹出一个QQ登陆界面的对话框.这个QQ图标就可以被称作图形化的用户界面. 其实,用户界面的类型分为两类:Com ...

  6. GUI程序设计2

    8. 按钮(JButton)使用示例 例14. 按钮使用示例. package GUI; import java.awt.BorderLayout; import java.awt.Container ...

  7. GUI程序设计

    1. 对话框(JDialog)使用示例 例1. JDialog简单使用示例. import javax.swing.JLabel; public class demoJDialog { JFrame ...

  8. Matlab GUI程序设计入门——信号发生器+时域分析

    背景:学习matlab gui编程入门,完成一个基于GUIDE的图形化界面程序,结合信号生成及分析等. 操作步骤: 1.新建程序 新建一个GUIDE程序 这里选择第一个选项,即创建一个空白的GUIDE ...

  9. MATLAB GUI程序设计中ListBox控件在运行期间消失的原因及解决方法

    在运行期间,ListBox控件突然消失,同时给出如下错误提示: Warning: single-selection listbox control requires that Value be an ...

随机推荐

  1. $CF24D\ Broken Robot\ DP+$高斯消元

    Luogu Description 你收到的礼物是一个非常聪明的机器人,行走在一块长方形的木板上.不幸的是,你知道它是坏的,表现得相当奇怪(随机).该板由n行和m列的单元格组成.机器人最初是在i行和j ...

  2. MySQL 核心三剑客 —— 索引、锁、事务

    一.常见存储引擎 1.1 InnoDB InnoDB 是 MySQL 5.5 之后默认的存储引擎,它具有高可靠.高性能的特点,主要具备以下优势: DML 操作完全遵循 ACID 模型,支持事务,支持崩 ...

  3. SpringBoot-2.1.1系列二:使用websocket

    1.什么是websocket? WebSocket协议是基于TCP的一种新的网络协议.它实现了浏览器与服务器全双工(full-duplex)通信--允许服务器主动发送信息给客户端. 2.为什么需要使用 ...

  4. Ant Design框架中不同的组件访问不同的models中的数据

    Ant Design框架中不同的组件访问不同的models中的数据 本文记录了我在使用该框架的时候踩过的坑,方便以后查阅. 一.models绑定 在某个组件(控件或是页面),要想从某个models中获 ...

  5. Redis 持久化的两种方案

    reids是一个key-value存储系统,为了保证效率,缓存在内存中,但是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,以保证数据的持久化. 所以:redis是一个支持持 ...

  6. Intellij Idea插件使用记录之Alibaba Java Coding Guidelines

    目录 Intellij Idea插件Alibaba Java Coding Guidelines 前言 使用 感谢 Intellij Idea插件Alibaba Java Coding Guideli ...

  7. makefile个人理解

    makefile makefile抽象层面的理解 学习某一样东西之前一定要明确学习的目的,即学习了这项工具能解决一些什么问题,其优势是什么? makefile的优势就是能够动态根据文件的新旧来决定是否 ...

  8. 2019牛客暑期多校第五场题解ABGH

    A.digits 2 传送门 题意:给你一个n,要求输出一个每一位数字之和能整除n且其本身也能整除n的数.n不超过100,要求的数不超过10000位数. 题解:直接将n输出n次. 代码: #inclu ...

  9. leetcode腾讯精选练习之两数相加

    两数相加 题目: 给出两个非空的链表用来表示两个非负的整数.其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储一位数字.如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们 ...

  10. ArcGIS Desktop直连PostgreSQL安装及配置图解(windows)

    目录 1 PostgreSQL 11.0安装及配置 2 psqlODBC安装及配置 3 PostGIS安装及配置 4 pgAdmin4使用入门 5 空间数据导入 5.1 将PostgreSQL的bin ...