【转】MFC 无边框窗口的拖动

MFC中无边框窗口的拖动
void  CXXXXDialog::OnLButtonDown(UINT   nFlags,   CPoint   point)   
{   
      PostMessage(WM_NCLBUTTONDOWN,   HTCAPTION,   MAKELPARAM(point.x,   point.y));   
}
 
要记得添加消息映射,不然这个代码是不起作用的。
 
转:
移动标准窗口是通过用鼠标单击窗口标题条来实现的,但对于没有标题条的窗口,就需要用鼠标单击窗口标题条以外区域来移动窗口。有两种方法可以达到这一目标。
 
方法一:
当窗口确定鼠标位置时,Windows向窗口发送WM_NCHITTEST消息,可以处理该消息,使得只要鼠标在窗口内,Windows便认为鼠标在标题条上。这需要重载CWnd类处理WM_NCHITTEST消息的OnNcHitTest函数,在函数中调用父类的该函数,如果返回HTCLIENT,说明鼠标在窗口客户区内,使重载函数返回HTCAPTION,使Windows误认为鼠标处于标题条上。
 
下例是使用该方法的实际代码:
UINT CEllipseWndDlg::OnNcHitTest(CPoint point)
{
    // 取得鼠标所在的窗口区域
    UINT nHitTest = CDialog::OnNcHitTest(point);
    // 如果鼠标在窗口客户区,则返回标题条代号给Windows
    // 使Windows按鼠标在标题条上类进行处理,即可单击移动窗口
    return (nHitTest==HTCLIENT) ? HTCAPTION : nHitTest;
}

方法二:

当用户在窗口客户区按下鼠标左键时,使Windows认为鼠标是在标题条上,即在处理WM_LBUTTONDOWN消息的处理函数OnLButtonDown中发送一个wParam参数为HTCAPTION,lParam为当前坐标的WM_NCLBUTTONDOWN消息。

 下面是使用该方法的实际代码:
void CEllipseWndDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
    // 调用父类处理函数完成基本操作
    CDialog::OnLButtonDown(nFlags, point);
    // 发送WM_NCLBUTTONDOWN消息
    // 使Windows认为鼠标在标题条上
    PostMessage(WM_NCLBUTTONDOWN,HTCAPTION, MAKELPARAM(point.x, point.y)); 
 //或SendMessage(WM_SYSCOMMAND,0xF012,0);   //0xF012 = SC_MOVE | HTCAPTION
}

首先,看看在正常情况下系统是怎样来移动程序窗口的。当使用者在程序窗口标题栏区域(非工作区)内,按下鼠标左键时将会发生下列事情:

 
◆ 系统向该窗口过程函数发送WM_NCLBUTTONDOWN消息。
 
◆ WM_NCLBUTTONDOWN消息最终将传送到窗口过程函数中的DefWindowProc()函数中去。
 
◆ DefWindowProc()函数将根据鼠标左键按下并移动,以及HTCAPTION标识所表示鼠标按下时的位置诸多信息,来执行该消息的缺省动作即窗口随同鼠标光标一起移动的操作。
 
下面作为练习来测试一下,首先在窗口回调函数(即窗口过程函数)中设置下列语句:
caseWM_NCLBUTTONDOWN

     return 0;

然后,同样是在窗口标题栏内按住鼠标左键并移动鼠标,但此时窗口却并不随同鼠标一起移动了,这是怎么回事?这是因为上述语句中设有“return 0”语句的缘故。该语句使得WM_NCLBUTTONDOWN消息未能传递到DefWindowProc()函数,就在窗口过程函数中提前返回了,当然移动窗口的操作就无从进行了。这也从反面印证了一个事实,那就是,最后完成移动窗口的操作将是由DefWindowProc()函数来完成的。

 
通过上面的分析可以勾划出这样一个操作过程:即用户在窗口标题栏内按下鼠标左键→ 系统发送WM_NCLBUTTONDOWN消息 → DefWindowProc()函数接收消息 → 用户移动鼠标 → DefWindowProc()函数执行窗口随同鼠标一起移动的操作。
 
由此得出一个结论,那就是要想实现移动窗口的操作,必须具备两个条件:一是要按下鼠标左键并移动(DefWindowProc()函数将检测这个条件);二是在按下鼠标左键时能发送WM_NCLBUTTONDOWN消息并返回HTCAPTION标识。
 
下面就根据以上的分析,在没有窗口标题栏的情况下,采取骗取DefWindowProc()函数的方式,来实现对无标题栏窗口实体的移动操作。
 
  
1、主动发送WM_NCLBUTTONDOWN消息
在窗口没有标题栏的情况下,在窗口实体上按下鼠标左键时,系统是不会发送WM_NCLBUTTONDOWN消息的,这是因为鼠标光标是在窗口的工作区内被按下的,此时系统发送的是WM_LBUTTONDOWN消息。
 
但通过上述的分析,可以知道DefWindowProc()函数并不关心WM_NCLBUTTONDOWN消息是由谁发出的,而只是关心是否有该消息发出。这样只要我们在按下鼠标左键的事件中,主动将WM_NCLBUTTONDOWN消息发出,岂不就可同时满足这两个条件吗!下面的代码就是根据这个思路来设计的。
 
case WM_LBUTTONDOWN:
        SendMessage(hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0);
        break;

消息发送时,通过HTCAPTION参数给DefWindowProc()函数带去一个信息,告诉它鼠标左键是在窗口非工作区内的标题栏处按下的。当然这是一个假情报,但DefWindowProc()函数会信以为真并根据这个信息来执行相应的操作。

  
2、主动发送WM_SYSCOMMAND消息
 
 case WM_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);

即:

 case WM_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、自编代码

前两种方法都有缺陷,会改变消息流,使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;


或:

 

OnLButtonDown() 中:

SetCapture();

CRect rW;

GetWindowRect(rW);

CPoint ptW = point;

ClientToScreen(&ptW);

m_ptCursorOffset.x = ptW.x - rW.left;

 

OnMouseMove()中:

if ((nFlags & MK_LBUTTON) && this == GetCapture())

{

CPoint ptW = point;

ClientToScreen(&ptW);

ptW.x -= m_ptCursorOffset.x;

ptW.y -= m_ptCursorOffset.y;

::SetWindowPos(m_hWnd, 0,ptW.x,ptW.y,0,0,SWP_NOSIZE);  

}

m_ptCursorOffset.y = ptW.y - rW.top;

OnLButtonUp()中:

ReleaseCapture();

 

BOOL SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int x, int y,int cx, int cy, UINT nFlags); //MoveWindow也可以实现窗体移动

函数功能

该函数改变一个子窗口,弹出式窗口或顶层窗口的尺寸,位置和Z序。
子窗口,弹出式窗口,及顶层窗口根据它们在屏幕上出现的顺序排序、顶层窗口设置的级别最高,并且被设置为Z序的第一个窗口。

参数

hWnd:窗口句柄
hWndlnsertAfter:在z序中的位于被置位的窗口前的窗口句柄。该参数必须为一个窗口句柄,或下列值之一:
HWND_BOTTOM:将窗口置于Z序的底部。如果参数hWnd标识了一个顶层窗口,则窗口失去顶级位置,并且被置在其他窗口的底部。
HWND_NOTOPMOST:将窗口置于所有非顶层窗口之上(即在所有顶层窗口之后)。如果窗口已经是非顶层窗口则该标志不起作用。
HWND_TOP:将窗口置于Z序的顶部。
HWND_TOPMOST:将窗口置于所有非顶层窗口之上。即使窗口未被激活窗口也将保持顶级位置。
查看该参数的使用方法,请看说明部分。
x:以客户坐标指定窗口新位置的左边界。
Y:以客户坐标指定窗口新位置的顶边界。
cx:以像素指定窗口的新的宽度。
cy:以像素指定窗口的新的高度。
uFlags:窗口尺寸和定位的标志。该参数可以是下列值的组合:
SWP_ASYNCWINDOWPOS:如果调用进程不拥有窗口,系统会向拥有窗口的线程发出需求。这就防止调用线程在其他线程处理需求的时候发生死锁。
SWP_DEFERERASE:防止产生WM_SYNCPAINT消息。
SWP_DRAWFRAME:在窗口周围画一个边框(定义在窗口类描述中)。
SWP_FRAMECHANGED:给窗口发送WM_NCCALCSIZE消息,即使窗口尺寸没有改变也会发送该消息。如果未指定这个标志,只有在改变了窗口尺寸时才发送WM_NCCALCSIZE。
SWP_HIDEWINDOW;隐藏窗口。
SWP_NOACTIVATE:不激活窗口。如果未设置标志,则窗口被激活,并被设置到其他最高级窗口或非最高级组的顶部(根据参数hWndlnsertAfter设置)。
SWP_NOCOPYBITS:清除客户区的所有内容。如果未设置该标志,客户区的有效内容被保存并且在窗口尺寸更新和重定位后拷贝回客户区。
SWP_NOMOVE:维持当前位置(忽略X和Y参数)。
SWP_NOOWNERZORDER:不改变z序中的所有者窗口的位置。
SWP_NOREDRAW:不重画改变的内容。如果设置了这个标志,则不发生任何重画动作。适用于客户区和非客户区(包括标题栏和滚动条)和任何由于窗回移动而露出的父窗口的所有部分。如果设置了这个标志,应用程序必须明确地使窗口无效并区重画窗口的任何部分和父窗口需要重画的部分。
SWP_NOREPOSITION;与SWP_NOOWNERZORDER标志相同。
SWP_NOSENDCHANGING:防止窗口接收WM_WINDOWPOSCHANGING消息。
SWP_NOSIZE:维持当前尺寸(忽略cx和Cy参数)。
SWP_NOZORDER:维持当前Z序(忽略hWndlnsertAfter参数)。
SWP_SHOWWINDOW:显示窗口。
返回值:如果函数成功,返回值为非零;如果函数失败,返回值为零。若想获得更多错误消息,请调用GetLastError函数。


参考:http://baike.baidu.com/view/1080349.htm

原文地址:http://blog.sina.com.cn/s/blog_6288219501015dwa.html

posted on 2013-12-20 10:24  梦琪小生  阅读(884)  评论(0编辑  收藏  举报

导航