FlatMenusForm - 一个类,它使默认的 3D 菜单看起来很平坦






3.27/5 (19投票s)
2003年6月1日
4分钟阅读

120059

675
如何更改 Windows 9x、NT 和 WinXP 经典外观菜单的默认 3D 外观
这是菜单的默认外观
这是菜单的新外观
引言
我想学习如何更改 Windows XP 经典外观下菜单的默认 3D 外观,因为在实现属主绘制菜单时,Windows 只提供菜单窗口的客户区供我绘制。当使用 Windows XP 外观时,它的平坦菜单效果很好,但切换到经典外观时——这太糟糕了,3D 边框根本不适合菜单项。然后,我开始在 .NET Framework SDK 中搜索解决我问题的方法,最终我放弃了——那里没有任何类或 enum
或其他东西……然后(我有点固执,想学习其中的诀窍),我深入研究了 Platform SDK 和 Win32 API……
基本思想
简而言之,这个想法是子类化 Windows 为其菜单提供的默认窗口类。这种子类化是通过使用 P/Invokes 并调用本机 API 来完成的。
Using the Code
首先,如何让您的菜单拥有平坦的外观——只需继承自 Base
类即可!我在 FlatMenuForm
中添加了两个额外的属性
BorderColor
- 用它来改变菜单周围的边框颜色MenuStyle
- 这是一个枚举,包含两个字段——Flat
和Default
。使用MenuStyle.Default
使用默认菜单外观,而使用另一个字段实现平坦外观。
现在让我们从头开始——我进行菜单窗口自定义绘制的第一次尝试。起初,我尝试了这样的代码
IntPtr hdc = GetWindowDC(mainMenu1.Handle);
Graphics g = Graphics.FromHdc(hdc);
Rectangle r = new Rectangle(0,0,(int)g.VisibleClipBounds.Width-1,
(int)g.VisibleClipBounds.Height-1);
g.DrawRectangle(new Pen(borderColor),r);
Win32.ReleaseDC(mainMenu1.Handle,hdc);
g.Dispose();
嗯,这根本不起作用,因为我总是收到一个“Out of Memory
”异常(这是因为,当我跟踪原因时,在调用 GetWindowDC
时会抛出一个 INVALID_WINDOW_HANDLE
win32 错误)。我尝试使用 MenuItem.Handle
属性运行这段代码,但得到了相同的异常。然后我开始阅读关于子类化窗口和更改其默认 WndProc
的内容。然后一个想法出现了——为什么不先尝试子类化菜单窗口呢……到目前为止一切似乎都很好,但是当我没有有效的窗口句柄时,如何子类化窗口呢?现在,SetWindowsHookEx
API 出现了,并指定了 WH_CALLWNDPROC
挂钩类型——它安装一个挂钩过程,该过程在系统将消息发送到目标窗口过程之前监视这些消息。
hookHandle = SetWindowsHookEx(4,hookProc,IntPtr.Zero,Win32.GetWindowThreadProcessId(Handle,0));
//WH_CALLWNDPROC is defined as 4 in winuser.h
该函数的第二个参数非常重要——它是我的 HookProc
的地址,它将监视特殊消息(顺便说一句,当您需要在托管代码中声明 API 并且有一个函数指针时,您可以使用 delegate
)。这是 HookProc
委托的声明
delegate int HookProc(int code, IntPtr wparam, ref Win32.CWPSTRUCT cwp);
CWPSTRUCT
结构定义了传递给 WH_CALLWNDPROC
挂钩过程的消息参数
[StructLayout(LayoutKind.Sequential)]
public struct CWPSTRUCT
{
public IntPtr lparam;
public IntPtr wparam;
public int message;
public IntPtr hwnd;
}
由于我在窗体构造函数中放置了 SetWindowsHookEx
调用,以便在窗体构建时挂钩窗口(我的意思是主窗体窗口)。这是 Hook
过程的实现
int Hooked(int code, IntPtr wparam, ref Win32.CWPSTRUCT cwp)
{
switch(code)
{
case 0: //HC_ACTION -> this means that the hook
//procedure should process the message contained in CWPSTRUCT
switch(cwp.message)
{
case 0x0001://WM_CREATE - catch this before the window is created
string s = string.Empty;
char[] className = new char[10];
//Get the window class name
int length = Win32.GetClassName(cwp.hwnd,className,9);
//Convert it to string
for(int i=0;i<length;i++)
s += className[i];
//Now check if the window is a menu
if(s == "#32768")//System class for menu
//if true - subclass the window
defaultWndProc = SetWindowLong(cwp.hwnd, (-4), subWndProc);
break;
}
break;
}
return Win32.CallNextHookEx(hookHandle,code,wparam, ref cwp);
//Let other applications use hook codes
}
另一个巨大的困难是获取正确的窗口类。这是一个系统定义的类,仅供系统使用,但在 Platform SDK 文档中给出了它的名称——它是“#32768
”。当我获得正确的类时——使用 SetWindowLong
API 和 GWL_WNDPROC
值来子类化它,该值设置窗口过程的新地址((-4)代表 GWL_WNDPROC
)。subWndProc
参数是类型为 MyWndProc
的委托
delegate int MyWndProc(IntPtr hwnd,int msg,IntPtr wparam,IntPtr lparam);
SubclassWndProc
的实现
int SubclassWndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam)
{
//tracer.Items.Add(msg.ToString());
switch(msg)
{
case 0x0085: //WM_NCPAINT
IntPtr menuDC = Win32.GetWindowDC(hwnd);
Graphics g = Graphics.FromHdc(menuDC);
DrawBorder(g);
Win32.ReleaseDC(hwnd,menuDC);
g.Dispose();
return 0;
case 0x0317: //WM_PRINT
int result = Win32.CallWindowProc(defaultWndProc,hwnd,msg,wparam,lparam);
menuDC = wparam;
g = Graphics.FromHdc(menuDC);
//Draw the border around the menu
DrawBorder(g);
Win32.ReleaseDC(hwnd,menuDC);
g.Dispose();
return result;
}
return Win32.CallWindowProc(defaultWndProc,hwnd,msg,wparam,lparam);//In all other cases
//use default wndproc
}
这可能看起来很奇怪,但是 Windows 在 WM_NCPAINT
之后发送一个 WM_PRINT
消息。我花了几个小时试图理解我的代码为什么出了问题并且不起作用,直到我往 SubclassWndProc
中添加了一个简单的追踪器,并找出发送到菜单窗口的消息。在处理 WM_PRINT
消息时,一切都正常——BINGO!终于,我改变了菜单窗口的默认外观!
我还想处理 WM_NCCALCSIZE
消息,以便减小菜单的非客户区,但失败了……在托管代码中如何做到这一点(我在 MFC 中实现了它)将非常感激!另外,我无法覆盖 WM_WINDOWPOSCHANGING
和 WM_WINDOWPOSCHANGED
的默认实现——我丢失了默认的系统动画……
还有一件事——我没有在其他平台上测试过这段代码(我的平台是 Windows XP),所以如果您发现任何错误,请告诉我!!!
关注点
我还添加了属主绘制菜单的平坦外观实现,在某种程度上,它们现在看起来确实像 Visual Studio .NET 的菜单!我还没有实现顶部菜单项右侧的阴影。也许可以通过获取桌面窗口 DC,在其上绘制,然后使该矩形失效来完成——如果我有足够的时间,我会尝试的。
这是我最终得到的结果
好了,就这样!
再次声明——任何评论、建议甚至批评都欢迎!
历史
- 2003 年 6 月 1 日:第一次修订
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。