windowsapi:C#+Windows API操纵系统菜单

  本文举例源代码或素材下载

  、前言

  本文针对C#.NET中没有提供直接类似Menu属性或类似GetMenu成员情况通过Windows API设计了个C#类Menu从而实现了传统对于系统菜单操作

   2、系统菜单介绍

  当你单击窗口图标或右击窗口标题栏时系统菜单即弹出它包含当前窗口默认行为区别窗口系统菜单看起来有些区别个正常窗口系统菜单看起来和个工具栏子对话框窗口菜单就不

  修改系统菜单好处:

  ·添加应用自己定义菜单项

  ·在WW被最小化时SS是个很好地方来放置动作可以被存取SS可以显示通过在任务栏窗口图标上单击右键

  ·使某菜单项失去能力如从系统菜单中移去“最大化”“最小化”“关闭”等由于这种改动还影响到窗口右上角 3个按钮所以这是个使窗口右上角“X”失去能力不错办法

  操纵系统菜单

  通过 APIGetMenu你就检索到了系统菜单个拷贝第 2个参数指明是否你要复位系统菜单到它缺省状态再加上另外几个API菜单如AppendMenu, InsertMenu等你就能实现对于系统菜单灵活控制

  下面我仅简单介绍如何添加菜单项以及如何实现新项和用户交互

   3、Menu 类介绍

  Menu类实现使得整个系统菜单存取变得非常容易你可以使用这个类来修改个窗口菜单 通过静态成员FromForm你得到个对象要求个Form对象或个从Form继承类作为它参数然后它创建个新对象当然如果GetMenu API失败将引发个NoMenuException例外

  注意个Windows API菜单要求个菜单句柄以利于操作菜单句柄实际上是个C指针所以在.NET中你要使用IntPtr来操作它许多还需要个位掩码标志来指明新菜单项动作或形式幸运你不必象在VC中那样通过某个头文件包含来使用系列位掩码标志定义.NET中已经提供了个现成公共枚举类ItemFlags下面对这个类几个重要成员作介绍说明:

  ·mfString―― 告诉子系统将显示由菜单项中“Item”参数传递

  ·mfSeparator――此时 "ID" 和 "Item" 参数被忽略

  ·MfBarBreak―― 当用于菜单条时其功能和mfBreak样;当用于下拉菜单子菜单或快捷菜单时列和旧有列由线垂直线所隔开

  ·MfBreak――把当前项目放在个新行(菜单条)或新列(下拉菜单子菜单或快捷菜单)

  注意:如果指定多个标志应该用位操作运算符|(或)连接例如:

  //将创建个菜单项 "Test" 且该项被选中(checked)
myMenu.AppendMenu(myID, "Test", ItemFlags.mfString |ItemFlags.mfChecked);


  “Item”参数指定了新项中要显示文本其ID必须是唯数字――用来标志该菜单项

  注意:确保新项ID大于0小于0XF000大于等于0XF000范围为系统命令所保留使用你也可以Menu静态思路方法VeryItemID来核验是否你ID正确

  另外还有两个需要解释常量:mfByCommand和mfByPosition

  第在缺省情况下使用mfByCommand第 2“Pos”解释依赖于这些标志:如果你指定mfByCommand“Pos”参数就是在新项目插入前项目ID;如果你指定mfByPosition“Pos”参数就是以0索引为开头新项相对位置;如果是-1并且指定mfByPosition该项目将被插入到最后这也正是为什么AppendMenu可以为InsertMenu所取代原因

   4、Menu 类代码分析

  using ;
using .Windows.Forms;
using .Diagnostics;
using .Runtime.InteropServices;
public NoMenuException : .Exception
{}
//这些值来自于MSDN
public enum ItemFlags
{
 // The item ...
 mfUnchecked = 0x00000000, // ... is not checked
 mfString = 0x00000000, // ... contains a 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 o the ID parameter.
 mfBarBreak = 0x00000020, // ... is a bar
 mfBreak = 0x00000040, // ... is a
 mfByPosition = 0x00000400, // ... is identied by the position
 mfByCommand = 0x00000000, // ... is identied 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 Menu
{
 // 提示:C#把声明为外部而且使用属性DllImport来指定DLL
 //和任何其他可能需要参数
 // 首先我们需要GetMenu
 // 注意这个没有Unicode 版本
[DllImport("USER32", EntryPo="GetMenu", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.Winapi)]
private extern IntPtr apiGetMenu(IntPtr WindowHandle, bRe);
 // 还需要AppendMenu 既然 .NET 使用Unicode,
 // 我们应该选取它Unicode版本
 [DllImport("USER32", EntryPo="AppendMenuW", SetLastError=true,
 CharSet=CharSet.Unicode, ExactSpelling=true,
 CallingConvention=CallingConvention.Winapi)]
 private extern apiAppendMenu( IntPtr MenuHandle, Flags, NewID, String Item );
 //还可能需要InsertMenu
 [DllImport("USER32", EntryPo="InsertMenuW", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.Winapi)]
private extern apiInsertMenu ( IntPtr hMenu, Position, Flags, NewId,String Item );
private IntPtr m_SysMenu = IntPtr.Zero; // 系统菜单句柄
public Menu( )
{}
// 在给定位置(以0为索引开始值)插入个分隔条
public bool InsertSeparator ( Pos )
{
  ( InsertMenu(Pos, ItemFlags.mfSeparator |ItemFlags.mfByPosition, 0, "") );
}
// 简化InsertMenu前提――Pos参数是个0开头相对索引位置
public bool InsertMenu ( Pos, ID, String Item )
{
  ( InsertMenu(Pos, ItemFlags.mfByPosition |ItemFlags.mfString, ID, Item) );
}
// 在给定位置插入个菜单项具体插入位置取决于Flags
public bool InsertMenu ( Pos, ItemFlags Flags, ID, String Item )
{
  ( apiInsertMenu(m_SysMenu, Pos, (Int32)Flags, ID, Item) 0);
}
// 添加个分隔条
public bool AppendSeparator ( )
{
  AppendMenu(0, "", ItemFlags.mfSeparator);
}
// 使用ItemFlags.mfString 作为缺省值
public bool AppendMenu ( ID, String Item )
{
  AppendMenu(ID, Item, ItemFlags.mfString);
}
// 被取代
public bool AppendMenu ( ID, String Item, ItemFlags Flags )
{
  ( apiAppendMenu(m_SysMenu, ()Flags, ID, Item) 0 );
}
//从个Form对象检索个新对象
public Menu FromForm ( Form Frm )
{
 Menu cSysMenu = Menu;
 cSysMenu.m_SysMenu = apiGetMenu(Frm.Handle, 0);
  ( cSysMenu.m_SysMenu IntPtr.Zero )
 { // 旦失败引发个异常
  throw NoMenuException;
 }
  cSysMenu;
}
// 当前窗口菜单还原 public void ReMenu ( Form Frm )
{
 apiGetMenu(Frm.Handle, 1);
}
// 检查是否个给定ID在系统菜单ID范围的内
public bool VeryItemID ( ID )
{
  (bool)( ID < 0xF000 && ID > 0 );
}
}


  你可以使用静态思路方法ReMenu把窗口系统菜单设置为原来状态――这在应用遇到或没有正确修改菜单时是很有用

   5、使用Menu类

  // Menu 对象
private Menu m_Menu = null;
// ID 常数定义
private const m_AboutID = 0x100;
private const m_ReID = 0x101;
private void frmMain_Load(object sender, .EventArgs e)
{
try
{
m_Menu = Menu.FromForm(this);
// 添加个separator ...
m_Menu.AppendSeparator;
// 添加"有关" 菜单项
m_Menu.AppendMenu(m_AboutID, "有关");
// 在菜单顶部加上"复位"菜单项
m_Menu.InsertSeparator(0);
m_Menu.InsertMenu(0, m_ReID, "复位系统菜单");
}
catch ( NoMenuException /* err */ )
{
// 建立你处理器
}
}


   6、检测自定义菜单项是否被点击

  这是较难实现部分你必须重载你从Form或Control继承类WndProc成员你可以这样实现:

  protected override void WndProc ( ref Message msg )
{
 base.WndProc(ref msg);
}


  注意必须基类WndProc实现;否则不能正常工作

  现在我们来分析下如何重载WndProc首先应该截获WM_SYSCOMMAND消息当用户点击系统菜单项或者选择“最大化”按钮“最小化”按钮或者“关闭”按钮时我们要检索该消息特别注意消息对象WParam参数正好包含了被点击菜单项ID于是我们可以实现如下重载:

  protected override void WndProc ( ref Message msg )
{
 // 通过截取WM_SYSCOMMAND消息并进行处理
 // 注意消息WM_SYSCOMMAND被定义在WindowMessages枚举类中
 // 消息WParam参数包含点击ID
 // 该值和通过上面类InsertMenu或AppendMenu成员传递
  ( msg.Msg ()WindowMessages.wmSysCommand )
 {
  switch ( msg.WParam.ToInt32 )
  {
    m_ReID: // re菜单项ID
   {
     ( MessageBox.Show(this, "\tAre you sure?","Question", MessageBoxButtons.YesNo)
DialogResult.Yes )
    { // 复位系统菜单
     Menu.ReMenu(this);
    }
   } ;
    m_AboutID:
   { // “有关”菜单项
    MessageBox.Show(this, "作者: 朱先中 \n\n "+"e-mail: [email protected]", "有关");
   } ;
   // 这里可以针对另外菜单项设计处理过程
  }
 }
 // 基类
 base.WndProc(ref msg);
}


   7、整理总结

  实现上述目标个可能思路方法是通过创建个事件OnSysCommand并当消息WM_SYSCOMMAND传来时激活它然后把属性WParam传递给该事件句柄读者可以自行编程验证

  总的本文通过个简单系统菜单修改例子分析了用C#使用.NET平台机制来DLL中非托管基本步骤及注意事项所附源程在Windows2000 Server/ VS .NET2003下调试通过

Tags:  windowsapi函数 windowsapi编程 windowsapi

延伸阅读

最新评论

发表评论