windows 句柄 消息响应 窗口过程
1.什么是句柄.
句柄是应用程序建立或使用的对象所使用的一个唯一的整数值(通常是32位),Windows要使用各种各样的句柄来标识诸如应用程序实例,窗口,图标,菜单,输出设备,文件等对象.
Windows是一个以虚拟内存为基础的操作系统,这种环境下,Windows内存管理器经常在内存中来回移动对象,依次满足各种应用程序的需要,对象被移动了,意味着地址就变.因此,Windows操作系统用一块内存地址,用来专门登记各种应用对象在内存中的地址变化,而这个地址(存储单元的位置)本身是不变的,Windows内存管理器在移动对象在内存的位置后,把对象新的地址告知这个句柄保存,这样我们只需记住这个句柄地址就可以间接地知道对象具体的内存中什么位置.这个地址是对象装载时有系统分配的,当系统卸载时又释放系统.
句柄地址(不变) |
记载对象内存的地址 |
记载实际对象 |
句柄是指针的指针.记载对象在内存中的地址.
2. Windows的消息机制
Windows是一个消息驱动式系统,Windows中有两种消息队列,一种是系统消息队列,另一种是应用程序消息队列,计算机中所有的输入设备由Windows监控,当一个事件发生时,Windows先将输入的消息放入系统的消息队列中,再将输入的消息拷贝到相应的应用程序队列中.应用程序的消息循环从他的消息队列中检索每个消息并且发送给相应的窗口函数中.注意:消息是非抢先式,不论事件急或不急,总是按到达先后排队(一些系统消息除外).
系统的消息队列中 |
应用程序的消息队列中 |
窗口函数 |
Windows中消息结构的原型
typedef struct tagMSG
{
HWND hwnd; //消息将要发送到的那个窗口的句柄,用这个参数可以决定让哪个窗口接收消息
UINT message; //消息号,它唯一标识了一种消息类型。
WPARAM wParam; //一个32位的消息参数,这个值的确切意义取决于消息本身
LPARAM lParam; //一个32位的消息参数,这个值的确切意义取决于消息本身
DWORD time; //消息放入消息队列中的时间, 从Windows启动后所测量的时间值
POINT pt; //消息放入消息队列时的鼠标坐标
} MSG, *PMSG;
2.1 消息队列
(1) 系统消息队列(System Message Queue)
设备驱动(mouse, keyboard)会把操作输入转化成消息存在系统队列中,然后系统会把此消息放到目标窗口所在的线程的消息队列(thread-specific message queue)中等待处理.
(2) 线程消息队列(Thread-specific Message Queue)
每一个GUI线程都会维护这样一个线程消息队列。(这个队列只有在线程调用GDI函数时才会创建,默认不创建)。然后线程消息队列中的消息会被送到相应的窗口过程(WndProc)处理, 注意: 线程消息队列中WM_PAINT,WM_TIMER只有在Queue中没有其他消息的时候才会被处理,WM_PAINT消息还会被合并以提高效率。其他所有消息以先进先出(FIFO)的方式被处理.
2.2 队列消息(Queued Messages)和非消息队列消息(Non – Queued Messages)
(1) 队列消息
消息会先保存在消息队列中,消息循环会从此队列中取消息并分发到各窗口处理
(2) 非队列消息
如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, WM_WINDOWPOSCHANGED
注意: postMessage发送的消息是队列消息,它会把消息Post到消息队列中;
SendMessage发送的消息是非队列消息, 被直接送到窗口过程处理
使用:MFC 寄送一个消息,首先获取CWnd类对象的指针.
然后调用CWnd的成员函数PostMessage()
2.3 窗口过程
每个窗口会有一个称为窗口过程的回调函数(WndProc),它带有四个参数,分别为:窗口句柄(Window Handle),消息ID(Message ID),和两个消息参数(wParam, lParam), 当窗口收到消息时系统就会调用此窗口过程来处理消息。(所以叫回调函数)
消息类型:
(1)系统定义消息(Sysetem-Defined Messages)
在SDK中事先定义好的消息,非用户定义的,其范围在[0x0000, 0x03ff]之间, 可以分为以下三类:
1> 窗口消息(Windows Message)
与窗口的内部运作有关,如创建窗口,绘制窗口,销毁窗口等。可以是一般的窗口,也可以是Dialog,控件等。
如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL...
2> 命令消息(Command Message)
与处理用户请求有关, 如单击菜单项或工具栏或控件时, 就会产生命令消息。
WM_COMMAND, LOWORD(wParam)表示菜单项,工具栏按钮或控件的ID。
如果是控件, HIWORD(wParam)表示控件消息类型
wNotifyCode = HIWORD(wParam); //通告代码
wID = LOWORD(wParam); //菜单条目,控件或快捷键的标示符
hwndCtl = (HWND)lParam; //控件句柄
3> 控件通知(Notify Message)
控件通知消息, 这是最灵活的消息格式, 其Message, wParam, lParam分别为:WM_NOTIFY, 控件ID,指向NMHDR的指针。NMHDR包含控件通知的内容, 可以任意扩展。
(2)用户自定义消息
对于其范围有如下规定:
WM_USER: 0x0400-0x7FFF (ex. WM_USER+10)
WM_APP(winver> 4.0): 0x8000-0xBFFF (ex.WM_APP+4)
RegisterWindowMessage: 0xC000-0xFFFF
4. WM_COMMAND消息
WM_COMMAND消息
当用户点击菜单、按钮、下拉列表框等控件时候,会触发WM_COMMAND
LOWORD(wParam) 是控件或菜单或加速键的ID,菜单的sparator的ID为0
如果LOWORD(wParam) 是控件ID,HIWORD(wParam)是notification code, 比如BN_CLICKED, BN_DBLCLK等,标志用户对控件的操作,双击,单击之类。
如果LOWORD(wParam) 是菜单ID,HIWORD(wParam)是0。
如果LOWORD(wParam) 是加速符ID,HIWORD(wParam)是1。
如果LOWORD(wParam) 是控件ID,lParam是控件的句柄值,否则为NULL。其实,GetDlgItem(hWnd, LOWORD(wParam)) == lParam。
Notification Code的命名规律:
列表框: LBN_*****
组合框: CBN_****
Tab框: TBN_****
按钮: BN_*****
Edit : EN_*****
对于WM_SYSCOMMAND 中如果是系统菜单的消息,都必须要交给DefWindowProc 来处理,并且将返回值返回给Windows ,不然你会发现不能拖动窗体、改变大小、最大最小化操作等。因为你如果不交给DefWindowProc 处理,相当于屏蔽了SC_RESTORE、SC_MOVE、SC_MAXIMIZE、SC_MINIMIZE、SC_CLOSE 等等操作了。这些命令都是通过Windows 投递WM_SYSCOMMAND 消息,在DefWindowProc 中进行处理的。
WM_COMMAND产生的条件:点击菜单,点击加速键,点击子窗口按钮,点击工具栏按钮。这些时候都有command消息产生。
WM_COMMAND消息中有两个参数,wparam、lparam,定义如下:
wParam 高两个字节 通知码
wParam 低两字节 命令ID
lParam 发送命令消息的子窗体句柄。
对于菜单和加速键来说,lParam为0,只有控件此项才非0。命令ID也就是资源脚本中定义的菜单项的命令ID或者加速键的命令ID;菜单的通知码为0;加速键的通知码为1。
对于Windows菜单中菜单项和加速键,点击后,Windows会向所属的窗体发送WM_SYSCOMMAND,而不是WM_COMMAND消息。注意,WINDOWS菜单是系统菜单,也就是在标题栏点击鼠标左键的时候弹出的菜单。我们可以捕获WM_CREATE消息,加入自己的操作:GetSysMenu获取系统菜单句柄,然后对系统菜单进行操作,并且捕获添加菜单项(根据菜单命令ID)ID对应的WM_SYSCOMMAND消息进行处理。修改系统默认的菜单行为。
子窗体和父窗体:
子窗体被触发时,向父窗体发送一个WM_COMMAND消息,父窗体的窗口函数处理这个消息,进行相关的处理。lParam表示子窗口句柄,LOWORD(wParam)表示子窗口ID,HIWORD (wParam)表示通知码(例如单击,双击,SETFOCUS等)。
WM_MESSAGE、WM_COMMAND、WM_NOTIFY等消息有什么不同?
WM_MESSAGE是最普通的WINDOWS消息,对于这种类型的消息没什么好说的。那WM_COMMAND和WM_NOTIFY消息都是WINDOWS CONTROL给它的父窗体发的消息,那这两种消息有什么不同呢?WM_COMMAND消息其实是早期的(WIN3.X时代)子窗体消息,子窗体给父窗体发送消息,父窗体就捕获WM_COMMAND来处理子窗体的消息。但是这个消息只包括了有限的信息,例如wParam包括了子窗口ID和通知码,lParam则包括了子窗口句柄,就这点信息了,如果想知道一些额外的信息的话(例如,鼠标点在了子控件的位置)就要借助于其他的WM_*消息。所以对于新型的WIN32控件,微软就增加了一个新的NOTIFICATION消息,这个消息的参数是这样的:wParam包含了控件ID,而lParam则包含了一个结构体的指针,这个结构体是NMHDR结构或者以NMHDR结构为第一项的一个更大的结构体。这样就可以包含了很多的子控件想给父窗体提供的信息了,甚至可以自己去定义这种的结构体。这就是这几种消息的差别点了。
控件的自画:
首先在创建控件的时候当然就是指定BS_OWNERDRAW的STYLE,这个STYLE是告诉控件,别自己处理外观,让主程序来处理你的外观,这时你就有权决定这个控件是画成什么样子了。然后就是处理WM_DRAWITEM的消息,利用 LPDRAWITEMSTRUCT pdis = (LPDRAWITEMSTRUCT) lParam; 来取得一些必要的信息,如按钮的DC,位置等。这才能对这个DC的内容进行绘画啊。COMMON CTRL的STYLE都在COMMCTRL.H头文件里。
按钮在以下状态时会对它的父窗口发送WM_COMMAND的消息:
按了一次(BN_CLICKED),取得焦点(BN_SETFOCUS),失去焦点(BN_KILLFOCUS)等。
这个是按钮的发送WM_COMMAND的条件,其他的控件什么时候会发送WM_COMMAND消息可查看该控件的通知码(在wParam的高位HIWORD)。例如,滚动条控件在被滚动的时候会向它的父窗体发送消息,但是不是WM_COMMAND消息,而是WM_VSCROLL和WM_HSCROLL消息。这只是为了说明凡是子控件,都会在适当的条件下向它的父窗体发送消息。无论是WM_COMMAND还是WM_NOTIFY或是WM_VSCROLL消息等。MoveWindow会产生WM_SIZE消息。
在Windows3.1里,控件会将mouse, keybord等等的消息通知它的父窗口, 使用的消息就只有WM_COMMAND, 事件种类和控件ID被包含在wParam中, 控件的句柄包含在lParam中。由于wParam和 lParam已经满了,当控件要向父窗口发送其它特殊消息同时附带很多信息的时候就没有地方可以存放它们了。所以Windows3.1中定义了许多其它的消息种类,比如WM_VSCROLL, WM_CTLCOLOR等等,每种消息wParam,lParam中附带的信息是不同的。
当到了Win32后,控件的种类越来越多,当然不可以为每一个控件都定义一套消息,这样也不利于系统的扩充。所以在Win32中定义了唯一一个强大的消息 WM_NOTIFY。当然WM_NOTIFY也遵守原来的消息规则,既只带参数wParam和lParam。唯一不同处在于,此时的lParam中传送的是一个NMHDR指针。不同的控件可以按照规则对NMHDR进行扩充,因此WM_NOTIFY消息传送的信息量可以相当的大,这个可以看看MSDN中的相关说明,TreeControl中就有很多这种消息。
现在就可以知道为什么有ON_MESSAGE ,ON_COMMAND, , ON_NOTIFY了。
ON_MESSAGE是处理所有的Windows的消息的,因为所有的消息都以相同的格式传送,也就是ID, WPARAM, LPARAM.
ON_COMMAND是专门处理WM_COMMAND消息的,这样我们就不用自己解开WM_COMMAND中wParam和lParam中传送的控件ID, 事件种类…,所有的都在MFC内部解决了:),当然方便了。
ON_NOTIFY更是不用说了,看看他的处理函数,是不是把NMHDR解出来了。
这样一样就一目了然了,ON_COMMAND和ON_NOTIFY都可以用ON_MESSAGE来处理,只不过自己要多做很多事情。ON_COMMAND和ON_NOTIFY最好就不要互换了!