游戏编程中的人工智能技术读书笔记(1)

来源:互联网 发布:淘宝客模板免费下载 编辑:程序博客网 时间:2024/06/05 01:53

第1篇 Windows编程

第1章 Windows概述

1.1     历史一瞥(A Little Bit of History)

1.1.1           Windows1.0

1.1.2           Windows2.0

1.1.3           Windows3.03.1

1.1.4           Windows95

1.1.5           Windows98及其后继版本

1.2     Hello World

1.3     第一个Windows程序

开动了!系好安全带,我们就要上路了!在开始的时候,Windows编程可能会使你觉得头疼,但可以保证,一旦你自己动手写过几个程序之后,就不会那头痛了。事实上,你会开始喜欢上它。好了,下面就是让屏幕的一个窗口显示“Hello World”字样所需的完整代码。

#include <windows.h>

int WINAPI WinMain(HINSTANCE hlnstance,

                 HINSTANCE hPrevInstance,

                 LPSTR lpCmdLine,

                 int nShowCmd)

{

    MessageBox(NULL,"Hello World!","MsgBox",0);

    return 0;

}

如果在集成开发环境(IDE)里输入这个程序,再编译和运行它,在屏幕上就会出现一个小的消息框,等待单击OK按钮后退出。如果决定自行输入源程序,在IDE里选择创建的project类型时,必须选择W32 Application而不是Win32 console Application。否则代码不会通过编译。

如你所见,首先不同的一点就是程序入口点不再是昔日的:

int main()

而是

int WINAPI WinMain(HINSTANCE hlnstance,

                 HINSTANCE hPrevInstance,

                 LPSTR lpCmdLine,

                 int nShowCmd)

下面就来解释一下。

基本上可以忽略WINAPI。它只是WINDEF.h中定义的一个宏,形式如下:

#define WINAPI _stdcall

这是使编译器知道应当以Windows兼容方式产生机器指令。如果忽略它,程序仍然可以编译并运行,但在编译期间会收到一条警告,建议一定要确保WinMain函数具有WINAPI前缀。

hInstance:

第一个参数hInstance是一个句柄(Handle)。这是一个Windows在运行时(runtime)为程序提供了一个基本识别标志(ID)Windows用这个句柄来识别程序,使它与同时处在运行状态的其他程序区别。

hPrevInstance:

第二个参数hPrevInstance也是一个句柄,但现在总是设为NULL。它曾被一些16Windows应用程序使用,以达到同时打开一个程序的几个copy的目的。但现在已经没什么用处了。

lpCmdLine:

参数lpCmdLine是用来将命令行参数传递给应用程序的。LPSTRWINNT.h中被定义为字符串指针。当从命令行运行应用程序时,字符串lpCmdLine中就会含有所输入的,除程序名字以外的所有字符。举例来说,如果执行程序的名称是MyGame.exe,而输入的命令是MyGame /s/d/log.txt,那么lpCmdLine中就会含有“/s/d/log.txt”字样。

nCmdShow:

最后一个参数nCmdShow告诉你的程序在启动后应怎样显示。有许多不同的参数可供选择,如下表所示。

1.1 nCmdShow选项

参数

意义

SW_HIDE

隐藏此窗口并激活另一个窗口

SW_MINIMIZE

最小化指定窗口,并激活处在系统列表里最顶层的窗口

SW_RESTORE

激活并显示某个窗口。如果该窗口处在最小化或最大化状态,则Windows替它恢复原始尺寸和位置

(注:与SW_SHOWNORMAL相同)

SW_SHOW

激活窗口并以当前尺寸和位置显示它

SW_SHOWMAXIMIZED

激活窗口并以最大化方式显示

SW_SHOWMINIMIZED

激活窗口并以图标方式显示

SW_SHOWMINNOACTIVE

将某个窗口以图标方式显示。当前活动窗口不受影响

SW_SHOWNA

将某个窗口以当前状态显示。当前活动窗口不受影响

SW_SHOWNOACTIVATE

将某个窗口以前次尺寸和位置显示。当前活动窗口不受影响

SW_SHOWNORMAL

激活并显示某个窗口。口如果该窗口处在最小化或最大化状态

(注:与SW_SHOWRESTORE作用相同)

当用户在桌面或在开始菜单里创建应用程序的快捷方式时,他可以指定应用程序的打开方式。因此,如果用户觉得窗口运动后就应该马上最大化,那就把nCmdShow设成SW_SHOWMAXIMIZED就好了。

接下来看这一行代码:

MessageBox(NULL,"Hello World!","MsgBox",0);

它是成千个Win32 API函数之中的一个。它可以显示一个消息框,还可以通过一些参数来设置这个消息框的式样。特别是在需要向用户显示错误信息的时候,非常有用。

下面是它的函数原型:

int MessageBox(HWND hWnd,              //有者(owner)窗体的句柄

            LPCTSTR lpText,            //消息框内显示的文本字符串

            LPCTSTR lpCation,              //消息框标题栏显示的文本字符串

            UINT uType);               //消息框的(,什么样图标提示等)

uType

1.2消息框uType样式

按钮类型设置

标志(flag)

意义

MB_ABORTRETRYIGNORE

消息框带三个按钮:终止,重试,忽略

MB_OK

消息框带一个按钮:确定(默认标志)

MB_OKCANCEL

消息框带二个按钮:确定和取消

MB_RETRYCANCEL

消息框带二个按钮:重试和取消

MB_YESNO

消息框带二个按钮:是和否

MB_YESNOCANCEL

消息框带三个按钮:是,否,取消

图标类型设置

MB_ICONWARNING

消息框中显示一个红色惊叹号图标

MB_ICONASTERISK

消息框中显示一个蓝色小写字母i的图标

MB_ICONQUESTION

消息框中显示一个黄色问号图标

MB_ICONSTOP

消息框中显示一个红色STOP记号的图标(红叉)

MessageBox还会给出一个返回值(HelloWorld1中忽略了这个返回值),它是意义是用户点击的按钮的代码。如果没有足够的内存来创建消息框,MessageBox将返回0.其他返回值如下:

MessageBox的返回值:

IDABORT                                                用户单击了终止按钮

IDCANCEL                                              用户单击了取消按钮

IDIGNORE                                              用户单击了忽略按钮

IDNO                                                       用户单击了否按钮

IDOK                                                        用户单击了确定按钮

IDRETRY                                                 用户单击了重试按钮

IDYES                                                      用户单击了是按钮

读者一定在疑惑那些变量名的奇怪前缀,如lpszh等。微软程序员们在编程时都使用一种共同的约定——匈牙利符号表示法(Hungarian Notation)。下面介绍一下匈牙利表示法。

1.3.1           匈牙利表示法

匈牙利表示法是微软雇员Charles Simonyi博士的发明。它之所以称为匈牙利表示法,是因Charles来自匈牙利。基本上,这是一个命名约定:在每一个变量名前添加表示变量类型的字母前缀,并继以一个大写字母开头的对变量的简短描述。例如,如果需要用一个整型变量来保存游戏中的得分,会把它命名为iScore。匈牙利表示法的发明来自于为微软程序员建立一个可遵循的编程规范的迫切需求。如果一个公司所有的程序员各自使用不同的命名约定,一切将变得非常混乱。

1.3 匈牙利记号表示法前缀

前缀

类型

sz

指向一个以/0字符结尾的字符串中的第一个字符

str

字符串

i

int

n

数或int

ui

unsigned int

c

char

w

(无符号短整型)

dw

双字(无符号长整型)

fn

函数指针

d

double

by

byte

l

long

p

指针

lp

长整型指针

lpstr

指向字符串的长整型指针

h

句柄(handle)

m_

类成员

g_

全局类型

hwnd

窗口句柄

hdc

Windows设备上下文(device context)的句柄

有两点需要说明:第一,如果在一个小函数里用到一个变量,那么笔者会使用他觉得适当的名字,并不严格遵循这一标准(默然:我非常赞同这样的观点,即:在99.99%的情况下,我们应该遵循规范,在0.01%的情况下,我们应该创新)。另外,笔者用大写字母C作为所有类的前缀,大写字母S作为所有结构的前缀。

1.3.2           第一个窗口

在创建窗口之前,必须首先创建自己的窗口类并将它注册,这样操作系统才能知道希望此窗口以何种类型显示,并如何动作。窗口可以是任意尺寸,边界,滚动条和菜单这些都可有可无。

默然:我将整个HelloWorld2的代码在此列出,这样便于大家对照后面的说明。

默然:下面列出HelloWorld2程序的defines.h文件中源代码清单:

#ifndef DEFINES_H

#define DEFINES_H

 

#define WINDOW_WIDTH  400

#define WINDOW_HEIGHT 400

 

#endif

默然:下面列出HelloWorld2程序的main.cpp文件中源代码清单:

#include <windows.h>

 

#include "defines.h"

 

//-----------------------------------------------------------------------

// 

//  Name: HelloWorld2 示例工程

// 

//  Author: Mat Buckland 2002

//  修改及注:默然说话

//  Desc: window窗口建示例(这个窗口好象)!

//

//------------------------------------------------------------------------

//--------------------------------- Globals ------------------------------

//

//------------------------------------------------------------------------

 

char* g_szApplicationName = "Hello World!";

char*  g_szWindowClassName = "MyWindowClass";

 

//---------------------------- WindowProc ---------------------------------

// 

//  这个数处理所有的Windows消息。

//  HelloWorld3中作详细讲解。

//  只需要知道,这个示例要存在。

//-------------------------------------------------------------------------

 

LRESULT CALLBACK WindowProc (HWND   hwnd,

                             UINT   msg,

                             WPARAM wParam,

                             LPARAM lParam)

{

 

        return DefWindowProc (hwnd, msg, wParam, lParam);

}

 

//-------------------------------- WinMain -------------------------------

//

//  Windows程序入口

//------------------------------------------------------------------------

int WINAPI WinMain (HINSTANCE hInstance,

                    HINSTANCE hPrevInstance,

                    LPSTR     szCmdLine,

                    int       iCmdShow)

{

     //理我的窗口

        HWND                    hWnd;

   

        //的窗口类结构

        WNDCLASSEX     winclass;

 

     // 首次充窗口类结构

       winclass.cbSize        = sizeof(WNDCLASSEX);

       winclass.style         = CS_HREDRAW | CS_VREDRAW;

     winclass.lpfnWndProc   = WindowProc;

     winclass.cbClsExtra    = 0;

     winclass.cbWndExtra    = 0;

     winclass.hInstance     = hInstance;

     winclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);

     winclass.hCursor       = LoadCursor(NULL, IDC_ARROW);

     winclass.hbrBackground = (HBRUSH)GetStockObject (WHITE_BRUSH);

     winclass.lpszMenuName  = NULL;

     winclass.lpszClassName = g_szWindowClassName;

       winclass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

 

        //此窗口

       if (!RegisterClassEx(&winclass))

       {

           MessageBox(NULL, "Class Registration Failed!", "Error", 0);

 

           //退出用程序

           return 0;

       }

 

        //建此窗口并将它ID赋值给hwnd   

     hWnd = CreateWindowEx (NULL,                 //

                            g_szWindowClassName,  // 窗口

                            g_szApplicationName,  // 窗口标题

                            WS_OVERLAPPEDWINDOW,  // 窗口

                            0,                    // 初始x

                            0,                    // 初始 y

                            WINDOW_WIDTH,         // 初始

                            WINDOW_HEIGHT,        // 初始高度

                            NULL,                 // 父窗口句柄

                            NULL,                 // 窗口菜句柄

                            hInstance,            // 程序例句柄

                            NULL);                // 参数

 

     //窗口建成功

     if(!hWnd)

     {

       MessageBox(NULL, "CreateWindowEx Failed!", "Error!", 0);

     }

    

     //置窗口示出

        ShowWindow (hWnd, iCmdShow);

 

     UpdateWindow(hWnd);

 

     UnregisterClass( g_szWindowClassName, winclass.hInstance );

 

     return 0;

}

1.3.2.1     注册窗口

窗口类型是由生成窗口的类的结构来定义。这此,必须编写一个WNDCLASSEX结构。结构形式如下:

typedef struct  _WNDCLASSEX{

    UINT       cbSize;

    UINT       style;

    WNDPROC        lpfnWndProc;

    int        cbClsExtra;

    int        cbWndExtra;

    HANDLE     hInstance;

    HICON      hIcon;

    HCURSOR       hCursor;

    HBRUSH     hbrBackground;

    LPCTSTR       lpszMenuName;

    LPCTSTR       lpszdassName;

    HICON      hIconSm;

)WNDCLASSEX

注意:Windows以前使用一个称为WNDCLASS的结构,但微软经过改进后设计了新的WNDCLASSEX结构。有一些其他结构也因同样理由而具有EX后缀。其实老的结构也还是可用的,不过没有意义。默然:这个结构Windows已经声明,并不需要我们自己来声明的,这里只是作为介绍而把它列出。

cbSize

用来保存WNDCLASSEX结构的大小,以byte为单位。可以这样设置:

cbSize=sizeof(WNDCLASSEX);

必须确保这一参数设置正确,否则注册类时,Windows将会提示有错误。

style

是窗口应具有的显示样式。可以选择几个并将它们OR(逻辑或)起来,样式最常用的设置是:

style=CS_HREDRAW | CS_VREDRAW;

这告诉Windows API,这个窗口在用户改变窗口大小(高度或宽度)时需要重绘。在Win32 API帮助文件中还可以找到更多样式选项。

lpfnWndProc

是一个指向Windows Procedure(Windows过程)的函数指针。后面会详细讨论。

cbClsExtra/cbWndExtra:大多数情况设置为0即可。它们存在的意义仅仅是,如果必要的话(其实一般没有必要),可以为窗口类多分配几个字节的存储空间。

hInstance

WinMain里面的hInstatnce参数。用于设置窗口类结构。

hIcon:应用程序所使用的图标的句柄。此图标在用Alt+Tab组合键来切换任务的时候会显示出来。可以用Windows的默认图标之一,也可以自定义图标并作为一个资源而包含在程序里。在下一章里将讲述如何编写程序。读者可以调用LoadIcon来获取图标句柄。

hCursor

是应用程序所使用的鼠标光标的句柄。要取得光标句柄应调用LoadCursor

hCursor=LoadCursor(NULL,IDC_ARROW);

hbrBackground

是一个用以指定创建窗口的客户区背景色的域。客户区是窗口中实际绘图和写字的区域。hbr前缀表示它是一个画刷句柄。Windows用画刷来指定填充区域的颜色甚至也包括预定义图案(pattern)。可以定义用户自己的画刷,也可以选用API已经定义好的许多画刷。例如,如果希望背景以白色填充,可以这样设:

hbrBacground=(HBRUSH)GetStockObject(WHITE_BRUSH);

在第2章介绍Windows的进一步编程知识时将会更详细地讨论画刷。

lpszMenuName

用来设置菜单的名字,如果不需要菜单,可以把这项设为NULL

hIconSm:

是一个较小的图标的句柄,该图标将在任务栏和应用程序窗口左上角出现。和往常一样,既可以亲手设计自己的图标,并作为资源包含在程序里,也可以使用默认的某一个图标。

技巧:在这里值得一提的是,很少有程序员能够真正记得所有这些参数。大多数程序员倾向于保留一个基本的窗口模板文件,这样可以在每当要开始新项目的时候将程序复制和粘贴即可,这样做很方便。

1.3.2.2     创建窗口

下面创建并显示窗口。这里需要调用CreateWindowEx函数。函数原型如下:

CreateWindowEx (DWORD       dwExStyle,         //

               LPCTSTR      lpClassName,       // 窗口

               LPCTSTR      lpWindowName,      //窗口标题

               DWORD        dwStyle,           //窗口

               int          x,                 //初始x

               int          y,                 //初始y

               int          nWidth,            //初始

               int          nHeight,           //初始高度

               HWND         hWndParent,        //父窗口句柄

               HMENU        hMenu,             //窗口菜句柄

               HINSTANCE    hInstance,         //程序例句柄

               LPVOID       lpParam);          //参数

 

dwExStyle

用来设置需要的任意扩展式样。表1.4里列出了一些可用的式样,但在这个Windows程序里是不需要使用的,设为NULL

1.4扩展窗口式样

扩展样式标志

描述

WS_EX_ACCEPTFILES

使用该样式创建而成的窗口接受拖放文件

WS_EX_APPWINDOW

使一个可见顶级窗口出现在任务栏上

WS_EX_CLIENTEDGE

指定该窗口使用凹陷的边框

WS_EX_DLGMODALFRAME

创建一个有双线边框的窗口。此窗口可以(可选)通过在dwStyle参数里使用WS_CAPTION式样而具有一个标题栏。

WS_EX_CONTEXTHELP

在窗口的标题栏里加入问号按钮。当用户单击该按钮时,光标会变成一个带有问号的光标。此时若用户单击窗口中的控件,则窗口控件会收到一条WM_HELP消息。窗口控件应当将此消息传递给其父窗口函数,由其通过HELP_WM_HELP调用WinHelp函数。Help程序会显示一个包含关于窗口控件的帮助信息的弹出窗口

WS_EX_WINDOWEDGE

指定该窗口使用凸出的边框。

 

lpClassName:

是字符串指针,对象是窗口类的名字。在这里以及后面的所有例子里,均被设为g_szWindowClassName

可以把窗口类名和应用程序名作为两个全局字符串g_szWindowClassNameg_szApplicationName存放在main.cpp的顶部。这样做日后修改名字更加容易,只需要看一个地方就可以了。

lpWindowName

显示在顶端的应用程序标题。本书的例子里被设为g_szApplicationName

dwStyle

包含了用于设置窗口样式的标志。表1.5列出一些较为常用的样式。

1.5 窗口样式

样式标志

描述

WS_BORDER

创建的窗口具有细线边框

WS_CAPTION

创建的窗口带有标题栏(包括WS_BORDER式样)

WS_HSCROLL

创建的窗口带有水平滚动条

WS_MAXIMIZE

创建的窗口初始为最大化

WS_OVERLAPPED

创建的窗口有标题栏和边框

WS_OVERLAPPEDWINOW

创建一个由WS_OVERLAPPED, WS_CAPTION, SW_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOX组合在一起的窗口(有边框和标题栏,系统菜单,边框可变大小,有最大化和最小化按钮)。它与WS_TILEDWINDOW相同。

WS_POPUP

创建一个弹出式窗口。这个式样不能和WS_CHILD式样一起使用

WS_THICKFRAME

创建的窗口的边框可变大小

WS_VSCROLL

创建的窗口带有垂直滚动条

x,y

用来设置窗口左上角的位置。在Windowsy的值是自上而下从零开始增加的。

nWidth,nHeight

指定了窗口的宽度和高度。在我们的工程里,这两个值被定义在defines.h这个头文件里,命名为WINDOW_WIDTHWINDOW_HEIGHT

hWndParent

此窗口父窗口的句柄。如果该窗口为主程序窗口,将句柄设为NULL,这就使Windows将桌面当作父窗口。

hMenu

是一个菜单句柄。菜单出现在应用程序顶部,我将在后面讨论菜单,现在设为NULL即可。

hInstance:

使用从WinMain里的hInstance

lpParam

只是在创建多文档界面(MDI)时才会用到,这里设为NULL

下面是HelloWorld2CreateWindowEx的完整调用代码:

hWnd = CreateWindowEx (NULL,                 //

                            g_szWindowClassName,  // 窗口

                            g_szApplicationName,  // 窗口标题

                            WS_OVERLAPPEDWINDOW,  // 窗口

                            0,                    // 初始x

                            0,                    // 初始 y

                            WINDOW_WIDTH,         // 初始

                            WINDOW_HEIGHT,        // 初始高度

                            NULL,                 // 父窗口句柄

                            NULL,                 // 窗口菜句柄

                            hInstance,            // 程序例句柄

                            NULL);                // 参数

最后,用下面这两个调用语句使窗口变为可见:

ShowWindow (hWnd, iCmdShow);

UpdateWindow(hWnd);

ShowWindow需要两个参数。第一个是需要显示的窗口——也就是你刚才创建的窗口的句柄hWnd。第二个参数是一个指定窗口是否可见和是否应当最小化,正常或最大化的标志。要记住,iCmdShow使用的值,就是用户在创建快捷方式或把程序加入开始菜单时所选用的值。

然后利用UpdateWindow使用窗口的客户区以背景画刷指定的颜色画出来。

由于已经注册了一个自定义的窗口类,在程序退出以前,必须撤销(Unregister)这个类。可以使用UnregisterClass来实现,如:

BOOL UnregisterClass(    LPCTSTR       lpClassName,             //指向标识窗口名的字符串指

                      HINSTANCE  hlnstance)                //指向窗口的句柄

将窗口的标识类名的字符串和窗口实例句柄传给这个函数,操作就全部完成了。

编译并运行HelloWorld2例子。你会看见新创建的窗口出现,一瞬间就消失了。所以,需要寻找另一种解决方案,这个解决方案就是非凡,奇妙的Windows消息循环。

1.3.3           Windows消息循环

默然:这是HelloWorld3main.cpp新增加及修改过的代码(注意与HelloWorld2进行比较),由于defines.h并没有发生变化,所以在这里不再列出。

……

……

//前面是文件包含(#include)及常量声明,与HelloWorld2相同

//---------------------------- WindowProc ---------------------------------

// 

//  处理所有windows消息的回调函数

//-------------------------------------------------------------------------

 

LRESULT CALLBACK WindowProc (HWND   hwnd,

                             UINT   msg,

                             WPARAM wParam,

                             LPARAM lParam)

{

  

     switch (msg)

     {

   

       //当你的应用程序窗口第一次被运行时 WM_CREATE消息会被发出。

       //created

       case WM_CREATE:

            {

               PlaySound("window_open.wav", NULL, SND_FILENAME | SND_ASYNC);

            }

 

       break;

   

       case WM_PAINT:

            {

                PAINTSTRUCT ps;

         

          BeginPaint (hwnd, &ps);

 

                  //**我们在这里做一些绘制工作**

    

          EndPaint (hwnd, &ps);

            }

 

       break;

         

        case WM_DESTROY:

            {

                // 消毁应用程序,这里发送了一个 WM_QUIT消息。

                PostQuitMessage (0);

            }

 

       break;

 

 

     }//switch结束

 

     //那些我们没有定义应该怎么处理的消息

        //Windows操作系统的默认消息处理函数去完成处理(大部分情况下,也就是简单的将消息删除)

        return DefWindowProc (hwnd, msg, wParam, lParam);

}

//-------------------------------- WinMain -------------------------------

//

//  窗口程序入口

//------------------------------------------------------------------------

int WINAPI WinMain (HINSTANCE hInstance,

                    HINSTANCE hPrevInstance,

                    LPSTR     szCmdLine,

                    int       iCmdShow)

{

    ……

    ……

    //以上WinMain大部分的代码由于与HellowWorld2的代码相同,所以不再重复。

    //“确认窗口创建成功”部分的代码是重复的,在此出现,仅是为了提供读者能定位新代码位置。

   

    //窗口建成功

     if(!hWnd)

     {

       MessageBox(NULL, "CreateWindowEx Failed!", "Error!", 0);

     }

    

     //置窗口示出

    ShowWindow (hWnd, iCmdShow);

     UpdateWindow (hWnd);

   

    //此处是HelloWorld3新增加的代码

    //保存从队列中获得的任意Windows消息

    MSG msg;

    

    //消息循环的入口

    while (GetMessage (&msg, NULL, 0, 0))

     {

          TranslateMessage (&msg);

          DispatchMessage (&msg);

     }//消息循环结束

 

     return msg.wParam;

}

Windows是一个事件驱动(Event-Driver)的操作系统。事件——也就是消息——是在用户作各种操作时产生出来的。它们会被储存在一个消息队列(message queue)中,直到当前活动的应用程序来处理它们。Windows会不断地创建新的消息。而程序员则必须找到一种从队列中读取消息并处理的方法。所以,在UpdateWindow(hWnd)之后应立即使用一个被称作Windows消息循环(Windows message pump)的循环过程:

     //保存从队列中获得的任意Windows消息

        MSG msg;

    

        //消息循环的入口

        while (GetMessage (&msg, NULL, 0, 0))

     {

          TranslateMessage (&msg);

          DispatchMessage (&msg);

     }//消息循环结束

上面这一段代码中,变量msg用来保存消息,它是一个结构体,定义如下:(默然:下面的代码Windows已经定义好了,并不需要键入)

typedef  struct  tagMSG{

    HWND       hwnd;

    UINT       message;

    WPARAM     wParam;

    LPARAM     lParam;

    DWORD      time;

    POINT      pt;

}MSG;

hwnd

将要包含该消息的窗口的句柄

message

消息标识符。表1.6列出了常用的消息标识符。

1.6常用Windows消息

消息

描述

WM_KEYUP

当用户放开某个非系统按键时,会发出此消息

WM_KEYDOWN

按下某个非系统按键时,会发出此消息

WM_MOUSEMOVE

每次光标移动时就会发出这条消息

WM_SIZE

用户改变窗口尺寸时就会发出此消息

WM_VSCROLL

垂直滚动条移动时就会发出这条消息

WM_HSCROLL

水平滚动条移动时就会发出这条消息

WM_ACTIVATE

有两种情况会发出这条信息,一是窗口被用户激活时,二是窗口被取消激活时——可以通过wParam判断到底是哪种

wParamlParam:

是两个32位的参数,它们包含了消息的一些附加信息。例如,如果消息是WM_KEYUP,则参数wParam指出了是哪一个按键刚刚被放开的信息,而lParam给出了诸如上一个按键状态和重复次数的附加信息。

time:

是消息进入事件队列的时间。

pt:

是一个POINT结构,其中存有消息进入队列的时候的鼠标坐标。POINT结构定义如下:(默然:下面的代码Windows已经定义好了,并不需要键入)

typeded  struct  tagPOINT{

         LONG  x;

         LONG  y;

}POINT;

下面逐行地解释消息循环。

while (GetMessage (&msg, NULL, 0, 0))

     {

GetMessage是从消息队列里取出消息的函数。如果队列里一条消息也没有,它会一直等待,直到出现一条消息。GetMessage获得消息的办法,是传递一个MSG结构的指针让Windows来填充这个结构。第二个参数是需要获取消息的窗口的句柄。把参数设为NULL可以让这个窗口处理所有的消息。第3和第4个值是不需要了解的附加过滤器,因此都设为0。如果GetMessage收到消息不是WM_QUIT,则都应该返回一非零值,循环就会一直循环下去。还有一点,当GetMessage收到一条信息后,该条消息就会被Windows从消息队列中删去。

TranslateMessage (&msg);

当一个按键被按下时,Windows会产生一条WM_KEYDOWN消息,而放开按键时,则Windows会产生一条WM_KEYUP消息。如果按下Alt键,那就产生了一条WM_SYSKEYDOWN消息,而若按住Alt键,再把一个键按下并松开,将得到一个WM_SYSKEYUP消息。如果用户不停地在键盘上按下了许多键,消息队列很容易会被消息堵塞。而TranslateMessage的作用,就是将这样的多个键盘消息,组合成为一个单个的消息WM_CHAR

默然:实在忍不住了!!!上面这段话让你看得莫名其妙,也不知道是不是作者不会使用互联网上著名的Google?下面是我查到的,能让大家一目了然的语言(拜托,并不是只要懂英语,就可以来翻译计算机书籍的!)

GetMessage是用来对你取的消息结构MSG变量进行必要的预处理(你可以不用关心它做了些什么)GetMessage函数取得的消息,要经过TranslateMessage处理一下,然后才可以传给DispatchMessage函数,因此,TranslateMessage必放在GetMessageDispatchMessage之间。

DispatchMessage (&msg);

默然:书上的原文又是一大段让人看着莫名其妙的话语,我又再一次感觉到了互联网及Google的重要性和强大,真心感谢那些为互联网的诞生而所出贡献的人们。

DispatchMessage其实就是用来完成调用WindowProc回调函数并把由GetMessage取得的消息结构MSG变量中的信息传递给WindowProc回调函数

这样,应用程序停留在消息循环的循环中不停地处理消息,直到用户关闭窗口,引发WM_QUIT消息,这时GetMessage返回零(C++,零代表假),于是应用程序退出消息pump并终止执行。

1.3.4           Windows过程(默然:即消息回调函数。。。汗。。。)

默然:消息回调是什么意思呢?按照候先生在《深入浅出MFC》中的说法,是一种为Windows准备的函数,即此函数虽然是你写的,但是你却不调用,留给操作系统调用。按我的理解,很类似于美国人做了一个产品的商标,制订了一点点粗略的标准(主要是外部包装应该是什么样子之类)然后让中国人来制造,具体中国人怎么造美国人不管,美国人只管中国人造的必须贴上它们的商标,然后由美国人去卖。这就叫消息回调(再举个例:老师布置了论文的题目,由学生去写,完成之后,老师拿去发表了,无论是老师帮学生发表,还是老师以自己的名义发表,都是消息回调。)

消息回调函数的定义形式如下:

LRESULT CALLBACK WindowProc(

    HWND   hwnd;  //窗口句柄

    UINT   uMsg;  //消息标识

    WPARAM wParam;    //结构MSG中的第一消息参数

    LPARAM lParam;    //结构MSG中的第二消息参数

);

LRESULT

是回调函数的返回值类型。一般正常情况下是个非零值。

CALLBACK

用来告诉WindowsWindowProc是一个回调函数,每当Windows产生了需要处理的事件时,就会调用此函数(即回调)。不需要一定将Windows的回调函数名称定为WindowProc,可以使用自己喜欢的名字。

注意:可以同时打开几个窗口,在各自的代码块中编写各自的WindowProc

hwnd

是用户要为它处理消息的窗口的句柄。

uMsg

消息标识符。即MSG结构中的message成员变量(见前面的MSG参数说明)

wParamlParam

MSG结构中的wParamlParam成员变量(见前面的MSG参数说明)

下面,列出Windowproc回调函数的代码:

LRESULT CALLBACK WindowProc (HWND   hwnd,

                             UINT   msg,

                             WPARAM wParam,

                             LPARAM lParam)

{

  

     switch (msg)

     {

   

       //当你的应用程序窗口第一次被运行时 WM_CREATE消息会被发出。

       //created

       case WM_CREATE:

            {

               PlaySound("window_open.wav", NULL, SND_FILENAME | SND_ASYNC);

            }

 

       break;

   

       case WM_PAINT:

            {

                PAINTSTRUCT ps;

         

          BeginPaint (hwnd, &ps);

 

                 //**我们在这里做一些绘制工作**

    

          EndPaint (hwnd, &ps);

            }

 

       break;

          

        case WM_DESTROY:

            {

                // 消毁应用程序,这里发送了一个 WM_QUIT消息。

                PostQuitMessage (0);

            }

 

       break;

 

 

     }//switch结束

 

     //那些我们没有定义应该怎么处理的消息

        //Windows操作系统的默认消息处理函数去完成处理(大部分情况下,也就是简单的将消息删除)

        return DefWindowProc (hwnd, msg, wParam, lParam);

}

大家已经看到,上例的WindowProc只是一个大的switch语句。它只处理了3种消息:WM_CREATEWM_PAINTWM_DESTROY

1.3.4.1     WM_CREATE消息

WM_CREATE消息是窗口第一次创建时产生的。这一消息被消息pump俘获后,最终被发送到WindowProc,再由后者的switch语句进行处理。

case WM_CREATE:

            {

               PlaySound("window_open.wav", NULL, SND_FILENAME | SND_ASYNC);

            }

 

       break;

这个例子里,当程序执行到switch语句的WM_CREATE段落时,在打开窗口的同时,调用API里的PlaySound函数,播放了一曲wav文件。PalySound定义如下:

BOOL PlaySound(

              LPCSTR      pszSound,

              HMODULE     hmod,

              DWORD       fdwSound);

pszSound

是一个字符串,指定了要播放的声音文件。如果它的值为NULL,则所有当前正在播放中的声音都会停下来。

hmod

是包含该wav作为资源的可执行文件的句柄。下一章将会介绍各种资源。在这里把此参数设成NULL就可以了。

fdwSound

是用来控制声音播放的一个标志。完整的列表可以查阅一些文档。这里使用了SND_FILENAMESND_ASYNC,这样PlaySound就知道pszSound是文件名,而且声音是异步播放的。异步意味着PlaySound函数在播放声音的同时,窗体也会打开。

注意:要使PlaySound函数,必须用include语句来包含一个Windows多媒体库,因此,在试图编译HelloWorld3之前,在工程的设置中确认编译器会连接winmm.lib

默然:如果你使用的是VC6,你可以选择菜单Project-- >Setting-- >Link,在Object/library modules文本框中加入winmm.lib

1.3.4.2     WM_PAINT消息

任何时候,只要系统需要重绘应用程序窗口的任何部分,都会产生一个WM_PAINT消息。许多初学Windows编程的人都认为,只要创建好窗口,接下来的任何事情Windows操作系统都会代办。事实并非如此。每当用户移动另一个窗口遮蔽了一部分窗口,或对窗口最大,最小化和改变尺寸时,WM_PAINT消息不会发出,但必须由用户自行重绘。幸好Windows会自动计算需要重绘的区域。如果窗口只有一部分被覆盖后又要重显,Windows并不重绘整个窗口,而只画那些恢复为可见的部分。需要重绘的区域通常称为无效区(Invalid Region)或更新区(Update Region)。当一条WM_PAINT消息产生,在调用BeginPaint之后,Windows便知道哪些区域已经恢复为有效,所有相关的WM_PAINT消息(可能积累了数条)都会从消息队列中删去。

case WM_PAINT:

            {

                PAINTSTRUCT ps;

         

          BeginPaint (hwnd, &ps);

 

                 //**做一些制工作**

    

          EndPaint (hwnd, &ps);

            }

 

       break;

处理WM_PAINT消息的第一件事就是创建一个PAINTSTRUCT结构。BeginPaint使用此结构来传递重绘窗口所需的信息。下面是PAINTSTRUCT的定义:(默然:下面的代码Windows已经定义好了,并不需要键入)

typedef  struct  tagPAINTSTRUCT{

    HDC hdc;

    BOOL   fErase;

    RECT   rcPaint;

    BOOL   fRestore;

    BOOL   fIncUpdate;

    BYTE   rgbReserved[32];

}PAINTSTRUCT;

hdc:

是设备描述表(Device Context)句柄。在下一章里将开始频繁地使用设备描述表在窗口里绘图。

fErase

用来告诉应用程序创建窗口类时是否应该用指定的颜色重绘背景。如果fErase非零,背景就会被重绘。

rcPaint

是一个RECT结构,告诉应用程序哪个区域已经被无效了,需要重绘。RECT结构是一个非常简单的结构,定义了矩形两个角的坐标(lefttop描述左上角坐标,rightbottom描述右下角坐标),格式如下:

typedef  struct  _RECT{

       LONG       left;

       LONG       top;

       LONG       right;

       LONG       bottom;

}RECT;

其余的参数:

保留给Windows的,不用理会。

当调用BeginPaint时,PAINTSTRUCT结构ps已经被填好,而背景一般也已重绘好了。可以进行自定义绘图。最后调用EndPaint函数,以通知Windows已经绘图完毕。

1.3.4.3     WM_DESTROY消息

这条消息的含义为:用户要求关闭应用程序窗口。关闭应用程序窗口的做法是调用PostQuitMessage(0),后者产生一条WM_QUIT消息并置入消息队列。切记,在消息循环中使用到GetMessage函数在遇到WM_QUIT消息时会返回0.这样应用程序就停止了。

case WM_DESTROY:

            {

                // 消毁应用程序,这里发送了一个 WM_QUIT消息。

                PostQuitMessage (0);

            }

技巧:如果不用PostQuitMessage(0)来发出WM_QUIT消息,那么尽管应用程序的窗口会关闭,程序本身仍然在运行!一些应用程序就用了这种方法来缩到任务栏里去。

1.3.4.4     其余的消息

虽然已知道如何处理上述3条消息,但那些发往WindowProc而未处理的消息怎么处理?Windows有一个DefWindowProc函数,用它可以处理那些用户没有处理的消息。最后应这样从WindowProc返回:

return DefWindowProc (hwnd, msg, wParam, lParam);

技巧:尽管程序通常不直接调用窗口过程,但可以通过使用SendMessage函数主动把消息加入消息队列(从而由WindowProc处理)。关于SendMessage函数的更多细节请查阅Windows Api文档。

1.3.5           键盘输入

当按下某个键时,产生了一个WM_KEYDOWNWM_SYSKEYDOWN消息,再放开这一键时,则产生了一个WM_KEYUPWM_SYSKEYUP消息。而其中的WM_SYSKEYDOWNWM_SYSKEYUP消息的使用对Windows操作系统来说比对应用程序更为重要,这些消息是在用户按下诸如Alt+TabAlt+F4之类的键时产生的。一般情况下,程序员不必处理WM_SYSKEYUPWM_SYSKEYDOWN消息,只要让DefWindowProc处理它们就可以了。

对于所有这些消息,wParam包含了正被按下的键的虚拟键代码(Virtual Key Code),而lParam包含诸如重复次数,扫描码等附加信息。本书里没有用到任何lParam中的信息。

在过去,程序员们写程序时必须直接从键盘读取击键的代码。而每一种不同类型的键盘为每一个键产生不同的编码,这样就产生了混乱。幸运的是,Windows引入了虚拟键代码,解决了这个问题。表1.7列出了一些在游戏中经常用到的虚拟键代码。

虚拟键

描述

VK_RETURN

Enter

VK_ESCAPE

Escap

VK_SPACE

Space

VK_TAB

Tab

VK_BACK

Backspace

VK_UP

上键

VK_DOWN

下键

VK_LEFT

左键

VK_RIGHT

右键

VK_HOME

Home

VK_PRIOR

PageUp

VK_NEXT

PageDown

VK_INSERT

Insert

VK_DELETE

Delete

VK_SNAPSHOT

PrintScreen

若用户按下的是数字或字母键,则虚拟键代码就是该数字或字母的ASCII码。为了读取键盘的输入,只需要为WM_KEYDOWNWM_KEYUP消息编写处理代码就可以了,象在HelloWorld4例程中,就加入了如下一段代码,用来检测Escape键是否按下。如果检测到,就让应用程序结束。(默然:我用生命保证,HelloWorld4中,就只多了这一段代码!)

……

case WM_KEYUP:

{

    switch(wParam){

        case VK_ESCAPE:

        {

            //如果按下了Excape,就退出程序

            PostQuitMessage(0);

        }

    }

……

由上可以看到,首先检测到WM_KEYUP消息,然后根据此消息的wParam部分来创建一个switch语句,来区分其中保存的不同的虚拟键代码。如果检测到的虚拟键代码是Escape键的代码VK_ESCAPE,就调用方法PostQuitMessage(0)来发送WM_QUIT消息并终止应用程序。

另外有一种获取键盘信息的方法,就是使用GetKeyboardStateGetKeyStateGetAsyncKeyState3个函数中的任意一个。它们都是很有用的函数,特别是在游戏编程时更是如此。利用它们,你可以在程序的任何地方来测试按键,不再需要麻烦消息循环。其中GetAsyncKeyState可能是最为有用的,因为可简单地调用它来检查某个特定键是否处于按下状态。其原型如下:

SHORT GetAsyncKeyState(int vKey);//虚拟键

要检测某个键是否已按下,将该键的虚拟键码传给函数GetAsyncKeyState作为参数,并检查返回值的最高有效位(即最左的一位)是否等于1。例如,测试向左箭头键是否已被按下的代码如下:

if(GetAsyncKeyState(VK_LEFT) & 0x8000){

    //**行左移的代**

}

您好:
    当您在阅读和使用我所提供的各种内容的时候,我非常感谢,您的阅读已是对我最大的支持。
    我更希望您能给予我更多的支持。
    1.希望您帮助我宣传我的博客,让更多的人知道它,从中获益。
    2.希望您能多提出宝贵意见,包括我所提供的内容中的错误,建设性的意见,希望获得哪些方面的帮助,您的经验之谈等等。
    3.更希望能得到您经济上的支持。
   
    我博客上面的内容均属于个人的经验,所有的内容均为开源内容,允许您用于任何非商业用途,并不以付费为前提,如果您觉得在阅读和使用我所提供的各种内容的过程中,您得到了帮助,并能在经济上给予我支持,我将感激不尽。
   
    您可以通过银行转帐付款给我:
    招商银行一卡通:
    卡号:6225888712586894
    姓名:牟勇
   
    您也可以通过汇款的方式:
    通讯地址:云南省昆明市女子(28)中学人民中路如意巷1号
    收信人:陈谦转牟勇收
    邮编:650021
   
    无论您给予我怎么样的支持,我都衷心的再次感谢。
    欢迎光临我的博客,欢迎宣传我的博客
    http://blog.csdn.net/mouyong
    http://blog.sina.com.cn/mouyong
    EMail:mouyong@yeah.net