[翻译]-WinCE 程序设计 (3rd 版)--1.6 Hello3

Hello3
回顾的够多了,是时候做一个完整的Windows 应用程序--Hello3了。虽然Hello3的整个程序文件以及书中全部例子都可以在附书光盘里找到,但我还是建议,对于初期的例子,您应当避免简单的从CD上装载工程文件,而是应该手工输入整个例子。通过这种略微有些枯燥的工作,你会体会到标准Win32程序与Windows CE程序之间在开发过程的不同以及在程序上的细微差别。清单1-3给出了Hello3的全部源代码。

清单1-3:程序Hello3
Hello3.cpp
//======================================================================
// Hello3 - A simple application for Windows CE
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h>                 // For all that Windows stuff
  
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
  
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc;
    HWND hWnd;
    MSG msg;
  
    // Register application main window class.
    wc.style = 0;                             // Window style
    wc.lpfnWndProc = MainWndProc;             // Callback function
    wc.cbClsExtra = 0;                        // Extra class data
    wc.cbWndExtra = 0;                        // Extra window data
    wc.hInstance = hInstance;                 // Owner handle
    wc.hIcon = NULL,                          // Application icon
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);// Default cursor
    wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
    wc.lpszMenuName =  NULL;                  // Menu name
    wc.lpszClassName = TEXT("MyClass");       // Window class name
  
    if (RegisterClass (&wc) == 0) return -1;
  
    // Create main window.
    hWnd = CreateWindowEx(WS_EX_NODRAG,       // Ex style flags
                          TEXT("MyClass"),    // Window class
                          TEXT("Hello"),      // Window title
                          // Style flags
                          WS_VISIBLE | WS_CAPTION | WS_SYSMENU,
                          CW_USEDEFAULT,      // x position
                          CW_USEDEFAULT,      // y position
                          CW_USEDEFAULT,      // Initial width
                          CW_USEDEFAULT,      // Initial height
                          NULL,               // Parent
                          NULL,               // Menu, must be null
                          hInstance,          // Application instance
                          NULL);              // Pointer to create
                                              // parameters
    if (!IsWindow (hWnd)) return -2;  // Fail code if not created.
  
    // Standard show and update calls
    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);
  
    // Application message loop
    while (GetMessage (&msg, NULL, 0, 0)) {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
    // Instance cleanup
    return msg.wParam;
}
//======================================================================
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
                              LPARAM lParam) {
    PAINTSTRUCT ps;
    RECT rect;
    HDC hdc;
  
    switch (wMsg) {
    case WM_PAINT:
        // Get the size of the client rectangle
        GetClientRect (hWnd, &rect);
  
        hdc = BeginPaint (hWnd, &ps);
        DrawText (hdc, TEXT ("Hello Windows CE!"), -1, &rect,
                  DT_CENTER | DT_VCENTER | DT_SINGLELINE);
  
        EndPaint (hWnd, &ps);
        return 0;
  
    case WM_DESTROY:
        PostQuitMessage (0);
        break;
    }
    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}


Hello3展示了Windows程序的各个方面,从注册窗口类到创建窗口及窗口过程。和头两个例子一样,Hello3有着相同的入口点--WinMain。但是因为Hello3创建了自己的窗口,所以它必须为主窗口注册一个窗口类,创建窗口并且提供一个消息循环来为窗口处理消息。

注册窗口类
在WinMain中,Hello3为主窗口注册了窗口类。注册一个窗口类只是简单的填充一个描述窗口类的有些大的结构并调用RegisterClass函数。RegisterClass和WNDCLASS结构定义如下:

ATOM RegisterClass (const WNDCLASS *lpWndClass);
  
typedef struct _WNDCLASS {
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HANDLE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
} WNDCLASS;

给WNDCLASS结构各个域赋的值为Hello3主窗口的所有实例定义了行为表现。style域为窗口设置了类的风格。在Windows CE中,类风格被限制为:
CS_GLOBALCLASS  表示类是全局的。这个标志只是出于兼容性才提供的,因为Windows CE中所有窗口类都是进程级全局类。
CS_HREDRAW      告诉系统如果窗口改变了水平大小,就强制重画窗口。
CS_VREDRAW      告诉系统如果窗口改变了垂直大小,就强制重画窗口。
CS_NOCLOSE      如果[关闭]按钮出现在标题栏上,则使其失效。
CS_PARENTDC 让窗口使用父窗口的设备环境变量
CS_DBLCLKS      允许[双击]通知(Windows CE下敲击两次为双击)传递给父窗口

lpfnWndProc分配的是窗口的窗口过程的地址。因为该域定义为指向窗口过程的指针,所以在源代码中,必须在域被设置之前,定义该过程的声明。否则,编译器类型检查时会警告该行。


cbClsExra允许程序员为类结构增加额外的空间来存储只有应用程序才知道的类特定数据。cbWndExtra更加便于使用,这个域为Windows内部结构增加空间,该结构负责维护窗口每个实例的状态。不在窗口结构本身里存储大量的数据,应用程序应该存储一个指向应用程序特定结构的指针,该结构包含窗口每个实例的数据。在Windows CE里,cbClsExtra 和cbWndExtra域必须时4字节的倍数。


hInstance域设置为程序的实例句柄,该句柄指明拥有窗口的进程。hIcon域设置为窗口默认图标的句柄,但在Windows CE中并不支持该域,所以该域应该设置为NULL。(在Windows CE中,会在类的第一个窗口被创建后设置类的图标。对于Hello3,没有图标提供,并且与其它Windows版本不同,Windows CE中没有任何预定义图标用于装载。)

除非应用程序是为带鼠标的Windows CE系统设计的,否则hCursor域应该设置为NULL。幸运的是,如果系统不支持光标,调用LoadCursor (IDC_ARROW) 函数会返回NULL。

hbrBackground域规定Windows CE如何画窗口背景。Windows用这个域中指定的刷子brush(一个小的预定义的像素数组)来画窗口背景。Windows CE提供许多预定义的刷子,你可以用GetStockObject函数来装载。如果hbrBackground域是NULL,窗口必须处理WM_ERASEBKGND消息,重画窗口背景。

lpszMenuName域必须设置为NULL,因为Windows CE不直接支持有菜单的窗口。在Windows CE中,菜单由主窗口创建的命令工具条、命令带或菜单条控件提供。

lpszClassName设置为程序员定义的字符串,用于为Windows指明类的名字。Hello3用的是“MyClass”做为类名。

整个WNDCLASS类被填充后,RegisterClass函数被调用,并用指向WNDCLASS结构的指针作为唯一的参数。如果函数成功,一个标记窗口类的值被返,如果失败,函数返回0。

创建窗口
一旦窗口类注册成功,就可以创建主窗口了。所有Windows程序员在他们的我Windows编程生涯里都会学习到CreateWindow和CreateWindowEx函数调用。CreateWindowEx 的原型如下:
HWND CreateWindowEx (DWORD dwExStyle, LPCTSTR lpClassName,
                     LPCTSTR lpWindowName, DWORD dwStyle,
                     int x, int y, int nWidth, int nHeight,
                     HWND hWndParent, HMENU hMenu,
                     HINSTANCE hInstance, LPVOID lpParam);

虽然参数数量让人畏惧,但是一旦你了解了这些参数,会发现它们很有逻辑性。第1个参数是扩展风格标志位。Windows CE能够支持的扩展风格标志如下:
WS_EX_TOPMOST  窗口置顶
WS_EX_WINDOWEDGE  窗口有凸起的边框
WS_EX_CLIENTEDGE  窗口有凹陷的边框
WS_EX_STATICEDGE  静态窗口具有3D外观
WS_EX_OVERLAPPEDWINDOW  是WS_EX_WINDOWEDGE 和WS_EX_CLIENTEDGE两个风格的组合
WS_EX_CAPTIONOKBUTTON  在标题栏上有OK按钮
WS_EX_CONTEXTHELP      在标题栏上有帮助按钮
WS_EX_NOACTIVATE       点击窗口时,窗口不成为活动窗口
WS_EX_NOANIMATION      当窗口创建的时候,顶层窗口没有弹出矩形,在任务条上也没有按钮。
WS_EX_NODRAG        防止窗口被移动

参数dwExStyle是CreateWindowEx和CreateWindow之间唯一有差别的地方。实际上,如果你在Windows CE头文件里看CreateWindow的声明,会发现CreateWindow只是简单的将dwExStyle设置为0并调用CreateWindowEx而已。

第2个参数是实例化窗口所使用的窗口类的名字。在Hello3里,类名是MyClass,也就是用RegisterClass注册的类的名字。

第3个参数用做窗口文本。在Windows其它版本里,它是用做标准窗口标题栏里的文字。在H/PC里,主窗口很少有标题栏,这个文字只用在任务条按钮上。在Pocket PC里,这个文字出现在显示屏顶部导航条里。文字使用TEXT宏,确保字符串在Windows CE下可以转换成Unicode。

风格标志规定了窗口的初始风格。风格标志即用于系统里所有相关窗口的普通风格,也用于特定类的风格,比如按钮类或者列表框类的风格。在这种情况下,我们需要指定的就是用WS_VISIBLE标志来指明窗口在创建时可视。经验丰富的Win32程序员应该查阅关于CreateWindow的文档,因为许多窗口风格标志在Windows CE下并不支持。

接下来的四个参数指定了窗口初始位置和大小。因为Wimdows CE下大部分应用程序都是全屏窗口,所以每一个大小和位置域都使用CW_USEDEFAULT标志作为默认值。在Windows CE当前版本里,默认值创建的窗口,其大小覆盖整个屏幕。注意不要为Windows CE设备假设任何特殊的尺寸,因为不同的实现有不同的屏幕尺寸。

下一个域填写为父窗口的句柄。因为是顶层窗口,所以父窗口域设置为NULL。菜单域也设置为NULL,因为Windows CE不支持顶层窗口菜单。

hInstance参数就是传递给程序的实例句柄。窗口创建中实例句柄总会用的上,实例句柄是在程序开始时就被存储的。最后的参数是一个指针,在WM_CREATE消息期间,用于在CreateWindow调用时传递数据给窗口过程。本例中,没有额外数据需要传递,所以该参数设置为NULL。


如果创建窗口成功,CreateWindow返回刚创建的窗口的句柄,如果函数调用期间有错误发生,则返回0。在通过错误检查语句--if语句之后,该窗口句柄用在接下来的两个语句里(ShowWindow 和UpdateWindow里)。ShowWindow函数修改窗口状态,使其同传给WinMain的参数nCmdShow中给的状态一致。UpdateWindow函数则强制Windows发送WM_PAINT消息给刚创建的窗口。

消息循环
主窗口创建后,WinMain进入消息循环,这是每个Windows 应用程序的心脏。Hello3的消息循环定义如下:
while (GetMessage (&msg, NULL, 0, 0)) {
    TranslateMessage (&msg);
    DispatchMessage (&msg);
}

该循环很简单:调用GetMessage函数,从应用程序消息队列中获取下一个消息。如果没有消息可用,则调用进入等待期,阻塞应用程序线程直到消息可用。当消息可用,该函数返回包含在MSG结构的消息数据。MSG结构自身包含几个域,有的用于识别消息,有的提供特定消息参数,有的识别在消息被发送之前,被笔触摸过的最后屏幕位置点。该位置信息不同于标准Win32消息位置数据,在XP下,返回的位置是当前鼠标位置而不是最后点击(或者tapped,在Windows CE里)的位置。

TranslateMessage 把适当的键盘信息转换成字符信息。(后面会讨论其它信息过滤器,比如IsDialogMsg。)DispatchMessage 接下来告诉Windows把消息发给应用程序适当的窗口。

获取消息、转换消息、分发消息这个过程会一直循环到GetMessage 收到WM_QUIT消息,这会使GetMessage返回0,这一点不同于其它消息。从while子句可以看出,GetMessage返回0将导致循环终止。

消息循环终止后,程序除了清理和退出外几乎不做什么。在Hello3里,程序简单的从WinMain中返回。WinMain的返回值成为程序的返回码。传统上,最后一个消息WM_QUIT的wParam参数值包含有返回值。为了响应应用程序对PostQuitMessage的调用,WM_QUIT消息被发送出去,此时WM_QUIT的wParam参数值被填充。


窗口过程
发送或提交(send 或 post方式)到Hello3主窗口的消息被送到MainWndProc过程中。和所有窗口过程一样,MainWndProc原型如下:
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
                              LPARAM lParam);

返回值类型LRESULT 实际上就是long型(在Windows里long是一个32位值),写成这种形式是为源代码和机器之间提供一个中间级。虽然你可以轻易的从包含文件中确定Windows编程时使用的变量的真实类型,但当你试图把代码做跨平台的转换时会产生问题。虽然了解变量类型的大小对计算内存使用是有用的,但没有什么好的理由去使用(实际上有很多不使用的理由)windows.h文件中提供的类型定义。

CALLBACK 类型指明该函数是EXE的外部入口点,这是Windows直接调用该过程所必须的。在桌面系统里,CALLBACK 指出参数是按类Pascal风格从右到左方式压进程序栈的,这和标准C语言方式相反。为外部入口点使用Pascal语言栈框架的原因可以追朔到Windows开发非常早的时期。使用固定大小、Pascal栈方式,意味着由被调用的过程来清理栈,而不是留给调用者来清理。这种方式可以有效的减少Windows及其附属程序的大小,所以早期的微软开发者认为这是一个好的方式。在Windows CE里,应用程序对所有函数都使用C栈框架,不管是否是外部调用。

传给窗口过程的第一个参数是窗口句柄,当您需要定义具体的窗口实例的时候,这个句柄是很有用的。wMsg参数表示发给窗口的消息。这不是WinMain消息循环里使用的MSG结构,而是一个包含消息值的unsigned整型。剩余两个参数,wParam 和lParam, 传递和具体消息有关的数据给窗口过程。它们的名字来自Win16时代,那时wParam是个16位值而lParam是32位值。同其它Win32操作系统一样,在Windows CE里,两个都是32位的。

和传统的窗口过程一样,Hello3的窗口过程通过一个switch语句解析wMsg消息ID。该switch语句包含2个case语句,一个用来解析WM_PAINT消息,另一个用来解析WM_DESTROY消息。这个窗口过程大概是窗口过程所能简化到及至的一个窗口过程了。

WM_PAINT
绘制窗口,处理WM_PAINT消息,这在任何Windows 程序中都是很重要的功能之一。窗口的外观是在程序处理WM_PAINT消息的过程中完成的。除了用您在注册窗口类时指定的刷子绘制默认背景外,Windows对处理该消息不提供任何帮助。Hello3中处理WM_PAINT消息如下:
case WM_PAINT:
    // Get the size of the client rectangle
    GetClientRect (hWnd, &rect);
  
    hdc = BeginPaint (hWnd, &ps);
    DrawText (hdc, TEXT ("Hello Windows CE!"), -1, &rect,
              DT_CENTER | DT_VCENTER | DT_SINGLELINE);
  
    EndPaint (hWnd, &ps);
    return 0;
在窗口绘制之前,程序必须确定窗口大小。在Windows程序里,一个标准窗口被划分为两个区域--非客户区和客户区。窗口标题栏和可变大小的边框通常占据了窗口的非客户区,这个区域由Windows负责绘制。客户区属于窗口的内部区域,由应用程序负责绘制。应用程序通过调用GetClientRect 函数来确定客户区的大小和位置。该函数返回一个RECT结构,包含左上角、右下角坐标等描述客户区矩形边界的信息。分成客户区和非客户区的好处是,应用程序不必绘制那些窗口标准元素,例如标题栏。

其它版本的Windows提供一系列WM_NCxxx消息,允许您的应用程序绘制非客户区。在Windows CE里,窗口很少有标题栏。因为很少有非客户区,所以Windows CE不发送非客户端消息给窗口过程。

WM_PAINT消息里执行的所有绘制工作都必须由两个函数BeginPaint 和EndPaint包围。BeginPaint 函数返回设备环境句柄HDC。设备环境是物理显示设备(例如视频显示器或打印机)的逻辑代表。Windows程序从不直接修改显示硬件。相反,Windows用设备环境将程序与具体硬件隔离开。

BeginPaint 填充一个PAINTSTRUCT结构,其结构如下:
typedef struct tagPAINTSTRUCT {
    HDC  hdc;
    BOOL fErase;
    RECT rcPaint;
    BOOL fRestore;
    BOOL fIncUpdate;
    BYTE rgbReserved[32];
} PAINTSTRUCT;

hdc就是BeginPaint函数返回的句柄。fErase指出窗口过程是否需要重画窗口背景。rcPaint是RECT结构,定义了需要重画的客户区。Hello3忽略该域,并假设在每个WM_PAINT消息中,整个客户区窗口都需要重画。当性能是需要考虑的问题时,该域是很有用的,因为有时仅仅需要重画部分窗口即可。即使当程序尝试重画rcPaint矩形以外的区域时,Windows也会阻止这么做的。该结构的其它域,fRestore, fIncUpdate, 和rgbReserved,属于Windows内部使用,应用程序可以忽略掉它们。

Hello3中唯一的绘制工作是在窗口绘制一行文本。Hello3调用DrawText函数来完成该绘制。我将在第2章描述DrawText的细节,如果您看一下该函数,很容易会明白这个调用在窗口上绘制了一行字符串“Hello Windows CE”。在DrawText返回后,调用EndPaint来通知Windows程序已经完成了窗口更新。

EndPaint调用同时也使没有被绘制的窗口其它区域有效。Windows保持一份无效窗口区域(也就是需要重画的区域)列表和有效区域(也就是已经更新的区域)列表。不论您是否在窗口画了什么,通过成对的调用BeginPaint和EndPaint,会通知Windows由您来处理窗口的无效区域。实际上,您必须调用BeginPaint和EndPaint,或者通过其它方式使窗口无效区域变有效,否则Windows会不断发送WM_PAINT消息给窗口,直到无效区域变有效。

WM_DESTROY
Hello3中处理的另一个消息是WM_DESTROY。当窗口即将被销毁时,该消息被送出。因为该窗口是应用程序主窗口,当窗口被销毁时应用程序将终止。处理WM_DESTROY消息的代码调用PostQuitMessage消息来触发该动作。PostQuitMessage函数将WM_QUIT消息放到到消息队列里。该函数的参数是返回码的值,该值放在WM_QUIT消息的wParam参数里传回应用程序。

如前所述,消息循环看到WM_QUIT消息就会退出循环。WinMain 接着调用TermInstance,在Hello3里,该函数什么也不做,只是返回。WinMain 接着返回,并终止程序。

Hello3是典型的Windows程序。这种编程风格有时也成为Petzold式Windows编程,这是为了向Charles Petzold,这位Windows编程大师表示敬意。Charles所著的《Windows 程序设计》(Programming Microsoft Windows )当前是第5版,并且依然是学习Windows编程的最好的书。

我更喜欢为我的Windows程序选择略微不同的设计。从某种意义上讲,这是一个将Windows程序功能组件化的方法,这使的更加容易将程序的一部分复制到另一个程序。在本章最终的例子里,我介绍这种编程风格,并一起介绍一些Windows CE应用程序所必须的额外特性。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值