分类: C/C++
2008-04-23 21:39:08
无标题栏对话框的拖动
编译/
很多基于对话框的应用程序都是不带框架的,也就是说对话框没有标题栏。众所周知,窗口的移动都是通过鼠标点住标题栏拖动窗口实现的,那么现在没有了标题栏,如何移动对话框呢?本文拟针对这个问题提出解决的办法。
解决这个问题有两种方案。一种很业余,另外一种比较专业。前者使用一种常规思路处理鼠标拖拽事件。当窗口获得WM_LBUTTONDOWN(OnLButtonDown)时,通过设置标志并调用SetCapture控制鼠标使应用程序进入移动模式。进入移动模式之后,只要有WM_MOUSEMOVE消息过来,就可以据此移动框架窗口。最后,当用户释放鼠标按钮,则WM_LBUTTONUP消息处理例程清除标志并调用ReleaseCapture函数将鼠标控制返还给Windows。之所以说这种方法业余,主要是因为比较繁琐,首先要决定窗口移到哪?然后要想好如何重绘窗口等等,而且根据屏幕显示属性对话框“效果”页中“视觉效果”项的“拖动实显示窗口内容”复选框是不是选中,拖动效果是不同的。那么你怎么知道的设置呢?(方法是调用SystemParametersInfo(SPI_GETDRAGFULLWINDOWS)。Windows要程序员来事务巨细地处理这些繁琐的事情真是太遭了。由于Windows本身知道通过鼠标点住标题栏可以移动窗口,那么能不能将鼠标在窗口客户区任何地方的点击拖动行为都模仿成好像是在标题栏中一样呢?
图五 显示器的属性对话框-“效果”标签
答案是肯定的。有心的读者可能已经猜测到下一步该怎么做了答。实际上,用鼠标点住对话框背景进行拖动操作并不难,但是你必须了解在标题栏里拖动窗口的原理。Windows首先确定鼠标点中了那个窗口,然后向那个窗口发送一个WM_NCHITTEST消息找出此窗口的哪个“非客户区”(如边界、最大化/最小化按钮、菜单、标题等等)拥有鼠标光标。接着默认的窗口过程响应消息并返回一个特定的代码。如果鼠标指针落在标题栏中,那么这个神奇的特定代码就是HTCAPTIONA。如果WM_NCHITTEST返回HTCAPTION,那么Windows便进入拖拽模式,以便对窗口进行移动操作。所以要想在客户区里用鼠标拖动对话框,那么只要在客户区里模仿标题栏里的鼠标拖动行为即可。这个我们下面要介绍的比较专业的方法,其主要思路是处理WM_NCHITTEST消息:
UINT CMyDialog::OnNcHitTest(CPoint pt) { CRect rc; GetClientRect(&rc); ClientToScreen(&rc); return rc.PtInRect(pt) ? HTCAPTION : CDialog::OnNcHitTest(pt); }上面这个代码很容易理解,当鼠标落在客户区内,函数返回HTCAPTION。对于一个简单的对话框来说,仅仅用这个代码就完全可以实现在对话框背景内的拖动操作。因为Windows使用z-order坐标来确定鼠标下是哪个窗口,所以对话框中其它的所有对象照常工作。如果用户单击某个控制,只要这个控制不是静态位图图像或者文本,那么Windows都将鼠标事件发送到该控制上,而不是对话框。由于静态位图图像或者文本对于对话框是透明的,所以鼠标在上面的拖动同样实现移动,而对于对话框中的编辑框、按钮、组合框等其它非静态控制则按通常的行为方式运行。
UINT CMyView::OnNcHitTest(CPoint pt) { return HTTRANSPARENT; }这样做以后,Windows将忽略视图并继续搜索能接收WM_NCHITTEST的窗口。如果顺利的话,将找到父窗口,这时用与对话框相同的WM_NCHITTEST处理代码即可,即在客户区中的点击返回HTCAPTION。你甚至可以通过鼠标坐标的象素计算,在规定的局部范围内实现视图透明。
UINT CMyView::OnNcHitTest(CPoint pt) { return PointLiesWithinDraggableRegion(pt) ? HTTRANSPARENT : CView::OnNcHitTest(pt); }这样拖拽/移动特性只能在PointLiesWithinDraggableRegion的非零窗口区域内有效。为此请参见另外一篇文章的基于框架/视图的例子:“MFC框架程序中全屏显示特性的实现”,其中就实现了用鼠标在客户区拖动整个框架窗口的效果。