C#+WindowsAPI操纵系统菜单
⼀、前⾔
本⽂针对C#.NET中没有提供直接的类似SystemMenu的属性或类似GetSystemMenu的成员函数的情况,通过调⽤Windows API设计了⼀个C#类SystemMenu,从⽽实现了传统的对于系统菜单的操作。
⼆、系统菜单简介
当你单击窗⼝图标或右击窗⼝标题栏时系统菜单即弹出。它包含当前窗⼝的默认⾏为。不同窗⼝的系统菜单看起来有些不同,如⼀个正常窗⼝的系统菜单看起来与⼀个⼯具栏⼦对话框窗⼝的菜单就不⼀样。
修改系统菜单的好处:
·添加应⽤程序⾃⼰定义的菜单项。
·在WW被最⼩化时,SS是⼀个很好的地⽅来放置动作,可以被存取,因为SS可以显⽰,通过在任务栏窗⼝图标上单击右键。
·使某菜单项失去能⼒,如从系统菜单中移去“最⼤化”,“最⼩化”“关闭”等。由于这种改动还影响到窗⼝右上⾓的三个按钮,所以这是⼀个使窗⼝右上⾓“X”失去能⼒的不错的办法。
操纵系统菜单
通过调⽤ API函数GetSystemMenu,你就检索到了系统菜单的⼀个拷贝。该函数的第⼆个参数指明是否你要复位系统菜单到它的缺省状态。再加上另外⼏个API菜单函数如AppendMenu, InsertMenu等,你就能实现对于系统菜单的灵活控制。
下⾯我仅简单介绍如何添加菜单项以及如何实现新项与⽤户的交互。
三、SystemMenu 类介绍system的头文件
SystemMenu类的实现使得整个系统菜单存取变得⾮常容易。你可以使⽤这个类来修改⼀个窗⼝的菜单。 通过调⽤静态成员函数FromForm你得到⼀个对象,该函数要求⼀个Form对象或⼀个从Form继承的类作为它的参数。然后它创建⼀个新的对象,当然如果GetSystemMenu API调⽤失败的话,将引发⼀个NoSystemMenuException例外。
注意,每⼀个Windows API菜单函数要求⼀个菜单句柄以利于操作。因为菜单句柄实际上是⼀个C++指针,所以在.NET中你要使⽤IntPtr 来操作它。许多函数还需要⼀个位掩码标志来指明新菜单项的动作或形式。幸运的是,你不必象在VC++中那样,通过某个头⽂件的包含来使⽤⼀系列的位掩码标志定义,.NET中已经提供了⼀个现成的公共枚举类ItemFlags。下⾯对这个类的⼏个重要成员作⼀说明:
·
mfString―― 告诉⼦系统将显⽰由菜单项中的“Item”参数传递的字符串。
·mfSeparator――此时 "ID" 与 "Item" 参数被忽略。
·MfBarBreak―― 当⽤于菜单条时,其功能与mfBreak⼀样;当⽤于下拉菜单,⼦菜单或快捷菜单时,新的⼀列与旧有的⼀列由⼀线垂直线所隔开。
·MfBreak――把当前项⽬放在⼀个新⾏(菜单条)或新的⼀列(下拉菜单,⼦菜单或快捷菜单)。
注意:如果指定多个标志,应该⽤位操作运算符|(或)连接。例如:
mySystemMenu.AppendMenu(myID, "Test", ItemFlags.mfString |ItemFlags.mfChecked);
“Item”参数指定了新项中要显⽰的⽂本,其ID必须是唯⼀的数字――⽤来标志该菜单项。
注意:确保新项的ID⼤于0⼩于0XF000。因为⼤于等于0XF000的范围为系统命令所保留使⽤。你也可以调⽤类SystemMenu的静态⽅法VerifyItemID来核验是否你的ID正确。
另外,还有两个需要解释的常量:mfByCommand和mfByPosition。
第⼀,在缺省情况下,使⽤mfByCommand。第⼆,“Pos”的解释依赖于这些标志:如果你指定mfByCo
mmand,“Pos”参数就是在新项⽬插⼊前项⽬的ID;如果你指定mfByPosition,“Pos”参数就是以0索引为开头的新项的相对位置;如果是-1并且指定mfByPosition,该项⽬将被插⼊到最后。这也正是为什么AppendMenu()可以为InsertMenu()所取代的原因。
四、SystemMenu 类代码分析
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
public class NoSystemMenuException : System.Exception
{}
//这些值来⾃于MSDN
public enum ItemFlags
{
// The item ...
mfUnchecked = 0x00000000, // ... is not checked
mfString = 0x00000000, // ... contains a string as label mfDisabled = 0x00000002, // ... is disabled
mfGrayed = 0x00000001, // ... is grayed
mfChecked = 0x00000008, // ... is checked
mfPopup = 0x00000010, // ... Is a popup menu. Pass the
// menu handle of the popup
// menu into the ID parameter.
mfBarBreak = 0x00000020, // ... is a bar break
mfBreak = 0x00000040, // ... is a break
mfByPosition = 0x00000400, // ... is identified by the position mfByCommand = 0x00000000, // ... is identified by its ID mfSeparator = 0x00000800 // ... is a seperator (String and
// ID parameters are ignored).
}
public enum WindowMessages
{
wmSysCommand = 0x0112
}
//
/// 帮助实现操作系统菜单的类的定义
///.
//注意:⽤P/Invoke调⽤动态链接库中⾮托管函数时,应执⾏如下步骤://1,定位包含该函数的DLL。
//2,把该DLL库装载⼊内存。
/
/3,到即将调⽤的函数地址,并将所有的现场压⼊堆栈。
//4,调⽤函数。
//
public class SystemMenu
{
// 提⽰:C#把函数声明为外部的,⽽且使⽤属性DllImport来指定DLL
//和任何其他可能需要的参数。
// ⾸先,我们需要GetSystemMenu() 函数
// 注意这个函数没有Unicode 版本
[DllImport("USER32", EntryPoint="GetSystemMenu", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.Winapi)]
private static extern IntPtr apiGetSystemMenu(IntPtr WindowHandle,int bReset);
// 还需要AppendMenu()。 既然 .NET 使⽤Unicode,
// 我们应该选取它的Unicode版本。
[DllImport("USER32", EntryPoint="AppendMenuW", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.Winapi)]
private static extern int apiAppendMenu( IntPtr MenuHandle, int Flags,int NewID, String Item );
//还可能需要InsertMenu()
[DllImport("USER32", EntryPoint="InsertMenuW", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.Winapi)]
private static extern int apiInsertMenu ( IntPtr hMenu, int Position,int Flags, int NewId,String Item ); private IntPtr m_SysMenu = IntPtr.Zero; // 系统菜单句柄
public SystemMenu( )
{}
// 在给定的位置(以0为索引开始值)插⼊⼀个分隔条
public bool InsertSeparator ( int Pos )
{
return ( InsertMenu(Pos, ItemFlags.mfSeparator |ItemFlags.mfByPosition, 0, "") );
}
// 简化的InsertMenu(),前提――Pos参数是⼀个0开头的相对索引位置
public bool InsertMenu ( int Pos, int ID, String Item )
{
return ( InsertMenu(Pos, ItemFlags.mfByPosition |ItemFlags.mfString, ID, Item) ); }
// 在给定位置插⼊⼀个菜单项。具体插⼊的位置取决于Flags
public bool InsertMenu ( int Pos, ItemFlags Flags, int ID, String Item )
{
return ( apiInsertMenu(m_SysMenu, Pos, (Int32)Flags, ID, Item) == 0);
}
// 添加⼀个分隔条
public bool AppendSeparator ( )
{
return AppendMenu(0, "", ItemFlags.mfSeparator);
}
// 使⽤ItemFlags.mfString 作为缺省值
public bool AppendMenu ( int ID, String Item )
{
return AppendMenu(ID, Item, ItemFlags.mfString);
}
// 被取代的函数
public bool AppendMenu ( int ID, String Item, ItemFlags Flags )
{
return ( apiAppendMenu(m_SysMenu, (int)Flags, ID, Item) == 0 );
}
/
/从⼀个Form对象检索⼀个新对象
public static SystemMenu FromForm ( Form Frm )
{
SystemMenu cSysMenu = new SystemMenu();
cSysMenu.m_SysMenu = apiGetSystemMenu(Frm.Handle, 0);
if ( cSysMenu.m_SysMenu == IntPtr.Zero )
{ // ⼀旦失败,引发⼀个异常
throw new NoSystemMenuException();
}
return cSysMenu;
}
/
/ 当前窗⼝菜单还原 public static void ResetSystemMenu ( Form Frm )
{
apiGetSystemMenu(Frm.Handle, 1);
}
// 检查是否⼀个给定的ID在系统菜单ID范围之内
public static bool VerifyItemID ( int ID )
{
return (bool)( ID < 0xF000 && ID > 0 );
}
}
你可以使⽤静态⽅法ResetSystemMenu把窗⼝的系统菜单设置为原来状态――这在应⽤程序遇到错误或没有正确修改菜单时是很有⽤的。
五、使⽤SystemMenu类
private SystemMenu m_SystemMenu = null;
// ID 常数定义
private const int m_AboutID = 0x100;
private const int m_ResetID = 0x101;
private void frmMain_Load(object sender, System.EventArgs e)
{
try
{
m_SystemMenu = SystemMenu.FromForm(this);
// 添加⼀个separator ...
m_SystemMenu.AppendSeparator();
// 添加"关于" 菜单项
m_SystemMenu.AppendMenu(m_AboutID, "关于");
// 在菜单顶部加上"复位"菜单项
m_SystemMenu.InsertSeparator(0);
m_SystemMenu.InsertMenu(0, m_ResetID, "复位系统菜单");
}
catch ( NoSystemMenuException /* err */ )
{
// 建⽴你的错误处理器
}
}
六、检测⾃定义的菜单项是否被点击