分享

WIN32无边框窗体的缩放、移动与WM

 c 资料收集 2014-03-24

一、WM_NCHITTEST消息

MSDN对它的解释是:

The WM_NCHITTEST message is sent to a window when the cursor moves, or when a mouse button is pressed or released. If the mouse is not captured, the message is sent to the window beneath the cursor. Otherwise, the message is sent to the window that has captured the mouse.

解释:
1、这个消息是当鼠标移动或者有鼠标键按下时候发出的。比如,我现在在窗体的某个位置点击了一下,系统会首先发出在发出WM_NCHITTEST消息,然后根据返回值来判断是发出WM_LBUTTONDOWN还是发送WM_NCLBUTTONDOWN!!!
2、Windows用这个消息来做什么? “HITTEST”就是“命中测试”的意思,WM_NCHITTEST消息用来获取鼠标当前命中的位置。WM_NCHITTEST的消息响应函数会根据鼠标当前的坐标来判断鼠标命中了窗口的哪个部位,消息响应函数的返回值指出了部位,例如它可能会返回HTCAPTION,或者HTCLIENT等。(其返回值有很多,请查阅MSDN)。

3、当窗体是WS_POP时,WM_NCHITTEST会与WS_SYSMENU类型冲突!!!!!也就是如果给POPUP窗体添加上WS_SYSMENU类型,那么就无法响应WM_NCHITTEST消息!!!!
4、残存疑问:这个消息的响应方式非常奇怪,只能由自己响应,即便是在父类中有响应函数,也不能让父类来响应,如果由父类响应会出现移动时跟鼠标移动位置不符合的问题,不知为何,这也就是为什么在DUILIB中,必须在用户产生的窗体类中响应WM_NCHITTEST;而不能由PaintManagerUI来响应;

示例:
实现效果:

根据当前的坐标位置,如果在上、下、左、右边框的20像素处时,就显示缩放鼠标,当缩放时,响应WM_SIZE消息,将窗体重绘;

  1. case WM_NCHITTEST:  
  2.     POINT pt;   
  3.     pt.x = GET_X_LPARAM(lParam);   
  4.     pt.y = GET_Y_LPARAM(lParam);  
  5.     ::ScreenToClient(hWnd,&pt);  
  6.   
  7.     RECT rcClient;  
  8.     ::GetClientRect(hWnd, &rcClient);  
  9.   
  10.     if (pt.x<rcClient.left+20&&pt.y<rcClient.top+20)//左上角,判断是不是在左上角,就是看当前坐标是不是即在左边拖动的范围内,又在上边拖动的范围内,其它角判断方法类似  
  11.     {  
  12.         return HTTOPLEFT;  
  13.     }else if (pt.x>rcClient.right-20 && pt.y<rcClient.top+20)//右上角  
  14.     {  
  15.         return HTTOPRIGHT;  
  16.     }else if (pt.x<rcClient.left+20 && pt.y>rcClient.bottom-20)//左下角  
  17.     {  
  18.         return HTBOTTOMLEFT;  
  19.     }else if (pt.x>rcClient.right-20 && pt.y>rcClient.bottom-20)//右下角  
  20.     {  
  21.         return HTBOTTOMRIGHT;  
  22.     }else if (pt.x<rcClient.left+20)  
  23.     {  
  24.         return HTLEFT;  
  25.     }else if (pt.x>rcClient.right-20)  
  26.     {  
  27.         return HTRIGHT;  
  28.     }else if (pt.y<rcClient.top+20)  
  29.     {  
  30.         return HTTOP;  
  31.     }if (pt.y>rcClient.bottom-20)  
  32.     {  
  33.         return HTBOTTOM;          //以上这四个是上、下、左、右四个边  
  34.     }else  
  35.     {  
  36.         return HTCAPTION;  
  37.     }  
  38.     break;  
  39. case WM_SIZE:   //要让窗体能够随着缩放改变,要响应WM-SIZE消息  
  40.     {  
  41.         RECT rcClient = { 0 };  
  42.         ::GetClientRect(hWnd, &rcClient);   
  43.         InvalidateRect(hWnd,&rcClient,FALSE);  
  44.     }  
  45.     break;  

二、无边框窗体的移动方法

1、截获WM_NCHITTEST消息

就像我们上面讲的WM_NCHITTEST一样,在需要移动窗体的位置,返回HTCAPTION就可以了。这里我们着得讲第二种方法;

2、使用SendMessage(hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0);  

直接发送这个消息,就可以实现拖动窗体,但有点需要注意,在发送这个消息后,所以的用户行为都会被系统认为是用户在非客户所做的,比如,在发送这个消息后,用户会紧跟着有鼠标弹起的动作,由于系统认为是在非客户区的用户行为,所以系统会发送WM_NCLBUTTONUP消息,而不是WM_LBUTTONUP消息!!!!
注意:
1、SetCapture(hwnd)与SendMessage(hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0);冲突!!!
如果在SendMessage前对窗体HWND使用SetCapture设置了焦点,则SendMessage(hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0)将会无效,解决方法是,在SendMessage()前添加上::ReleaseCapture()释放焦点;
2、在SendMessage(hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0)后,系统不会接收到WM_LBUTTONUP消息
在上面我们讲了,在发送WM_NCLBUTTONDOWN消息后,系统会把紧随的用户行为判断为在非客户区的动作,所以当用户LBUTTONUP的时候,系统会发送WM_NCLBUTTONUP而不是WM_LBUTTONUP,所以,如果我们要在拖曳窗体后,要在WM_LBUTTTONUP中响应鼠标弹起操作,怎么办?很简单的方法就是,人为的发送WM_LBUTTONUP消息。所以下面这段代码才是正确使用SendMessage(hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0)的代码段!

  1. ::ReleaseCapture();  
  2. SendMessage(GetHWND(), WM_NCLBUTTONDOWN, HTCAPTION, NULL);  
  3. SendMessage(GetHWND(), WM_LBUTTONUP, NULL, NULL);//解决了在发送WM_NCLBUTTONDOWN后无法响应WM_LBUTTONUP的问题,  
  4. //就是在移动完窗体后,人为的发送消息WM_LBUTTONUP  

参考资料:

《WIN32无标题栏窗口移动方法种种》:http://blog.sina.com.cn/s/blog_5f700b480101he5n.html


三、UpdateLayeredWindow重要心得

对于WM_NCHITTEST的讲解就到这了,但在源码中,有几个地方要注意一下:(这个要配合源码才能清楚)
1、我使用的是GDI+绘图方式;更新窗体方式采用了UpdateLayeredWindow()函数;
2、创建窗体类,我使用的扩展样式是:WS_EX_APPWINDOW

  1. HWND hWnd = ::CreateWindowEx(WS_EX_STATICEDGE | WS_EX_APPWINDOW, L"resize", _T(""),WS_VISIBLE | WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,600,300, NULL, NULL, (HINSTANCE)::GetModuleHandle(NULL),NULL);   
3、这一点是着重要讲的!!!UpdateLayeredWindow函数与WS_EX_APPWINDOW和WS_LAYERED的关系!!!

(1)如果是WS_EX_APPWINDOW,此时在updatelayerwindow,并不会由系统拦截WM_PAINT消息,就像平时处理窗口一样,使用invalidateRect()函数是有效的。也就是说当窗体样式是WS_EX_APPWINDOW,在MSDN中讲的层窗体对于updatelyeredWindow()的那些是不适用的!!!!在这种情况下,系统像平时处理无效区域一样,是可以局部重绘的!!!而且如果窗体类型是WS_EX_LAYERED,局部重绘就失效了!!!!

(2)而如果第一个参数是:WS_EX_LAYERED,此时就必须手动的在各处添加SendMessage(hwnd,wm_paint),这样才会进入WM_PAINT的处理中;

本文由HARVIC完成,如若转载,请标明出处,请大家尊重初创者的版权,谢谢!!

源文地址:http://blog.csdn.net/harvic880925/article/details/9785439

源码地址:http://download.csdn.net/detail/harvic880925/5876317

声明:感谢金山影音漂亮的界面图片,该图片来自网络。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多