? 处理窗口消息

窗口消息

窗口消息,就是指Windows发出的一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。例如,对于单击鼠标所产生的消息来说,这个记录中包含了单击鼠标时的坐标。

一、窗口消息范围


消息范围

表示

0 ~ WM_USER–1

操作系统保留的消息。

WM_USER ~ 0x7FFF

私有窗口级别的自定义消息。

WM_APP ~ 0xBFFF

应用程序级别的自定义消息。其中0xAAAA/*_WM_AARDIO_RESERVED*/ 至 0xBFFF 为aardio标准库保留值请勿使用。

0xC000 ~ 0xFFFF

::RegisterWindowMessage函数定义一个新的窗口消息,该消息保证在整个系统范围内是唯一的。

0xFFFF ~

操作系统保留的消息。

二、响应控件命令、通知消息


对于一般的窗口控件发生的事件,通过创建响应命令、通知消息的函数就可以处理了。
一个窗口内的控件发生了一些事情,需要通知父窗口,就会发送 _WM_COMMAND 或者 _WM_NOTIFY 消息给父窗口.

最初Windows 3.x 就有的标准控件(Standard Controls),如Edit,Combobox,Listbox,Button等,发送的控件通知消息的格式是 WM_COMMAND;而后期的 Win32 通用控件(Common Controls),如List View,Image List,IP Address,Tree View,Toolbar 等,发送的都是 WM_NOTIFY 控件通知消息。另外,当用户选择菜单的一个命令项,也会发送 _WM_COMMAND 消息。

早期开发桌面软件使用的是 C++ 这样的静态语言,通常主要代码都是写在父窗口中,所以这种通知父窗口的消息一定意义上来说提供了方便。但是在 aardio 这样的动态语言中这种方式意义不大,在父窗口上处理所有子控件的消息其实是想对麻烦显不必要的,所以 aardio 将这些消息重新发回给控件对象自己处理。

在 aardio 中通过控件的 oncommand 函数处理自身发出的 _WM_COMMAND 消息,通过控件的 onnotify 函数处理自身发出的 _WM_NOTIFY 消息。

在 aardio 开发环境的窗体设计器中,右键点击控件,
在弹出菜单中点击「响应命令」就可以为该控件添加 oncommand 函数。
在弹出菜单中点击「响应通知」就可以为该控件添加 onnotify 函数。



在「 aardio 开发环境 / 窗体设计器」放置的「窗口控件」上直接「双击鼠标左键」可以快速创建或跳转到 oncommand 或 onnotify 函数,一般标准控件双击会创建 oncommand 函数( 如果已创建 oncommand 则跳转到该函数 ),通用控件双击会创建 onnotify 函数( 如果已创建 onnotify 则跳转到该函数 )。

三、窗口消息回调


如果你需要进一步拦截所有消息,在aardio里,非常的简单。
请看下面的动画过程。



在消息回调函数里,可以处理控件或窗口的所有消息。

对于主窗体:同样是在右键菜单中点击【创建窗口消息回调函数】,如下:

winform.wndproc = function (hwnd,message,wParam,lParam){
    
select ( message ) { 
        
case  0x205 /*_WM_RBUTTONUP*/ {
            
//鼠标右键弹起,下面获取坐标
             var  x,y = win.getMessagePos(lParam);
            
        }
        
else {
            
        }
    }
    
//无返回值则继续调用默认回调函数
}



wparam 通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。
lparam 通常是一个指向内存中数据的32位指针。 根据不同的消息,它们有不同的意义,例如在鼠标右键弹起消息里,::LOWORD(lparam)取lparam的低位表示x坐标, ::HIWORD(lparam)取出lparam的高位表示y坐标.

hwnd 32位的窗口句柄。 一般我们不用管这个参数。
message 用于区别其他消息的常量值,这些常量值通常以下划线开头,或_WM_开头

四、发送消息

使用 import win; 语句导入windows支持库,在这个库中定义了下面的消息API函数.

::PostMessage = ::User32.api("PostMessageW" , "int(addr hWnd,INT msg,int wParam,int lParam)" )
::PostThreadMessage = ::User32.api(
"PostThreadMessageW" , "int(addr idThread,INT msg,int wParam,int lParam)" ); 
::SendMessage = ::User32.api(
"SendMessageW" , "int(addr hWnd,INT msg,pointer wParam,pointer lParam)" )
::SendMessageInt = ::User32.api(
"SendMessageW" , "int(addr hWnd,INT msg,int wParam,int lParam)" )
::SendMessageByInt = ::User32.api(
"SendMessageW" , "int(addr hWnd,INT msg,int &wParam,int &lParam)" )
::SendMessageByString = ::User32.api(
"SendMessageW" , "int(addr,INT,int,string &lParam)" )
::SendMessageByStr = ::User32.api(
"SendMessageW" , "int(addr,INT,int,str &lParam)" )
::SendMessageByStruct = ::User32.api(
"SendMessageW" , "int(addr,INT,int,struct &lParam)"
::SendMessageTimeout = ::User32.api(
"SendMessageTimeoutW" , "int(addr hwnd,INT msg,pointer wParam,pointer lParam,INT flags,INT timeout,int & resultult)"


注意在 aardio 中并不是一定要先声明 API 才能调用,也可以直接调用 ::User32.SendMessage() 或 ::User32.PostMessage() 函数。
直接调用 ::User32.SendMessage() 或 ::User32.PostMessage() 的好处是参数可以使用任何可以兼容转换为该API 参数类型的数据类型,参数可以使用的数据类型更多。

而声明的 API 函数有更严格的参数类型检查,也必须严格使用与声明的参数类型兼容的参数,不同的参数类型可能要声明不同的 API 函数。

PostMessage系列函数只负责将消息放到消息队列中然后直接返回,消息由win.loopMessage()处理.
工作线程如果向UI线程消息队列快速发送大量的消息,导至消息队列大小超过系统限制,会导致后续消息丢失,无法正常响应用户操作.

SendMessage系列函数调用窗口回调函数,并等待直到获取返回代码,消息不会放入队列中,即不会被 win.loopMessage() 处理
在多线程中,如果多个线程都频繁的调用SendMessage系列函数,因为该函数会在相同的GUI线程中阻塞处理, 这会导致多线程实际上失去并发执行的效果.

 

/*
了解消息可以做很多有趣的事,例如我们可以不要标题栏(在窗体属性中将text属性清空),不要边框。
自已用控件来模拟windows的标题栏以及边框,可以用图片控件做出漂亮的无边框窗体。在控件的的消息回调中拦截 _WM_LBUTTONDOWN
*/

import win;

//一.模拟标题栏( 也可以直接使用封装好的 winform.hitCaption() 函数
::User32.PostMessage(winform.hwnd, 0xA1/*_WM_NCLBUTTONDOWN*/ , 0x2/*_HTCAPTION*/, 0)


//二、模拟边框(也可以使用标准 库中封装好的 win.ui.resizeBorder  )

//上下左右8个方向调整窗体大小
::User32.SendMessage(winform.hwnd, 0xA1
/*_WM_NCLBUTTONDOWN*/ , 0xC /*_HTTOP*/ , 0) //上边
::User32.SendMessage(winform.hwnd, 0xA1 /*_WM_NCLBUTTONDOWN*/ , 0xF /*_HTBOTTOM*/ , 0) //下边
::User32.SendMessage(winform.hwnd, 0xA1 /*_WM_NCLBUTTONDOWN*/ , 0xA /*_HTLEFT*/ , 0 ); //左边
::User32.SendMessage(winform.hwnd, 0xA1 /*_WM_NCLBUTTONDOWN*/ , 0xB /*_HTRIGHT*/ , 0); //右边
::User32.SendMessage(winform.hwnd, 0xA1 /*_WM_NCLBUTTONDOWN*/ , 0xD /*_HTTOPLEFT*/ , 0); //左上角
::User32.SendMessage(winform.hwnd, 0xA1 /*_WM_NCLBUTTONDOWN*/ , 0x10 /*_HTBOTTOMLEFT*/ , 0 ); //左下角
::User32.SendMessage(winform.hwnd, 0xA1 /*_WM_NCLBUTTONDOWN*/ , 0xE /*_HTTOPRIGHT*/ , 0 ); //右上角
::User32.SendMessage(winform.hwnd, 0xA1 /*_WM_NCLBUTTONDOWN*/ , 0x11 /*_HTBOTTOMRIGHT*/ , 0); //右下角

//三.最大化最小化窗体(和上面调用方法一样,只有sendmessage方法参数不一样)

//1.模拟窗体最小化
::User32.PostMessage(winform.hwnd, 0x112/*_WM_SYSCOMMAND*/,0xF020/*_SC_MINIMIZE*/, 0);

//2.模拟窗体最大化
::User32.PostMessage(winform.hwnd, 0x112/*_WM_SYSCOMMAND*/, 0xF030/*_SC_MAXIMIZE*/,0);

//3.模拟窗体最大化后还原
::User32.PostMessage(winform.hwnd, 0x112/*_WM_SYSCOMMAND*/, 0xF120/*_SC_RESTORE*/, 0);

注意在 aardio 标准库中已经封装好了以上范例中类似的函数,
如 winform 为一个 win.form 对象,则:

    winform.hitMax()  //模拟点击最大化
    winform.hitMin()  //模拟点击最小化
    winform.hitClose()  //模拟点击关闭按钮,当然也可以用 winform.close() 直接关闭
    winform.hitCaption()  //模拟点击标题栏,通常在无边框窗口的 winform.onMouseDown 事件中调用此函数。