win32(3)--消息处理机制

来源:互联网 发布:淘宝新店怎么刷销量 编辑:程序博客网 时间:2024/05/02 13:25

Windows应用程序是消息驱动的

一、消息

消息系统对win32程序来说十分重要。一个消息,是系统定义的值,它定义了一个事件,向Windows系统发出一个通知告诉应用程序的某个事件发生了,例如鼠标点击,键盘按下,窗口尺寸改变等等都会使Windows系统发送消息给应用程序

二、消息结构体MSG

struct tagMSG {

    HWND        hwnd; // 窗口句柄

   UINT        message; // 消息ID(系统指定的宏)

   WPARAM      wParam;// 附加消息

   LPARAM      lParam; // 附加消息

   DWORD       time; // 投递到消息队列的时间

   POINT       pt;   //投递到到队列时的位置

} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

三、消息队列

当windows应用程序开始执行的时候,系统会为当前应用程序创建一个消息队列用来存放消息,我们可以通过GetMessage去得到消息,然后由消息处理函数处理消息。

过程:事件–> 封装成消息 —> 投递到消息队列 –> 消息响应(消息处理)

1)系统消息队列:由系统维护、存放的是系统消息,如鼠标键盘的信息。设备驱动(mouse, keyboard)会把操作输入转化成消息存在系统队列中,然后系统会把此消息放到目标窗口所在的线程的消息队列中等待处理。

2)线程消息队列;存放的是当前应用程序的消息。每一个GUI线程都会维护这样一个线程消息队列(这个队列只有在线程调用GDI函数时才会创建,默认不创建)。然后线程消息队列中的消息会被送到相应的窗口过程(WndProc)处理(窗口消息处理函数)

四、消息分类

1)系统消息

系统已定义好的消息(0~0~0x3FF

大致分为三类

1.窗口消息:与窗口的内部运作有关,比如创建窗口、绘制窗口、销毁窗口等。

    2.命令消息:与处理用户请求有关,比如点击菜单、工具栏、按钮控件等。

    3.控件通知:这个比较灵活,其Message, wParam, lParam分别为:WM_NOTIFY, 控件ID,指向NMHDR的指针。NMHDR包含控件通知的内容, 可以任意扩展。

2)自定义消息

用户自己定义的,操作系统不知道,用户自行处理(0x400~0x7FFF)(也就是1024-32767),通常用WM_USER + n 的方法定义(#define WM_USER 0x400)

3)队列消息和非队列消息

消息的获取和发送都在队列中即是队列消息,如鼠标键盘消息,WM_PAINT;不进队列直接给消息处理函数为非队列消息,例如

WM_CREATE,WM_SIZE。


GetMessage函数:从消息队列中获取消息,是个堵塞函数,如果没有得到消息一直等待,直到获取到消息。如果有消息则从消息队列

中获取放入MSG中,然后从消息队列删除该消息,正常情况返回非0;WM_QUIT消息到来的时候返回0;错误返回-1;

所以:MSDN上这样写:

BOOL bRet; //#define int BOOL 大写的BOOL 就是int,和小写bool不同

while( (bRet = GetMessage( &msg, hWnd, 0, 0 )) != 0){

if (bRet == -1)   {

// handle the error and possibly exit

}

else {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

}

 

PeekMessage函数:从消息队列中获取消息,非堵塞函数,如果没有消息直接返回0,有消息就返回非0。

BOOL WINAPI PeekMessage(

_Out_    LPMSG lpMsg, //消息结构体指针

_In_opt_ HWND  hWnd, //窗口句柄,一般写NULL

_In_     UINT  wMsgFilterMin, //最小消息ID,一般写0

_In_     UINT  wMsgFilterMax,//最大消息ID,一般写0,上下都是0表示不过滤

_In_     UINT  wRemoveMsg //是否要移除该消息

);

最后一个参数:

PM_NOREMOVE:执行PeekMessage后不从消息队列删除该消息

PM_REMOVE:执行完PeekMessage后从消息队列删除该消息

PM_NOYIELD:Prevents the system from releasing any thread that is waiting for the caller to go idle (see WaitForInputIdle).

Combine this value with either PM_NOREMOVE or PM_REMOVE.

利用PeekMessage函数,我们可以把消息循环改写一下,可以利用这个死循环做些别的事情。

while (1){

if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {

if (GetMessage(&msg, NULL, 0, 0)) {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

else break;

}

//这里可以做点其他的什么事情

}

SendMessage和PostMessage(PostThreadMessage):

LRESULT WINAPI SendMessage(

_In_ HWND   hWnd,

_In_ UINT   Msg,

_In_ WPARAM wParam,

_In_ LPARAM lParam

);

BOOL WINAPI PostMessage(

_In_opt_ HWND   hWnd,

_In_     UINT   Msg,

_In_     WPARAM wParam,

_In_     LPARAM lParam

);

 

SendMessage:直接把消息送到窗口消息处理函数中处理,处理完了才返回,也就是阻塞函数。(是不是进消息队列要看情况而定)

PostMessage:把消息放到指定窗口所在的线程消息队列中后立即返回。

PostThreadMessage:把消息放到指定线程的消息队列中后立即返回。

两者的区别:

1、PostMessage 是异步的,SendMessage 是同步的。

PostMessage 只把消息放到队列,不管消息是不是被处理就返回,消息可能不被处理;SendMessage等待消息被处理完了才返回,

如果消息不被处理,发送消息的线程将一直处于阻塞状态,等待消息的返回。

2、同一个线程内:

SendMessage 发送消息时,由USER32.DLL模块调用目标窗口的消息处理程序,并将结果返回,SendMessage 在同一个线程里面发

送消息不进入线程消息队列;PostMessage 发送的消息要先放到消息队列,然后通过消息循环分派到目标窗口(DispatchMessage)。

3、不同线程:

SendMessage 发送消息到目标线程的消息队列,然后发送消息的线程在USER32.DLL模块内监视和等待消息的处理结果,直到目标窗口

的才处理返回。PostMessge()到别的线程的时候最好使用PostThreadMessage()代替。PostMessage()的HWND 参数可以为NULL,相

当于PostThreadMessage() + GetCrrentThreadId。

4、系统处理消息:

使用PostMessage,SendNotifyMessage,SendMessageCallback等异步函数发送系统消息时,参数不可以使用指针,因为发送者不等

待消息的处理就返回,接收者还没有处理,指针就有可能被释放了,或则内容变化了。

TranslateMessage(转换消息):

用来把虚拟键消息转换为字符消息。由于Windows对所有键盘编码都是采用虚拟键的定义,这样当按键按下时,并不是字符消息,需要

键盘映射转换为字符的消息。转换后的字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出。

TranslateMessage(&msg){

  if(没有键盘消息)

return ;

  if(是否是可见字符){

  if(lock键盘是否按下){

    Postmessage(键盘按下的大写字母消息);

}

else{

Postmessage(键盘按下的小写字母消息);

}

}

}

DispatchMessage(派发消息):

把消息发送到窗口的消息处理函数。


零碎知识:在Window窗口程序中加一个DOS窗口。

一种方法:

//该代码写在所有的.h后面,可以让窗口程序带个dos窗口,方便调试

//注意 UNICODE的启动函数是 wWinMainCRTStartup,MBCS是WinMainCRTStartup(没有w)

#ifdef UNICODE

#pragma comment(linker,”/subsystem:\”console\” /entry:\”wWinMainCRTStartup\””)

#else

#pragma comment(linker,”/subsystem:\”console\” /entry:\”WinMainCRTStartup\””)

#endif // UNICODE

 

第二种方法:

AllocConsole();

HANDLE g_hand = GetStdHandle(STD_OUTPUT_HANDLE);

WriteConsole(g_hand, _T(“显示的内容”),_tcslen(_T(“显示的内容”)), NULL, NULL);

小练习:做一个窗口,要动态光标,点击鼠标左键换一个图标

#include <Windows.h>#include <tchar.h>HCURSOR MCur[2];HINSTANCE HInstance;//5.消息处理LRESULT WinProc(HWND hWnd, UINT msg, WPARAM wparam, LPARAM lparam){switch (msg){case WM_CREATE:MCur[0] = (HCURSOR)LoadImage(HInstance, _T("ani1.ani"), IMAGE_CURSOR, 0, 0, LR_LOADFROMFILE);MCur[1] = (HCURSOR)LoadImage(HInstance, _T("ani2.ani"), IMAGE_CURSOR, 0, 0, LR_LOADFROMFILE);break;case WM_LBUTTONDOWN: {int i = 0;i = (++i) % 2;SetCursor(MCur[i]);break; }case WM_DESTROY: PostQuitMessage(0);break;}return DefWindowProc(hWnd, msg, wparam, lparam);}int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE phInstance,LPSTR lpCmdLine,int nCmdShow){HInstance = hInstance;//1.定义窗口对象WNDCLASS wc; // EXwc.cbClsExtra = NULL;wc.cbWndExtra = NULL; wc.hInstance = hInstance; wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc.hCursor = NULL;wc.hIcon = NULL; wc.lpfnWndProc = (WNDPROC)WinProc; wc.lpszClassName = _T("WA");wc.lpszMenuName = NULL;wc.style = CS_HREDRAW;  if (!RegisterClass(&wc)){MessageBox(NULL, _T("ERROR"), _T("ROORO"), MB_OK);return 0;}HWND hWnd = CreateWindow(_T("WA"), _T("啦啦啦"), WS_OVERLAPPEDWINDOW,200, 200, 300, 400, NULL, NULL, hInstance, NULL);if (!hWnd){MessageBox(NULL, _T("ERROR"), _T("ROORO"), MB_OK);return 0;}ShowWindow(hWnd, SW_SHOW);UpdateWindow(hWnd);MSG msg = { 0 };while (GetMessage(&msg, NULL, 0, 0)){TranslateMessage(&msg);DispatchMessage(&msg);}UnregisterClass(_T("WA"), hInstance);return 0;}



原创粉丝点击