如何把菜单栏和标题栏合为一体
你会发现,现在越来越多的桌面应用程序将菜单栏和标题栏合为一体。要实现这种效果,一般有两种方案:
- 将菜单绘制到标题栏上
- 移除标题栏,把菜单栏当标题用
众所周知,标题栏和菜单栏都是典型的非客户区,而在Windows平台上,非客户区的自绘对很多程序猿来说,真的是痛苦不堪啊。因此方案一自然而然就被淘汰了。既然如此,那么如何用方案二实现我们要的效果呢?
这里,先把“效果”简单说下:应用程序的界面上,标题栏和菜单栏在同一个区域显示,用户点击“菜单区域”,弹出对应的菜单;用户点击其他空白区域,如同点击了标题栏 (表述不清,不过我想大家都理解了吧……)。
方案二里,实际上包含了两个要点:
- 移除标题栏
- 用菜单栏模拟标题栏
如何移除标题栏,这个简单,移除WS_CAPTION窗口风格即可。不过要注意的是,不要忘记调用SetWindowPos并传入SWP_FRAMECHANGED标志位。那么如何让菜单栏模拟标题栏的功能呢?说到这里,要先补充一点基础知识。
在Windows上,你如何知道鼠标所在的点位于当前窗口的哪一个部分?这要借助一个消息:WM_NCHITTEST。通过这个消息,我们知道一个窗口被细分出了20多个区域,其中有两个就是我们现在感兴趣的,HTMENU和HTCAPTION。现在想法来了,在自己处理WM_NCHITTEST消息时,如果能把系统返回的部分HTMENU转化成HTCAPTION,是不是就能达到模拟标题栏的效果了呢?事实告诉我们,基本上是达到了。代码如下:
1: UINT Cls_OnNCHitTest(HWND hwnd, int x, int y)
2: {
3: UINT uNcHitResult = FORWARD_WM_NCHITTEST(hwnd, x, y, DefWindowProc);
4:
5: if (HTMENU == uNcHitResult)
6: {
7: HMENU hMenu = GetMenu(hwnd);
8: int nMenuItemCount = GetMenuItemCount(hMenu);
9:
10: RECT rcTheLastMenu = { 0, 0, 0, 0 };
11: if (GetMenuItemRect(hwnd, hMenu, nMenuItemCount - 1, &rcTheLastMenu))
12: {
13: // 当点击在菜单空白区域时,模拟成标题栏行为
14: if (rcTheLastMenu.right < x)
15: {
16: uNcHitResult = HTCAPTION;
17: }
18: }
19: }
20:
21: return uNcHitResult;
22: }
经过这样的处理后,单击/双击菜单栏空白区域,以及拖拽等都好像是在操作标题栏一样。
写完这些代码之后,你会发现几个问题:
- 右键单击菜单栏空白区域看不到系统菜单
- 最大化后全屏了,窗口覆盖了任务栏
所以说,这种模拟方式只是基本实现了功能,还没有完全达到要求。现在我们来一步一步解决这些问题。先看系统菜单。
要解决系统菜单的问题,首先要知道怎么获得系统菜单。这个可以通过GetSystemMenu的方式来实现。
接下来,在什么时候弹出菜单?这个问题也简单,一般我们选用WM_NCRBUTTONUP。怎么弹的问题交给TrackPopupMenuEx。代码如下:
1: void Cls_OnNCRButtonUp(HWND hwnd, int x, int y, UINT codeHitTest)
2: {
3: if (HTCAPTION == codeHitTest)
4: {
5: HMENU hSysMenu = GetSystemMenu(hwnd, FALSE);
6: int nMenuAlign = GetSystemMetrics(SM_MENUDROPALIGNMENT);
7:
8: // 特别注意TPM_RETURNCMD,我们需要知道用户到底点中了哪一个系统菜单项
9: BOOL bRes = TrackPopupMenuEx(hSysMenu, nMenuAlign | TPM_RETURNCMD, x, y, hwnd, NULL);
10: return FORWARD_WM_SYSCOMMAND(hwnd, bRes, x, y, DefWindowProc);
11: }
12:
13: FORWARD_WM_NCRBUTTONUP(hwnd, x, y, codeHitTest, DefWindowProc);
14: }
到目前为止,我们看似已经把菜单的问题全部解决了。但事实上,问题还是有一点的。系统菜单项的状态好像有点问题。要解决这个问题,我们要借助WM_INITMENU消息,通过当前窗口的最大化或最小化状态,挑战对应的菜单项状态。请看代码:
1: void Cls_OnInitMenu(HWND hwnd, HMENU hMenu)
2: {
3: if (GetSystemMenu(hwnd, FALSE) == hMenu)
4: {
5: UINT uSysCmds[] = {SC_SIZE, SC_MOVE, SC_MINIMIZE, SC_MAXIMIZE, SC_CLOSE, SC_RESTORE};
6: if (IsIconic(hwnd))
7: {
8: for (int i = 0; i < _countof(uSysCmds); ++i)
9: {
10: switch (uSysCmds[i])
11: {
12: case SC_MAXIMIZE:
13: case SC_RESTORE:
14: case SC_CLOSE:
15: EnableMenuItem(hMenu, uSysCmds[i], MF_ENABLED);
16: break;
17:
18: default:
19: EnableMenuItem(hMenu, uSysCmds[i], MF_DISABLED | MF_GRAYED);
20: break;
21: }
22: }
23: }
24: else if (IsZoomed(hwnd))
25: {
26: for (int i = 0; i < _countof(uSysCmds); ++i)
27: {
28: switch (uSysCmds[i])
29: {
30: case SC_RESTORE:
31: case SC_MINIMIZE:
32: case SC_CLOSE:
33: EnableMenuItem(hMenu, uSysCmds[i], MF_ENABLED);
34: break;
35:
36: default:
37: EnableMenuItem(hMenu, uSysCmds[i], MF_DISABLED | MF_GRAYED);
38: break;
39: }
40: }
41: }
42: else
43: {
44: for (int i = 0; i < _countof(uSysCmds); ++i)
45: {
46: switch (uSysCmds[i])
47: {
48: case SC_RESTORE:
49: EnableMenuItem(hMenu, uSysCmds[i], MF_DISABLED | MF_GRAYED);
50: break;
51:
52: default:
53: EnableMenuItem(hMenu, uSysCmds[i], MF_ENABLED);
54: break;
55: }
56: }
57: }
58: }
59: else
60: {
61: FORWARD_WM_INITMENU(hwnd, hMenu, DefWindowProc);
62: }
63: }
大功告成啦。唉,好像还有点不对,第一次弹出的系统菜单状态好像有问题,再弹出来,好像就对了,咋回事?这要怪GetSystemMenu了。要解决这个问题,只要在没有显示系统菜单前就先调用下GetSystemMenu做一份拷贝先。
至此,系统菜单的问题应该全部都解决了。下面就是要解决全屏的问题了。
未完。。。。。
posted on 2012-04-07 17:35 wpcockroach 阅读(3118) 评论(2) 编辑 收藏 举报