无边框窗口拖动

23 篇文章 0 订阅
这几天做UI,遇到了很多以前遇到,但是自己却没有解决的问题,在此把这些重新遇到的问题解决一下。也是我认识到在软件编程的一个道理:欠下的总要换的。
这次所遇到的问题是:无边框的窗口移动。
首先我要说说自己尝试过的方案,然后再说比较好的方案。
我首先想到的方案就是:鼠标按下,记录一下坐标,鼠标移动的时候,把窗口移动到新的位置。这种方案能解决问题,但是鼠标移动太快的时候,窗口跟不上鼠标的动作。然后这个方案是不行的。
然后想到的是把鼠标MOVE时间置换成定时器,让窗口定期移动,我把时间设置为0.001s,可是还是不行,还是跟不上鼠标的进度。我就开始郁闷了,究竟怎么办呢?
我脑中似乎想到以前有一个窗口移动的时候可以禁止窗口重绘什么的函数。于是又开始自己的探索之旅。无意之间我看到下面这篇文章
http://blog.sina.com.cn/s/blog_6288219501015dwa.html
然后我就开始尝试上面提供的方法:
第一种:响应WM_LBUTTONDOWN消息
void   CXXXXDialog::OnLButtonDown(UINT   nFlags,   CPoint   point)   
{   
              PostMessage(WM_NCLBUTTONDOWN,   HTCAPTION,   MAKELPARAM(point.x,   point.y));   
}

PostMessage是向窗口发送一个WM_NCLBUTTONDOWN消息,这个消息模拟系统窗口移动消息。把系统自动发送的消息,变为主动发送。
第二种方法:
当窗口确定鼠标位置时,Windows向窗口发送WM_NCHITTEST消息,可以处理该消息,使得只要鼠标在窗口内,Windows便认为鼠标在标题条上。这需要重载CWnd类处理WM_NCHITTEST消息的OnNcHitTest函数,在函数中调用父类的该函数,如果返回HTCLIENT,说明鼠标在窗口客户区内,使重载函数返回HTCAPTION,使Windows误认为鼠标处于标题条上。
LRESULT C**Dlg::OnNcHitTest(CPoint point)
{
	 UINT nHitTest = CDialog::OnNcHitTest(point);//获取点击的区域
	  return (nHitTest==HTCLIENT) ? HTCAPTION : nHitTest;//如果在客户区,则欺骗系统说在标题栏
	//return CDialogEx::OnNcHitTest(point);
}

我们想想正常情况下,用户在标题栏上点击鼠标会发生什么事情?
1、系统向该窗口过程函数发送WM_NCLBUTTONDOWN消息。
2、WM_NCLBUTTONDOWN消息最终将传送到窗口过程函数中的DefWindowProc()函数中去。
3、DefWindowProc()函数将根据鼠标左键按下并移动,以及HTCAPTION标识所表示鼠标按下时的位置诸多信息,来执行该消息的缺省动作即窗口随同鼠标光标一起移动的操作。
1、主动发送WM_NCLBUTTONDOWN消息
在窗口没有标题栏的情况下,在窗口实体上按下鼠标左键时,系统是不会发送WM_NCLBUTTONDOWN消息的,这是因为鼠标光标是在窗口的工作区内被按下的,此时系统发送的是WM_LBUTTONDOWN消息。
//转载:
但通过上述的分析,可以知道DefWindowProc()函数并不关心WM_NCLBUTTONDOWN消息是由谁发出的,而只是关心是否有该消息发出。这样只要我们在按下鼠标左键的事件中,主动将WM_NCLBUTTONDOWN消息发出,岂不就可同时满足这两个条件吗!下面的代码就是根据这个思路来设计的。
 caseWM_LBUTTONDOWN:
                 SendMessage(hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0);
                 break; 

消息发送时,通过HTCAPTION参数给DefWindowProc()函数带去一个信息,告诉它鼠标左键是在窗口非工作区内的标题栏处按下的。当然这是一个假情报,但DefWindowProc()函数会信以为真并根据这个信息来执行相应的操作。
2、主动发送WM_SYSCOMMAND消息
caseWM_LBUTTONDOWN:
                 SendMessage(hWnd,WM_SYSCOMMAND,SC_DRAGMOVE,0);
                 break; 
能使用WM_SYSCOMMAND消息来移动窗口,得益于一个新近扩展的SC_DRAGMOVE风格标志,该标志从字面上就能看出是"拖曳移动"的意思。该标志在低版本的编译器(VC 6.0中就没有)中是找不到的,所以在引用该标志时应预先声明:
#define        SC_DRAGMOVE  0xF012
也可以直接使用其常量值: 
SendMessage(hWnd,WM_SYSCOMMAND,0xF012,0);
上述无标题栏窗口移动窗体的机理与窗口有标题栏时是相似的,相同处最后都是由DefWindowProc()函数来完成实际的操作;不同处是发送消息的方式不同,一个是由系统隐含发送;另一个则是由程序公开发送。采取上述两种方法来移动窗口,同有标题栏移动窗口的视觉效果是一样的,那就是在移动时,先移动一个指示框(一个虚线框),等确定好窗体移动后的新位置后,当松开鼠标左键时窗口的实体才被正真移动到虚线框所指向的位置处。那么,能否直接移动窗口实体而不出现虚线框呢?答案是肯定的。

实际上,操作系统准备了两种移动窗口的方式,一种是有虚线框,另一种则是没有虚线框,只是Windows系统默认的是有虚线框。不过,如果我们在上述示例代码中再添加下列语句: 
SystemParametersInfo(SPI_SETDRAGFULLWINDOWS,true,NULL,0);
即:
caseWM_LBUTTONDOWN:
      SystemParametersInfo(SPI_SETDRAGFULLWINDOWS,true,NULL,0);
                 SendMessage(hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0);
                 break; 
这样在移动窗口时,虚线指示框将不会出现。注意,上述语句的位置顺序不能错,否则,在移动时虚线框还会出现。
不过,系统默认按有虚线指示框的方式来移动窗口是通过权衡利弊的。这是因为有虚线框方式移动窗口,在开始时并未正真地移动窗口,而是用一个线框来指定窗口将要到达的位置,然后一次性的将窗口移动到指定的位置(在指定的位置重新绘制)处。也就是说,在移动窗口的过程中,窗口实体只需要重绘一次即可。如果不用虚线框,而是直接移动窗口实体的话,在窗口的移动过程中,将会形成N次绘制窗口,增加系统处理图形的负担,而使窗口的绘制质量严重下降,造成不好的视觉效果。为此,在编程实践中可以这样来安排:对异形窗口为了体现异形视觉效果,可以使用无虚线框的方式来移动窗口;而对于一般矩形窗口就可按有虚线框的方式来移动窗口,以求确保窗口的重绘质量。
诚然,上面设置的代码可以使异形窗口无虚线框方式移动,但由于SystemParametersInfo()函数是系统级的,对它的调用将会影响电脑桌面上所有程序窗口都会按无虚线框方式移动,如果这样的话。必将会使桌面整体的视觉效果大打折扣。如果不想影响其它窗口的移动效果,而只是要求在移动本异形窗口时不出现虚线框的话,则可在窗口过程函数中再增添下列代码即可:
      case WM_MOUSEMOVE:
                 SystemParametersInfo(SPI_SETDRAGFULLWINDOWS,false ,NULL,0);
                 break; 

3、自编代码(@TT 通过我的测试,这个方法还是逃脱不了窗口跟不上鼠标的悲剧,不建议使用,只不过SetCapture可以确保你所有的操作都在本窗口)
前两种方法都有缺陷,会改变消息流,使OnLButtonDown,OnLButtonUp等不响应自编代码同样可以实现无标题栏窗口的移动操作,而且更加灵活多样。例如,操作可以由鼠标左键或鼠标右键来完成。下面是通过鼠标右键来完成窗口移动的实际代码,在程序窗口的过程函数中添加下列代码语句:
static POINT pt, pe;
static RECT rt, re;
case WM_RBUTTONDOWN:
    SetCapture(hWnd);                         // 设置鼠标捕获(防止光标跑出窗口失去鼠标热点)
    GetCursorPos(&pt);                         // 获取鼠标光标指针当前位置
    GetWindowRect(hWnd,&rt);   // 获取窗口位置与大小
    re.right=rt.right-rt.left;               // 保存窗口宽度
    re.bottom=rt.bottom-rt.top; // 保存窗口高度
    break;
case WM_RBUTTONUP:
    ReleaseCapture();                           // 释放鼠标捕获,恢复正常状态
    break;
case WM_MOUSEMOVE:
    GetCursorPos(&pe);                              // 获取光标指针的新位置
    if(wParam==MK_RBUTTON)               // 当鼠标右键按下
    {
        re.left=rt.left+(pe.x - pt.x);    // 窗口新的水平位置
        re.top =rt.top+(pe.y - pt.y); // 窗口新的垂直位置
        MoveWindow(hWnd,re.left,re.top,re.right,re.bottom,true);// 移动窗口
    }
    break;







  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值