科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件C#Windows API操纵系统菜单

C#Windows API操纵系统菜单

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文针对C#.NET中没有提供直接的类似SystemMenu的属性或类似GetSystemMenu的成员函数的实际,编写了一个C#类SystemMenu,从而实现了传统的对于系统菜单的操作

作者:朱先忠编译 来源:天极开发 2007年11月11日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
四、SystemMenu 类代码分析

using System;
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类

// 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 */ )

{

// 建立你的错误处理器

}

}

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

  这是较难实现的部分。因为你必须重载你的从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()成员函数传递的一样

 if ( msg.Msg == (int)WindowMessages.wmSysCommand )
 {
  switch ( msg.WParam.ToInt32() )
  {
   case m_ResetID: // reset菜单项的ID
   {
    if ( MessageBox.Show(this, "\tAre you sure?","Question", MessageBoxButtons.YesNo) ==
DialogResult.Yes )
    { // 复位系统菜单
     SystemMenu.ResetSystemMenu(this);
    }
   } break;
   case m_AboutID:
   { // “关于”菜单项
    MessageBox.Show(this, "作者: 朱先中 \n\n "+"e-mail: sdmyzxz@163.com", "关于");
   } break;
   // 这里可以针对另外的菜单项设计处理过程
  }
 }
 // 调用基类函数
 base.WndProc(ref msg);
}

  七、总结

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

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

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章