如何用C#编程修改系统菜单
嘿,Philippe,至少在我有10年经验之前我不能称自己为C#专家,而且C#出现并不长。然而,我知道你的问题的答案:使用 GetSystemMenu。对,就如你在C++中一样。怎样做呢?自然是用 托管。
有时我感觉就像坏掉的唱片,因为如此多的C#问题,我都用相同的答案:托管。那是因为我得到的大部分问题都是 GUI 问题,并且 Windows 窗体目前只暴露基本的窗口子集。一旦你想做一些复杂的东西,你还必须返回到Win32®。幸运的是,Microsoft .NET Framework 托管服务使得它更容易。
如果有一种方法能用 Windows 窗体获得系统菜单,那么窗体类中就应该有一个类似SystemMenu的东西。啊哈,事实上没有这样的属性。 控件一般都用 Control.ContextMenu 得到上下文菜单,窗体用 Form.Menu 获得主菜单,但没有 SystemMenu 或是其它的属性 用 Menu 来直接存取系统菜单。这就是你要使用托管的原因。我写了一个小程序,SysMenu,来示范如何使用托管。Figure 5列出了代码。Figure 6为结果。
Figure 6 修改后的系统菜单
为使用GetSystemMenu API函数,首先声明托管方式, 用 DllImpor。. 对于SysMenu, 你实际上需要两个函数: GetSystemMenu 和 AppendMenu.
using System.Runtime.InteropServices;
public class Form1 : Form
{
[DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hwnd, int bRevert);
[DllImport("user32.dll")]
private static extern bool AppendMenu(IntPtr hMenu, MenuFlags uFlags, uint uIDNewItem, String lpNewItem);
}
你应该经常使用 IntPtr 来代替 HWNDs、HMENUs和其它类型的窗口句柄。对于 LPCTSTRs, 将参数声明为String类型。托管服务会在传给 Windows 之前将System::String自动转换为 LPCTSTR类型。对于 MenuFlags, 那是你必须自己定义的枚举:
public enum MenuFlags {
MF_INSERT = 0x00000000,
MF_CHANGE = 0x00000080,
ooo // etc
}
你不一定非要用枚举,但用枚举更安全。MF_XXX 值来自 WinUser.h。最后, 你需要一个新的命令 ID。在SysMenu中,IDC_MYCOMMAND值为 100. 如果你使用的值小于0xF000, 你要保证不和SC_MINIMIZE, SC_MAXIMIZE 或其它内建的系统命令冲突。同时也必须确保不和你自己的主菜单命令冲突。有了这些定义之后, 你便可以开始添加菜单项。所有需要做的只是在你的窗体构造函数中添加很少的代码。首先是获得系统菜单:
// Get system menu
IntPtr hSysMenu = GetSystemMenu(this.Handle, 0);
随后是加入你的命令:
// Add separator and new command
AppendMenu(hSysMenu,MenuFlags.MF_SEPARATOR,0,null);
AppendMenu(hSysMenu,MenuFlags.MF_BYCOMMAND, IDC_MYCOMMAND, "Do you like interop?");
现在当用户在窗口标题栏点击系统菜单,你的新菜单将显示,如 Figure 6所示。只是为了好玩,我给了它一个复选标记。但用户调用你的命令时会发生什么呢?目前,什么也不会发生。为处理这个命令,你必须重写窗体的虚拟 WndProc 方法:
const int WM_SYSCOMMAND = 0x0112;
protected override void WndProc(ref Message msg)
{
if (msg.Msg==WM_SYSCOMMAND) {
if (msg.WParam.ToInt32() == IDC_MYCOMMAND) {
// handle it!
return;
}
}
base.WndProc(ref msg);
}