一 、小记;
PostQuitMessage(0);
产生WM_QUIT消息给进程队列,且立即返回,同时使得消息循环退出,使得进程终止。(其实它通过PostMessage(hWnd,WM_QUIT,0,0)发送消息)
MoveWindow();//移动窗口
BOOL MoveWindow(
HWND hWnd, // 窗口句柄
int X, // 水平位置
int Y, // 垂直位置
int nWidth, // 宽度
int nHeight, // 高度
BOOL bRepaint // 重绘标识
);
二 、消息机制
1.程序执行机制:
过程驱动:程序的执行是有序的,按照预先预定义的顺序执行。
事件驱动:程序执行是无序的,用户可以按照需要随机触发相应的事件。
Win32窗口程序就是按照事件驱动方式执行,即消息机制。当系统通知窗口时,采用消息的方式派发给窗口。
每个窗口都要有个消息处理函数。
当系统通知窗口时,会调用窗口处理函数同时,将消息ID和消息参数传递给窗口处理函数。
在窗口处理函数中,不处理的消息,使用缺省窗口处理函数,例如DefWindowProc 。
2.消息相关函数
回调机制:程序员自己定义的函数,但交给操作系统调用。
获取消息:
BOOL GetMessage(
LPMSG lpMsg, //存放获取到的消息BUFF
HWND hWnd, //窗口句柄
UINT wMsgFilterMin, //获取消息的最小ID范围
UINT wMsgFilterMax //获取消息的最大ID范围
);
lpMsg - 当获取到消息后,将消息的参数存放到MSG结构中。
hWnd - 获取hWnd所指定窗口的消息,若NULL,则获取所有消息。
wMsgFilterMin和wMsgFilterMax -只能获取到由它们指定的消息范围内的消息,如果都为0,表示没有范围。
若接受的消息为WM_QUIT,则返回值为0,则退出消息循环。
GetMessage - 从系统获取消息,将消息从系统中移除,阻塞函数。当系统无消息时,GetMessage会等候下一条消息。
PeekMessage - 以查看的方式从系统获取消息,可以不将消息从系统移除,非阻塞函数。当系统无消息时,返回FALSE,继续执行后续代码。
使用PeekMessage()可以在不锁住我们的程序的前提下对消息进行检查。许多程序使用GetMessage(),也可以很好的工作。但使用GetMessage(),程序在收到paint消息或其他别的什么窗口消息之前不会做任何事。
翻译消息:将按键消息,翻译成字符消息。
BOOL TranslateMessage(
CONST MSG *lpMsg //要翻译的消息地址
);
检查消息是否是按键的消息,若是,则产生一个字符消息。如果不是按键消息,不做任何处理,继续执行。
派发消息: 将消息派发到该消息所属窗口的窗口处理函数上。
DispatchMessage(&msg)
{
- 获取消息是属于本进程哪个窗口(&msg)->hWnd;
- 根据窗口句柄获取内存中的信息 如:类名
- 将类名与操作系统中去匹配 窗口类名
- 若匹配,调用被匹配的哪个窗口类中存的函数(消息处理函数)。
- 将其中的4个参数一一传递给处理函数。
}
3.常用Windows消息
WM_DESTROY - 窗口被销毁时的消息,无消息参数。常用于在窗口被销毁之前,做相应的善后处理,例如资源、内存等。
WM_SYSCOMMAND - 系统命令消息,当点击窗口的最大化、最小化、关闭等命令时,收到这个消息。
常用在窗口关闭时,提示用户处理。
wParam - 具体命令,例如关闭SC_CLOSE等.( wParam==SC_CLOSE)
lParam - 鼠标位置
LOWORD 低字 - 水平位置 (LOWORD(lParam))
HIWORD 高字 - 垂直位置 (HIWORD(lParam))
WM_CREATE - 在窗口创建成功还未显示之前,收到这个消息。
常用于初始化窗口的参数、资源等等,包括创建子窗口等。
WPARAM - 不使用,LPARAM - 是CREATESTRUCT结构的指针,保存了CreatWindowEx中的12个参数。若使用它,注意要强制转换类型。(CREATESTRUCT*)lParam。
WM_SIZE --常用于窗口大小变化后,调整窗口内各个部分的布局
WPARAM - 窗口大小变化的原因,LPARAM-变化窗口客户区的大小LOWORD - 变化后的宽度 (LOWORD(lParam))
HIOWORD- 变化后的高度 (HIWORD(lParam))
WM_QUIT - 用于结束消息循环处理。
wParam - PostQuitMessage函数传递的参数,lParam -不使用。
当GetMessage收到这个消息后,会返回FALSE,结束while处理,退出消息循环。
WM_PAINT - 绘图消息
键盘消息
鼠标消息
定时器消息
4.消息的发送(仅此两种,其他形式的发送消息都是基于这两种)
SendMessage - 发送消息,会等候消息处理的结果。
PostMessage - 投递消息,消息发出后立刻返回,不等候消息执行结果。
LRESULT SendMessage / BOOL PostMessage(
HWND hWnd,//消息发送的目的窗口
UINT Msg, //消息ID
WPARAM wParam, //消息参数
LPARAM lParam //消息参数
);
5.消息的分类
1 系统消息 - ID范围 0 - 0x03FF (0~1023)
由系统定义好的消息,可以在程序中直接使用。
2 用户自定义消息 - ID范围 0x0400 - 0x7FFF (31743个)
由用户自己定义,满足用户自己的需求。由用户自己发出消息,并响应处理。
自定义消息宏:WM_USER (其实已定义为0x0400)
3 应用程序消息 - ID范围 0x8000 - 0xBFFF
程序之间通讯时使用的消息。
应用程序消息宏:WM_APP
4 系统注册消息 - ID范围 0xC000 - 0xFFFF
在系统注册并生成相应消息,然后可以在各个程序中使用这个消息。
用户使用自定义消息时:
#define WM_MYMESG WM_USER+N (0<N<=31743)
6.消息队列
消息队列用于存放消息的一个队列,消息在队列中先入先出。所有窗口程序都具有消息队列。程序可以从队列中获取消息。
消息队列的类型
系统消息队列-由系统维护的消息队列。存放系统产生的消息,例如鼠标、键盘等。
程序消息队列-属于每一个应用程序(线程)的消息队列。由应用程序(线程)维护。
消息队列的关系
1 当鼠标、键盘产生消息时,会将消息存放到系统消息队列
2 系统会根据存放的消息,找到对应窗口的消息队列。
3 将消息投递到程序的消息队列中。
操作系统如何将一个消息正确转发?
- 将系统消息队列中的消息提取出来。
- 根据消息的hWnd,获取内存。
- 在内存中获取“Main”。
- 拿着“Main”去操作系统中去匹配窗口类名。
- 一旦匹配到窗口类,可以确定这窗口类的句柄g_hInstance。
- 将消息转发给相应的窗口程序的消息队列中。
7.消息和消息队列
根据消息和消息队列之间使用关系,将消息分成两类:
队列消息 - 消息的发送和获取,都是通过消息队列完成。
非队列消息 - 消息的发送和获取,是直接调用消息的窗口处理完成。
队列消息 - 消息发送后,首先放入队列,然后通过消息循环,从队列当中获取。
GetMessage - 从消息队列中获取消息
PostMessage - 将消息投递到(系统的)消息队列,后直接返回,不管对方是否获取消息并成功执行。以后交给操作系统转发给相应的窗口的消息队列,此后交给GetMessage()去获取。
常见队列消息:WM_PAINT、键盘、鼠标、定时器。
非队列消息 - 消息发送时,首先查找消息接收窗口的窗口处理函数,直接调用处理函数,完成消息,(不用排队,且无需Getmessage()去自己的消息队列去获取消息)。
SendMessage - 直接将消息发送给窗口的处理函数,并等候处理结果。
常见消息:WM_CREATE、WM_SIZE等。
如何找到相应的处理函数呢?(类似于DispatchMessage())
- 根据hWnd,找到内存。
- 在内存中获取 “Main”。
- 拿着“Main”在操作系统中去匹配窗口类。
- 匹配成功,具体的窗口类。
- return 那个窗口类中的处理函数(WndProc)
8.消息获取:GetMessage /PeekMessage次序
1 .在程序(线程)消息队列查找消息,如果队列有消息,检查消息是否满足指定条件(HWND,ID范围),不满足条件就不会取出该消息,否则从队列取出消息返回。
2 .如果程序(线程)消息队列没有消息,向系统消息队列获取属于本程序的消息。如果系统队列的当前消息属于本程序,系统会将消息转发到程序
3 .如果系统消息队列也没有消息,检查当前窗口的需要重新绘制的区域,如果发现有需要绘制的区域,产生WM_PAINT消息,取得消息返回处理。
4 .如果没有重新绘制区域,检查定时器如果有到时的定时器,产生WM_TIMER,返回处理执行。
5 .如果没有到时的定时器,整理程序的资源、内存等等。
6 .GetMessage会继续等候下一条消息。PeekMessage会返回FALSE,交出程序的控制权。
注意:GetMessage如果获取到是WM_QUIT,函数会返回FALSE。
9. WM_PAINT - 绘图消息
WM_PAINT -- 当窗口需要绘制的时候,会发送窗口处理函数。
窗口无效区域:
被声明成需要重新绘制的区域。
BOOL InvalidateRect(
HWND hWnd, //窗口句柄
CONST RECT* lpRect, //区域的矩形坐标
BOOL bErase //重绘前是否先擦除
);
在程序中,如果需要绘制窗口,调用函数声明窗口无效区域。
WM_PAINT参数:WPARAM – 设备上下文,LPARAM - 不使用。
消息处理步骤:
1 开始绘图处理
HDC BeginPaint(
HWND hwnd, //绘图窗口
LPPAINTSTRUCT lpPaint //绘图参数的BUFF
); 返回绘图设备句柄HDC
2 绘图
3 结束绘图处理
BOOL EndPaint(
HWND hWnd, //绘图窗口
CONST PAINTSTRUCT *lpPaint //绘图参数的指针BeginPaint返回
);
10.键盘消息
- 1 键盘消息
WM_KEYDOWN - 按键被按下时产生
WM_KEYUP - 按键被放开时产生
WM_SYSKEYDOWN - 系统键按下时产生 比如ALT、F10等
WM_SYSKEYUP - 系统键放开时产生
WM_CHAR - 字符消息
- 2 消息参数
按键消息:
WPARAM - 按键的Virtual Key
LPARAM - 按键的参数,例如按下次数
WM_CHAR消息:
WPARAM - 输入的字符
LPARAM - 按键的相关参数
1 KEYDOWN可以重复出现,KEYUP只能在按键松开时出现1次。
2 TranslateMessage在转换WM_KEYDOWN消息时,对于可见字符可以产生WM_CHAR,不可见字符无此消息。
过程:
1、先判断是否是WM_KEYDOWN消息(msg.message==WM_KEYDOWN)。若是进入2,否则返回。
2、判断是否是可见字符(通过msg.wParam(虚拟键码),判断是否为可见字符按键),若不是,则返回。若是进入3。
3、产生可见字符的WM_CHAR消息给操作系统,然后操作系统再转发给窗口的消息队列,再通过GetMessage()抓取,此时跳过TranslateMessage(),由DispatchMessage()分派消息给窗口的处理函数。(判断Caps Lock按键是否打开,根据Caps Lock按键的状态将虚拟键码转换为ASCII码)。(其实对于可见)
3 WM_KEYDOWN/UP的wParam是表示的按键,WM_CHAR是表示输入的字符。
11.鼠标消息
1 基本鼠标消息
WM_LBUTTONDOWN - 鼠标左键按下
WM_LBUTTONUP - 鼠标左键抬起
WM_RBUTTONDOWN - 鼠标右键按下
WM_RBUTTONUP - 鼠标右键抬起
WM_MOUSEMOVE - 鼠标移动消息
2 双击消息
WM_LBUTTONDBLCLK - 鼠标左键双击
WM_RBUTTONDBLCLK - 鼠标右键双击
3 滚轮消息
WM_MOUSEWHEEL - 鼠标滚轮消息
基本鼠标消息:
消息参数
WPARAM - 其他按键的状态,例如Ctrl/Shift等
LPARAM - 鼠标的位置,窗口客户区坐标系。
LOWORD X坐标位置
HIWORD Y坐标位置
鼠标消息使用
一般情况鼠标按下/抬起成对出现。在鼠标移动过程中,会根据移动速度产生一系列的WM_MOUSEMOVE消息。
双击消息:
消息参数:
WPARAM - 其他按键的状态,例如Ctrl/Shift等
LPARAM - 鼠标的位置,窗口客户区坐标系。
LOWORD X坐标位置
HIWORD Y坐标位置
使用时需要在注册窗口类的时候添加CS_DBLCLKS 风格,否则不会有(鼠标左右键)双击效果。
消息产生的先后顺序:
以WM_LBUTTONDBLCLK为例:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONUP
滚轮消息:
消息参数
WPARAM:
LOWORD - 其他按键的状态
HIWORD - 滚轮的偏移量,是120的倍数,通过正负值表示表示滚动方向。
正:向前滚动
负:向后滚动
LPARAM:
鼠标当前的位置,屏幕坐标系(注意不是当前窗口的坐标系)
LOWORD - X坐标
HIWORD - Y坐标
通过偏移量,获取滚动的方向和倍数。
12.定时器消息:
定时器消息(WM_TIMER):
可以在程序中设置定时器,当到达时间间隔时,定时器会向程序发送一个WM_TIMER消息。定时器的精度是毫秒,但是准确度很低。例如设置时间间隔为1000ms,但是会在非1000毫秒到达。
消息的参数:
WPARAM - 定时器ID
LPARAM - 定时器处理函数的指针
定时器使用:
创建定时器:
UINT SetTimer(
HWND hWnd,//定时器窗口句柄
UINT nIDEvent, //定时器ID
UINT uElapse,//时间间隔(毫秒为单位)
TIMERPROC lpTimerFunc //定时器处理函数指针
);创建成功,返回非0。
若lpTimerFunc为NULL,则使用窗口处理函数做为定时器处理函数,否则使用定时器处理函数处理定时器消息。
关闭定时器:
BOOL KillTimer(
HWND hWnd,//定时器窗口句柄
UINT uIDEvent //定时器ID
);
附:GetClientRect 获取窗口客户区大小.
RECT rect={0};
GetClientRect(hWnd,&rect);
定时器处理函数原型:
VOID CALLBACK TimerProc(
HWND hwnd, // 定时器窗口句柄
UINT uMsg, // WM_TIMER消息
UINT idEvent, //定时器ID
DWORD dwTime // 自系统开启到当前流逝的时间(毫秒为单位)
);
三、 菜单
1. 菜单的分类
1.1 窗口的顶层菜单
1.2 弹出式菜单
1.3 系统菜单
HMENU类型表示菜单窗口的顶层菜单,菜单每一项有相应的ID。
窗口的顶层菜单:
创建菜单:
HMENU CreateMenu(VOID); //创建成功返回菜单句柄
增加菜单项
BOOL AppendMenu(
HMENU hMenu, //菜单句柄
UINT uFlags, //菜单项风格
UINT_PTR uIDNewItem, //菜单项ID
LPCTSTR lpNewItem //菜单项的名称
);
弹出式菜单 Popup:
创建菜单
HMENU CreatePopupMenu(VOID);//创建成功返回菜单句柄
加入顶层菜单
BOOL AppendMenu(
HMENU hMenu, //菜单句柄
UINT uFlags, //菜单项增加选项,此时为MF_POPUP
UINT_PTR uIDNewItem, //弹出式菜单的句柄
LPCTSTR lpNewItem //菜单项的名称
);
注意:或是InsertMenu();
MF_POPUP -弹出下拉菜单或子菜单,uIDNewItem必须填写下拉菜单或子菜单句柄。
MF_SEPARATOR -分割线
MF_STRING -具有这种风格的菜单项,被点击后会发出消息(WM_COMMAND)
设置到窗口:
BOOL SetMenu(
HWND hWnd, //窗口句柄
HMENU hMenu //菜单句柄
);
如:
HMENU hMain=CreateMenu();//创建菜单
HMENU hFile=CreatePopupMenu();//创建弹出式菜单
AppendMenu(hFile,MF_STRING|MF_CHECKED,1003,"新建");//新建前打钩
AppendMenu(hFile,MF_SEPARATOR,0,"");//分割线
AppendMenu(hFile,MF_STRING|MF_MENUBREAK,1004,"退出");//退出在弹出菜单中 下换列在一列
HMENU hHelp=CreatePopupMenu();
AppendMenu(hHelp,MF_STRING|MF_GRAYED,1005,"关于");//关于变灰,使其不可用
AppendMenu(hMain,MF_POPUP,(UINT)hFile,"文件");
AppendMenu(hMain,MF_POPUP,(UINT)hHelp,"帮助");
SetMenu(hWnd,hMain);
菜单命令处理:
WM_COMMAND 消息
WPARAM:
HIWORD(高16位) – 当对于菜单为0
LOWORD (低16位)- 菜单项的ID
LPARAM – 当对于菜单为NULL
命令处理过程
获取WPARAM菜单ID,根据ID处理。
菜单的使用
菜单项的状态
在增加菜单项可以设置菜单项的状态。
可以使用菜单API 修改状态
CheckMenuItem
EnableMenuItem
DWORD CheckMenuItem(
HMENU hmenu, // 菜单句柄
UINT uIDCheckItem, // 测试的菜单项
UINT uCheck // 菜单项标识
);
函数使用时,需要通过设置uCheck 为MF_BYPOSITION或者MF_BYCOMMAND,确定使用菜单索引或者菜单ID。若为MF_BYPOSITION,则uIDCheckItem应为相应菜单的索引序号,注意,此序号为以0开始,分割线也要计数的,另外使用了MF_MENUBREAK的那一列项计入在第二列,即从第一列计数完再第二列。若为MF_BYCOMMAND,则uIDCheckItem应为菜单的ID号。EnableMenuItem()相似用法。
在菜单被激活但是未显示(比如点击下拉菜单时里面的菜单项),窗口会收到WM_INITMENUPOPUP消息
WPARAM - 菜单句柄(即将显示出来的菜单句柄)
LPARAM - LOWORD 是菜单项索引(被点击的菜单项的索引,从左至右自0开始的…)
- HIWORD 是窗口菜单标识,(即是否为窗口菜单的标识,顶层菜单、系统菜单为窗口菜单,顶层菜单不是.)
窗口菜单:。。。
右键菜单Context Menu
1 创建菜单
右键菜单是一个弹出式菜单(鼠标右键弹起触发,可以用WM_RBUTTONUP,但建议用WM_CONTEXTMENU更专业),使用CreatePopupMenu创建。
2 显示菜单
BOOL TrackPopupMenu(
HMENU hMenu, //菜单句柄
UINT uFlags, //显示方式
int x, //水平位置,屏幕坐标系,非窗口
int y, //垂直位置,屏幕坐标系,非窗口
int nReserved, //保留,必须为0
HWND hWnd, //处理菜单消息的窗口句柄
CONST RECT *prcRect //NULL,忽略
);
TrackPopupMenu是阻塞函数。
使用WM_RBUTTONUP时,由于获得的是窗口坐标参数,故窗口坐标需要转化为屏幕坐标如下。
//客户区窗口坐标转变为Windows屏幕坐标
BOOL ClientToScreen(
HWND hWnd, // 窗口句柄
LPPOINT lpPoint // 坐标(输入、输出同一参数),入为客户窗口,输出为屏幕
);
///Windows屏幕坐标转变为客户区窗口坐标
BOOL ScreenToClient(
HWND hWnd, // 窗口句柄
LPPOINT lpPoint // 坐标(输入、输出同一参数)(与上相反过程)
);
而使用WM_CONTEXTMENU时,不需要转换,参数已经为屏幕坐标系下的。
WParam - 右键点击的窗口句柄
LPARAM - LOWORD X坐标,屏幕坐标系
HIWORD Y坐标,屏幕坐标系
WM_CONTEXTMENU消息是在WM_RBUTTONUP消息之后产生。
3 命令处理
触发WM_COMMAND消息,与窗口菜单一致
如果Track设置了TPM_RETURNCMD选项,那么被点击的菜单项ID通过函数的返回值获取,另外为阻塞函数且选择该选项,此时不会进入消息循环,返回值的菜单项ID可以处理其他事物。若没有该选项,不会返回被点击菜单项ID,且会进入消息循环。
4 菜单项的状态
WM_INITMENUPOPUP,按照弹出菜单处理,用法同WM_INITMENUPOPUP消息,即点击右键激活但为显示的状态下。
系统菜单(比如窗口图标点击弹出的菜单)
1 系统菜单的获取
HMENU GetSystemMenu(
HWND hWnd,//窗口句柄
BOOL bRevert //重置选项
); 返回获取到的系统菜单句柄
bRevert: TRUE - 删除旧菜单,恢复到默认的系统菜单
FALSE - 返回当前的系统菜单句柄。一般下建议使用FALSE。
2 系统菜单的修改
AppendMenu
DeleteMenu (也可以删除其他的非系统菜单项)
BOOL DeleteMenu(
HMENU hMenu, //菜单句柄
UINT uPosition, // 菜单选项或索引
UINT uFlags // 菜单项标识
);
使用同CheckMenuItem()相似,
3 系统菜单命令响应
通过WM_SYSCOMMAND消息响应菜单命令。
WPARAM的LOWORD是命令ID,即点击的相应菜单项ID。