在很多情况下,我们需要去掉 Windows 窗口的默认标题栏和边框,但又希望能够像原来一样对窗口进行正常的改变位置或者大小的操作。许多程序员采用响应 WM_LBUTTONDOWN/ WM_LBUTTONUP 消息,鼠标拖动时自己绘制拖动框(在系统的“拖动时显示窗口内容”选项关闭的情况下)或者不停调用 MoveWindow()/ SetWindowPos() API 对窗口的位置或者大小进行更新(在系统的“拖动时显示窗口内容”选项开启的情况下)。这样做虽然可以达到效果,但是相较老汉下面将要描述的方法,稍微麻烦了一些,而且出于用户体验的完整性考虑,需要自己检测拖动过程中用户是否按下了 Esc 键,或者是正在拖动的窗口突然被其他窗口夺取了焦点等等小概率事件。
老汉介绍的方法,整个拖动操作仍然使用系统自身的功能,所以不存在上述问题。其核心思想就是改变系统在对窗口进行点击测试是返回的值,对系统进行一定程度上的欺骗,使其认为鼠标依旧是点击到了默认的标题栏或者边框,从而执行相应的动作。
下面是示例代码。使用向导生成一个 MFC 对话框应用,假定对话框类的名字为 CMvszDlg,向该类添加对 WM_PAINT 和 WM_NCHITTEST 消息的处理函数(默认在 ClassWizard 中是看不到 WM_NCHITTEST 消息的,需要切换到 ClassWizard 的“Class Info”标签,将“Message filter”选择为“Window”)。然后把下面的代码分别复制到对应的消息处理函数中,编译运行即可看到效果。其中, OnPaint() 的实现是为了演示目的,在真实的应用中,显然可以使用更漂亮的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
#define BORDER_SIZE 4 #define CAPTION_SIZE 24 void CMvszDlg::OnPaint() { Invalidate(); CPaintDC dc(this); CRect rc; GetClientRect(&rc); CBrush* pbr = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); dc.SelectObject(pbr); dc.Rectangle(&rc); dc.MoveTo(0, BORDER_SIZE + CAPTION_SIZE); dc.LineTo(rc.right, BORDER_SIZE + CAPTION_SIZE); dc.MoveTo(0, rc.bottom - (BORDER_SIZE + CAPTION_SIZE)); dc.LineTo(BORDER_SIZE, rc.bottom - (BORDER_SIZE + CAPTION_SIZE)); dc.MoveTo(rc.right - BORDER_SIZE, rc.bottom - (BORDER_SIZE + CAPTION_SIZE)); dc.LineTo(rc.right, rc.bottom - (BORDER_SIZE + CAPTION_SIZE)); dc.MoveTo(BORDER_SIZE + CAPTION_SIZE, 0); dc.LineTo(BORDER_SIZE + CAPTION_SIZE, BORDER_SIZE); dc.MoveTo(BORDER_SIZE + CAPTION_SIZE, rc.bottom - BORDER_SIZE); dc.LineTo(BORDER_SIZE + CAPTION_SIZE, rc.bottom); dc.MoveTo(rc.right - (BORDER_SIZE + CAPTION_SIZE), 0); dc.LineTo(rc.right - (BORDER_SIZE + CAPTION_SIZE), BORDER_SIZE); dc.MoveTo(rc.right - (BORDER_SIZE + CAPTION_SIZE), rc.bottom - BORDER_SIZE); dc.LineTo(rc.right - (BORDER_SIZE + CAPTION_SIZE), rc.bottom); rc.DeflateRect(BORDER_SIZE, BORDER_SIZE); dc.Rectangle(&rc); } UINT CMvszDlg::OnNcHitTest(CPoint point) { ScreenToClient(&point); CRect rc; GetClientRect(&rc); CRect rcTest = rc; rcTest.DeflateRect(BORDER_SIZE, BORDER_SIZE); int iBottom = rcTest.bottom; rcTest.bottom = rcTest.top + CAPTION_SIZE; if(rcTest.PtInRect(point)) return HTCAPTION; rcTest.top = rcTest.bottom; rcTest.bottom = iBottom; if(rcTest.PtInRect(point)) return HTCLIENT; rcTest.SetRect(0, 0, BORDER_SIZE + CAPTION_SIZE, BORDER_SIZE + CAPTION_SIZE); if(rcTest.PtInRect(point)) return HTTOPLEFT; rcTest.SetRect(BORDER_SIZE + CAPTION_SIZE, 0, rc.right - (BORDER_SIZE + CAPTION_SIZE), BORDER_SIZE + CAPTION_SIZE); if(rcTest.PtInRect(point)) return HTTOP; rcTest.SetRect(rc.right - (BORDER_SIZE + CAPTION_SIZE), 0, rc.right, BORDER_SIZE + CAPTION_SIZE); if(rcTest.PtInRect(point)) return HTTOPRIGHT; rcTest.SetRect(0, BORDER_SIZE + CAPTION_SIZE, BORDER_SIZE, rc.bottom - (BORDER_SIZE + CAPTION_SIZE)); if(rcTest.PtInRect(point)) return HTLEFT; rcTest.SetRect(rc.right - BORDER_SIZE, BORDER_SIZE + CAPTION_SIZE, rc.right, rc.bottom - (BORDER_SIZE + CAPTION_SIZE)); if(rcTest.PtInRect(point)) return HTRIGHT; rcTest.SetRect(0, rc.bottom - (BORDER_SIZE + CAPTION_SIZE), BORDER_SIZE + CAPTION_SIZE, rc.bottom); if(rcTest.PtInRect(point)) return HTBOTTOMLEFT; rcTest.SetRect(BORDER_SIZE + CAPTION_SIZE, rc.bottom - BORDER_SIZE, rc.right - (BORDER_SIZE + CAPTION_SIZE), rc.bottom); if(rcTest.PtInRect(point)) return HTBOTTOM; rcTest.SetRect(rc.right - (BORDER_SIZE + CAPTION_SIZE), rc.bottom - (BORDER_SIZE + CAPTION_SIZE), rc.right, rc.bottom); if(rcTest.PtInRect(point)) return HTBOTTOMRIGHT; return HTERROR; } |