windows操作系统最大的特点就是其图形化的操作界面,该界面是建立在消息处理机制的基础上的。windows程序的本质是借助消息来驱动的,程序不断等待,等待任何可能的输入,然后判断,根据不同的消息调用消息处理函数进行适当的处理。这种输入是操作系统捕捉到后以消息形式(一种数据结构)进入到程序中。
消息的基本概念
对于一个win32程序来说,消息机制十分重要,它是一个程序的动力源泉。一个消息是系统定义的一个32位的值,它定义了一个事件,当事件发生时,向windows发出一个通知,告诉应用程序某个事情发生了。例如,单机鼠标,改变窗口尺寸,按下键盘上的一个键都会使windows发送一个消息给应用程序。
windows操作系统中包括以下几种消息:
- 标准windows消息。这种消息以WM_开头。
- 通知消息。通知消息是针对标准windows控件的消息。这些控件包括:按钮(Button),组合框(ComboBox),编辑框(TextBox)、列表框(ListBox)、ListView控件、Treeview控件、工具条(Toolbar)、菜单(Menu)等。每种消息以不同的字符串打头。
- 自定义消息。编程人员还可以自定义消息,这种消息一般在WM_USER基础标识之上增加一个不会冲突的消息标识
消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。消息的记录类型被定义为MSG,MSG含有来自Windows应用程序消息队列的消息信息,它在Windows中声明如下:
typedef struct tagMsg{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG
其中:
- hwnd:该接收消息的窗口句柄
- message:消息常量标识符
- wParam:32位消息的特定附加信息,确切含义依赖于消息值
- lParam:32位消息的特定附加消息,确切含义依赖于消息值
- time:消息创建时间
- pt:消息创建时鼠标/光标在屏幕坐标系中的位置
消息可以由消息或应用程序产生,消息产生后,系统会根据消息做出响应。
windows的消息由三个部分组成:
- 消息队列,Windows能够为所有的应用程序维护一个消息队列。应用程序必须从消息队列中获取消息,然后分派给某个窗口。
- Windows能够为所有的应用程序维护一个消息队列。应用程序必须从消息队列中获取消息,然后分派给某个窗口。
窗口过程。每个窗口都有一个窗口过程函数来接收传递给窗口的消息,它的任务就是获取消息然后响应它。窗口过程是一个回调函数,处理了一个消息后,它通常要返回一个值给Windows。(注意回调函数是程序中的一种函数,它由Windows或外部模块调用。)
SDK下的消息机制实现
为了能够支持消息的实现,需要窗口来提供消息队列以接收特定的消息,需要窗口过程来响应特定的消息,需要消息循环来检索消息。
一个消息从产生到被一个窗口响应涉及5个步骤:系统中发生了某个事件;
- Windows把这个事件翻译为消息,然后把它放到消息队列中
- 应用程序从消息队列中接收到这个消息,把它存放在TMsg记录中
- 应用程序把消息传递给一个适当的窗口过程;
- 窗口过程响应这个消息并进行处理
步骤3和步骤4构成了应用程序的消息循环。消息循环往往是Windows应用程序的核心,因为消息循环使一个应用程序能够响应外部的事件。消息循环的任务就是从消息队列中检索消息,然后把消息传递给适当的窗口。
在Win32SDK中提供了与消息实现相关的一系列方法,具体而言,主要函数有以下几种。
注册窗口函数:RegisterClass()和RegisterClassEx()
函数RegisterClass()和RegisterClassEx()注册窗口类,该窗口类会在随后调用CreateWindow()函数或CreateWindowEx()函数中使用。两个函数的原型定义如下:
ATOM RegisterClass(
CONST WNDCLASS *lpWndClass
);
ATOM RegisterClassEx(
CONST WNDCLASSEX *Ipwcx
);
其中,参数lpWndClass或Ipwcx指向一个WNDCLASS结构的指针。在将它传递给函数之前,必须在该结构中填充适当的类属性。
如果函数成功,返回值是唯一标识已注册类的一个原子;如果函数失败,返回值为0。若想获得更多错误信息,可以调用GetLastError()函数获得错误号。如果函数成功,返回值是唯一标识已注册类的一个原子;如果函数失败,返回值为0。若想获得更多错误信息,可以调用GetLastError()函数获得错误号。
创建窗口函数:CreateWindow()和CreateWindowEx()
函数CreateWindow()和CreateWindowEx()负责创建窗口,窗口可能是一个重叠式窗口、弹出式窗口或子窗口。两个函数的原型定义如下:
HWND CreateWindow(
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
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
);
这两个函数负责创建窗口,指定窗口类、窗口标题、窗口风格以及窗口的初始位置及大小(可选的)。函数也指示该窗口的父窗口或所属窗口(如果存在的话)及窗口的菜单。若要使用除CreateWindow()函数支持的风格以外的扩展风格,则使用CreateWindowEx()函数代替CreateWindow()函数。其中:
- lpClassName:指向注册窗口类名的指针,由之前调用的RegisterClass()函数或RegisterClassEx()函数创建。
- lpWindowName:指向一个指定窗口名的空结束的字符串指针。如果窗口有标题条,则窗口名称显示在标题条中。当使用CreateWindow()函数创建控件时,可使用lpWindowName来指定控制文本。
- dwStyle:指定创建窗口的风格。该参数可以是下列窗口风格的组合:
- WS_BORDER:创建一个单边框的窗口;
- WS_CAPTION:创建一个有标题条的窗口(包括WS_BODER风格);
- WS_CHILD:创建一个子窗口,这个风格不能与WS_POPUP风格合用;
- WS_CHLDWINDOW:与WS_CHILD样式相同;
- WS_CLIPCHILDREN:在创建父窗口时使用该样式,当在父窗口中执行绘制操作时,并不绘制子窗口占用的区域;
- WS_CLIPSIBLINGS:当两个窗口相互重叠时,设置了该样式的子窗口重绘时不能绘制被重叠的部分;
- WS_DISABLED:创建一个初始状态为禁止的子窗口,一个禁止状态的窗口不能接收来自用户的输入信息;
- WS_DLGFRAME:创建一个带对话框边框风格的窗口,这种风格的窗口不能带标题条;
- WS_GROUP:指定一组控件的第一个控件;
- WS_HSCROLL:创建一个有水平滚动条的窗口;
- WS_ICONIC:创建一个初始状态为最小化状态的窗口,与WS_MINIMIZE风格相同;
- WS_MAXIMIZE:创建一个初始状态为最大化状态的窗口;
- WS_MAXIMIZEBOX:创建一个带有最大化按钮的窗口,该风格不能与WS_EX_CONTEXTHELP风格同时出现,同时必须指定WS_SYSMENU风格;
- WS_OVERLAPPED:创建一个层叠的窗口,一个层叠的窗口有一个标题条和一个边框,与WS_TILED风格相同;
- WS_OVERLAPPEDWINDOW:创建一个具有WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_THICKFRAME、WS_MINIMIZEBOX、WS_MAXIMIZEBOX风格的层叠窗口,与WS_TILEDWINDOW风格相同;
- WS_POPUP:创建一个弹出式窗口;
- WS_POPUPWINDOW:创建一个具有WS_BORDER、WS_POPUP、WS_SYSMENU风格的窗口,WS_CAPTION和WS_POPUPWINDOW必须同时设定才能使窗口控制菜单可见;
- WS_SIZEBOX:创建一个可调边框的窗口,与WS_THICKFRAME风格相同;
- WS_SYSMENU:创建一个在标题条上带有窗口菜单的窗口,必须同时设定WS_CAPTION风格;
- WS_TABSTOP:创建一个控件,这个控件在用户按下Tab键时可以获得键盘焦点;
- WS_THICKFRAME:创建一个具有可调边框的窗口,与WS_SIZEBOX风格相同;
- WS_TILED:创建一个层叠的窗口,与WS_OVERLAPPED风格相同;
- WS_TILEDWINDOW:创建一个具有WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_THICKFRAME、WS_MINIMIZEBOX和WS_MAXMIZEBOX风格的层叠窗口,与WS_OVERLAPPEDWINDOW风格相同;
- WS_VISIBLE:创建一个初始状态为可见的窗口;
- WS_VSCROLL:创建一个有垂直滚动条的窗口。
- X:指定窗口的初始水平位置。
- Y:指定窗口的初始垂直位置。
- nWidth:指定窗口的宽度。
- nHeight:指定窗口的高度。
- hWndParent:指向被创建窗口的父窗口或所有者窗口的句柄。
- hMenu:菜单句柄,或依据窗口风格指明一个子窗口标识。
- hlnstance:与窗口相关联的模块实例的句柄。
- lpParam:指向一个值的指针,该值传递给窗口WM_CREATE消息。
CreateWindowEx()函数与CreateWindow()函数相比,增加了窗口的扩展样式,用dwExStyle参数来指明。
如果函数成功,返回值为新窗口的句柄,如果函数失败,返回值为NULL。若想获得更多错误信息,可以调用GetLastError()函数获得错误号。
接收消息函数:GetMessage()、PeekMessage()和WaitMessage()
GetMessage()函数从调用线程的消息队列里取得一个消息并将其放于指定的结构。该函数的原型定义如下:
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax
);
此函数可取得与指定窗口联系的消息和由PostThreadMesssge()寄送的线程消息。此函数接收一定范围的消息值。GetMessage()不接收属于其他线程或应用程序的消息。获取消息成功后,线程将从消息队列中删除该消息。函数会一直等待直到有消息到来才有返回值。
其中:
● lpMsg:指向MSG结构的指针,该结构从线程的消息队列里接收消息信息。
● hWnd:取得其消息的窗口的句柄。当其值取NULL时,GetMessage()为任何属于调用线程的窗口检索消息,线程消息通过PostThreadMessage()寄送给调用线程。
● wMsgFilterMin:指定被检索的最小消息值的整数。
● wMsgFilterMax:指定被检索的最大消息值的整数。
如果函数取得WM_QUIT之外的其他消息,返回非零值。如果函数取得WM_QUIT消息,返回值是零。如果出现了错误,返回值是-1。若想获得更多错误信息,可以调用GetLastError()函数获得错误号。
除了GetMessage()函数外,PeekMessage()函数和WaitMessage()函数也可以用于消息的接收。
PeekMessage()函数用于查看应用程序的消息队列,该函数的原型定义如下:
BOOL PeekMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax,
UINT wRemoveMsg
);
如果消息队列中有消息就将其放入lpMsg所指的结构中,不过,与GetMessage()不同的是,PeekMessage()函数不会等到有消息放入队列时才返回。同样,如果hWnd为NULL,则PeekMessage()获取属于调用该函数应用程序的任一窗口的消息,如果hWnd=-1,那么函数只返回把hWnd参数为NULL的PostAppMessage()函数送去的消息。如果wMsgFilterMin和wMsgFilterMax都是0,则PeekMessage()就返回所有可得到的消息。函数获取之后将删除消息队列中的除WM_PAINT消息之外的其他消息,而WM_PAINT则在其处理之后才被删除。
其中:
● lpMsg:指向MSG结构的指针,该结构从线程的消息队列里接收消息信息。
● hWnd:其消息被检查的窗口句柄。
● wMsgFilterMin:指定被检索的最小消息值的整数。
● wMsgFilterMax:指定被检索的最大消息值的整数。
● wRemoveMsg:确定消息如何被处理。此参数可取下列值之一:
- PM_NOREMOVE:PeekMessage()处理后,消息不从队列里除掉;
- PM_REMOVE:PeekMessage()处理后,消息从队列里除掉;
- PM_NOYIELD:可将该参数随意组合到PM_NOREMOVE或PM_REMOVE,此标志使系统不释放等待调用程序空闲的线程;
- PM_QS_INPUT:Windows NT5.0和Windows98,只处理鼠标和键盘消息;
- PM_QS_PAINT:Windows NT5.0和Windows98,只处理画图消息;
- PM_QS_POSTMESSAGE:Windows NT5.0和Windows98,只处理所有被寄送的消息,包括计时器和热键;
- PM_QS_SENDMESSAGE:Windows NT5.0和Windows98,只处理所有发送消息。
如果消息可得到,函数PeekMessage()的返回为非零值;如果没有消息可得到,返回值是零。
当一个应用程序无事可做时,WaitMessage()函数将控制权交给另外的应用程序,同时将该应用程序挂起,直到一个新的消息被放入应用程序的队列之中才返回。该函数的原型定义如下:
BOOL WaitMessage(VOID);
如果函数调用成功,返回非零值;如果函数调用失败,返回值是0。若想获得更多错误信息,可以调用GetLastError()函数获得错误号。
转换消息函数:TranslateMessage()
TranslateMessage()函数将虚拟键消息转换为字符消息。字符消息被寄送到调用线程的消息队列里,在下一次线程调用函数GetMessage()或PeekMessage()时被读出。该函数的原型定义如下:
BOOL TranslateMessage(
const MSG *lpMsg
);
其中,lpMsg指向含有消息的MSG结构的指针,该结构里含有用函数GetMessage()或PeekMessage()从调用线程的消息队列里取得的消息信息。
如果消息被转换(即字符消息被寄送到调用线程的消息队列里),返回非零值。如果消息是WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN或WM_SYSKEYUP,返回非零值,不考虑转换。如果消息没被转换(即字符消息没被寄送到调用线程的消息队列里),返回值是0。
分发消息函数:DispatchMessage()
DispatchMessage()函数分发一个消息给窗口程序。该函数的原型定义如下:
LRESULT DispatchMessage(
const MSG *lpmsg
);
其中,lpmsg指向含有消息的MSG结构的指针。
返回值是窗口程序返回的值。尽管返回值的含义依赖于被调度的消息,但返回值通常被忽略。
在一个典型的Win32程序的主体中,通常使用while语句实现消息循环。程序通过GetMessage()函数从与某个线程相对应的消息队列里面把消息取出来,放到类型为MSG的消息变量msg中;然后TranslateMessage()函数把消息转化为字符消息,并存放到相应的消息队列里面;最后DispatchMessage()函数把消息分发到相关的窗口过程去处理。窗口过程根据消息的类型对不同的消息进行相关的处理。在SDK编程过程中,用户需要在窗口过程中分析消息的类型及其参数的含义。
MFC下消息实现
MFC类库是一套Windows下C++编程的最为流行的类库。MFC的框架结构合理地封装了Win32API函数,并设计了一套方便的消息映射机制。在MFC的框架结构下,“消息映射”是通过巧妙的宏定义形成一张消息映射表格来进行的。这样一旦消息发生,框架就可以根据消息映射表格来进行消息映射和命令传递。
在MFC下建立消息映射和消息处理的通常步骤如下:
1)定义消息。Microsoft推荐用户自定义消息至少是WM_USER+100,因为很多新控件也要使用WM_USER消息,示例如下:
#define WM_MYMESSAGE (WM_USER + 100)
2)实现消息处理函数。该函数使用WPRAM和LPARAM参数,并返回LPESULT。
LPESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
// TODO: 处理用户自定义消息,填空就是要填到这里。
return 0;
}
3)在类的头文件的AFX_MSG块中说明消息处理函数
// {{AFX_MSG(CMainFrame)
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
// }}AFX_MSG
DECLARE_MESSAGE_MAP()
4)在用户类的消息块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中
ON_MESSAGE( WM_MYMESSAGE, OnMyMessage )