用C#和本地WindowsAPI操纵系统菜单一、前言.NET框架是Windows应用领域中一个非常新的技术,可以肯定在未来的一段时间内,.NET应用必须与现存的Windows技术交互作用。这种交互作用主要体现在两个领域:COM和应用编程接口(API)。为此,.NET框架在WindowsAPI之上提供了一个OO层,但是有时候可能需要使用一个.NET不可到达的API调用。在这种情况下,可以使用.NET平台调用(P/Invoke)机制从.NET中调用C或C++函数。因为WindowsAPI函数在DLL中,所以,P/Invoke为从.NET代码调用DLL中的C或C++函数提供了一种通用机制。本文针对C#.NET中没有提供直接的类似SystemMenu的属性或类似GetSystemMenu的成员函数的实际,编写了一个C#类SystemMenu,从而实现了传统的对于系统菜单的操作,这是通过调用本地WindowsAPI来完成的。二、系统菜单简介当你单击窗口图标或右击窗口标题栏时系统菜单即弹出。它包含当前窗口的默认行为。不同窗口的系统菜单看起来有些不同,如一个正常的窗口的系统菜单看起来与一个工具栏子对话框窗口的菜单就不一样。修改系统菜单的好处:·添加应用程序自己定义的菜单项。·在WW被最小化时,SS是一个很好的地方来放置动作,可以被存取,因为SS可以显示,通过在任务栏窗口图标上单击右键。·使某菜单项失去能力,如从系统菜单中移去“最大化”,“最小化”“关闭”等。由于这种改动还影响到窗口右上角的三个按钮,所以这是一个使窗口右上角“X”失去能力的不错的办法。操纵系统菜单通过调用API函数GetSystemMenu,你就检索到了系统菜单的一个拷贝。该函数的第二个参数指明是否你要复位系统菜单到它的缺省状态。再加上另外几个API菜单函数如AppendMenu,InsertMenu等,你就能实现对于系统菜单的灵活控制。下面我仅简单介绍如何添加菜单项以及如何实现新项与用户的交互。三、SystemMenu类介绍SystemMenu类的实现使得整个系统菜单存取容易许多。你可以使用这个类来修改一个窗口的菜单。通过调用静态成员函数FromForm你得到一个对象,该函数要求一个Form对象或一个从Form继承的类作为它的参数。然后它创建一个新的对象,当然如果GetSystemMenuAPI调用失败的话,将引发一个NoSystemMenuException例外。注意,每一个WindowsAPI菜单函数要求一个菜单句柄以利于操作。因为菜单句柄实际上是一个C++指针,所以在.NET中你要使用IntPtr来操作它。许多函数还需要一个位掩码标志来指明新菜单项的动作或形式。幸运的是,你不必象在VC++中那样,通过某个头文件的包含来使用一系列的位掩码标志定义,.NET中已经提供了一个现成的公共枚举类ItemFlags。下面对这个类的几个重要成员作一说明:·mfString――告诉子系统将显示由菜单项中的“Item”参数传递的字符串。·mfSeparator――此时ID与Item参数被忽略。·MfBarBreak――当用于菜单条时,其功能与mfBreak一样;当用于下拉菜单,子菜单或快捷菜单时,新的一列与旧有的一列由一线垂直线所隔开。·MfBreak――把当前项目放在一个新行(菜单条)或新的一列(下拉菜单,子菜单或快捷菜单)。注意:如果指定多个标志,应该用位操作运算符|(或)连接。例如://将创建一个菜单项Test,且该项被选中(checked)mySystemMenu.AppendMenu(myID,Test,ItemFlags.mfString|ItemFlags.mfChecked);“Item”参数指定了新项中要显示的文本,其ID必须是唯一的数字――用来标志该菜单项。注意:确保新项的ID大于0小于0XF000。因为大于等于0XF000的范围为系统命令所保留使用。你也可以调用类SystemMenu的静态方法VerifyItemID来核验是否你的ID正确。另外,还有两个需要解释的常量:mfByCommand和mfByPosition。第一,在缺省情况下,使用mfByCommand。第二,“Pos”的解释依赖于这些标志:如果你指定mfByCommand,“Pos”参数就是在新项目插入前项目的ID;如果你指定mfByPosition,“Pos”参数就是以0索引为开头的新项的相对位置;如果是-1并且指定mfByPosition,该项目将被插入到最后。这也正是为什么AppendMenu()可以为InsertMenu()所取代的原因。四、SystemMenu类代码分析usingSystem;usingSystem.Windows.Forms;usingSystem.Diagnostics;usingSystem.Runtime.InteropServices;publicclassNoSystemMenuException:System.Exception{}//这些值来自于MSDNpublicenumItemFlags{//Theitem...mfUnchecked=0x00000000,//...isnotcheckedmfString=0x00000000,//...containsastringaslabelmfDisabled=0x00000002,//...isdisabledmfGrayed=0x00000001,//...isgrayedmfChecked=0x00000008,//...ischeckedmfPopup=0x00000010,//...Isapopupmenu.Passthe//menuhandleofthepopup//menuintotheIDparameter.mfBarBreak=0x00000020,//...isabarbreakmfBreak=0x00000040,//...isabreakmfByPosition=0x00000400,//...isidentifiedbythepositionmfByCommand=0x00000000,//...isidentifiedbyitsIDmfSeparator=0x00000800//...isaseperator(Stringand//IDparametersareignored).}publicenumWindowMessages{wmSysCommand=0x0112}////帮助实现操作系统菜单的类的定义///.//注意:用P/Invoke调用动态链接库中非托管函数时,应执行如下步骤://1,定位包含该函数的DLL。//2,把该DLL库装载入内存。//3,找到即将调用的函数地址,并将所有的现场压入堆栈。//4,调用函数。//publicclassSystemMenu{//提示:C#把函数声明为外部的,而且使用属性DllImport来指定DLL//和任何其他可能需要的参数。//首先,我们需要GetSystemMenu()函数//注意这个函数没有Unicode版本[DllImport(USER32,EntryPoint=GetSystemMenu,SetLastError=true,CharSet=CharSet.Unicode,ExactSpelling=true,CallingConvention=CallingConvention.Winapi)]privatestaticexternIntPtrapiGetSystemMenu(IntPtrWindowHandle,intbReset);//还需要AppendMenu()。既然.NET使用Unicode,//我们应该选取它的Unicode版本。[DllImport(USER32,EntryPoint=AppendMenuW,SetLastError=true,CharSet=CharSet.Unicode,ExactSpelling=true,CallingConvention=CallingConvention.Winapi)]privatestaticexternintapiAppendMenu(IntPtrMenuHandle,intFlags,intNewID,StringItem);//还可能需要InsertMenu()[DllImport(USER32,EntryPoint=InsertMenuW,SetLastError=true,CharSet=CharSet.Unicode,ExactSpelling=true,CallingConvention=CallingConvention.Winapi)]privatestaticexternintapiInsertMenu(IntPtrhMenu,intPosition,intFlags,intNewId,StringItem);privateIntPtrm_SysMenu=IntPtr.Zero;//系统菜单句柄publicSystemMenu(){}//在给定的位置(以0为索引开始值)插入一个分隔条publicboolInsertSeparator(intPos){return(InsertMenu(Pos,ItemFlags.mfSeparator|ItemFlags.mfByPosition,0,));}//简化的InsertMenu(),前提――Pos参数是一个0开头的相对索引位置publicboolInsertMenu(intPos,intID,StringItem){return(InsertMenu(Pos,ItemFlags.mfByPosition|ItemFlags.mfString,ID,Item));}//在给定位置插入一个菜单项。具体插入的位置取决于FlagspublicboolInsertMenu(intPos,ItemFlagsFlags,intID,StringItem){return(apiInsertMenu(m_SysMenu,Pos,(Int32)Flags,ID,Item)==0);}//添加一个分隔条publicboolAppendSeparator(){returnAppendMenu(0,,ItemFlags.mfSeparator);}//使用ItemFlags.mfString作为缺省值publicboolAppendMenu(intID,StringItem){returnAppendMenu(ID,Item,ItemFlags.mfString);}//被取代的函数publicboolAppendMenu(intID,StringItem,ItemFlagsFlags){return(apiAppendMenu(m_SysMenu,(int)Flags,ID,Item)==0);}//从一个Form对象检索一个新对象publicstaticSystemMenuFromForm(FormFrm){SystemMenucSysMenu=newSystemMenu();cSysMenu.m_SysMenu=apiGetSystemMenu(Frm.Handle,0);if(cSysMenu.m_SysMenu==IntPtr.Zero){//一旦失败,引发一个异常thrownewNoSystemMenuException();}returncSysMenu;}//当前窗口菜单还原publicstaticvoidResetSystemMenu(FormFrm){apiGetSystemMenu(Frm.Handle,1);}//检查是否一个给定的ID在系统菜单ID范围之内publicstaticboolVerifyItemID(intID){return(bool)(ID0xF000&&ID0);}}你可以使用静态方法ResetSystemMenu把窗口的系统菜单设置为原来状态――这在应用程序遇到