一、前言

  .net框架是Windows应用领域中一个非常新的技术,可以肯定在未来的一段时间内,.NET应用必须与现存的Windows技术交互作用。这种交互作用主要体现在两个领域:COM和应用编程接口(API)。为此,.NET框架在Windows API之上提供了一个OO层,但是有时候可能需要使用一个.NET不可到达的API调用。在这种情况下,可以使用.NET平台调用(P/Invoke)机制从.NET中调用C或C++函数。因为Windows API函数在DLL中,所以,P/Invoke为从.NET代码调用DLL中的C或C++函数提供了一种通用机制。

  本文针对C#.NET中没有提供直接的类似SystemMenu的属性或类似GetSystemMenu的成员函数的实际,编写了一个C#类SystemMenu,从而实现了传统的对于系统菜单的操作,这是通过调用本地Windows API来完成的。

  二、系统菜单简介

  当你单击窗口图标或右击窗口标题栏时系统菜单即弹出。它包含当前窗口的默认行为。不同窗口的系统菜单看起来有些不同,如一个正常的窗口的系统菜单看起来与一个工具栏子对话框窗口的菜单就不一样。

  修改系统菜单的好处:

  ·添加应用程序自己定义的菜单项。

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

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

  操纵系统菜单

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

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

  三、SystemMenu 类介绍

  SystemMenu类的实现使得整个系统菜单存取容易许多。你可以使用这个类来修改一个窗口的菜单。 通过调用静态成员函数FromForm你得到一个对象,该函数要求一个Form对象或一个从Form继承的类作为它的参数。然后它创建一个新的对象,当然如果GetSystemMenu API调用失败的话,将引发一个NoSystemMenuException例外。

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

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

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

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

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

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

//将创建一个菜单项 "Test" ,且该项被选中(checked)

mySystemMenu.AppendMenu(myID, "Test", ItemFlags.mfStringItemFlags.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 类代码分析

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);
}

本文转自94cool博客园博客,原文链接http://www.cnblogs.com/94cool/archive/2009/08/13/1545396.html,如需转载请自行联系原作者

用C#和本地Windows API操纵系统菜单相关推荐

  1. Windows API函数大全

    1. API之网络函数 WNetAddConnection 创建同一个网络资源的永久性连接 WNetAddConnection2 创建同一个网络资源的连接 WNetAddConnection3 创建同 ...

  2. vc++之windows api

    WindowsAPI函数清单 WindowsAPI简介: API的英文全称(Application Programming Interface),WIN32 API也就是MicrosoftWindow ...

  3. Windows API函数大全---附:windows运行命令详解

    1. API之网络函数             WNetAddConnection 创建同一个网络资源的永久性连接             WNetAddConnection2 创建同一个网络资源的连 ...

  4. C#通过Windows API捕获窗,获取窗口文本(FindWindow、GetWindowText),附录:Windows窗口消息大全、Windows API大全

    文章目录 一.前言 二.使用Spy++工具分析窗口 三.C#通过Windows API捕获窗口,获取窗口文本 四.附录:Windows窗口消息 五.Windows API大全 1.API之网络函数 2 ...

  5. Windows API 函数大全

    Windows API函数大全,从事软件开发的朋友可以参考下 1. API之网络函数  WNetAddConnection 创建同一个网络资源的永久性连接  WNetAddConnection2 创建 ...

  6. Windows Api常用函数大全

    1. API之网络函数 WNetAddConnection 创建同一个网络资源的永久性连接  WNetAddConnection2 创建同一个网络资源的连接  WNetAddConnection3 创 ...

  7. Windows API函数大全(方便查找)

    Windows API函数大全 目录 Windows API函数大全 1. API之网络函数 2. API之消息函数 3. API之文件处理函数 4. API之打印函数 5. API之文本和字体函数 ...

  8. WINDOWS API函数说明

    Windows API函数大全,从事软件开发的朋友可以参考下 1. API之网络函数  WNetAddConnection 创建同一个网络资源的永久性连接  WNetAddConnection2 创建 ...

  9. 易语言常用WINdows API分类查询

    WINdows API分类 1.API之网络函数 2.API之消息函数 3.API之文件处理函数 4.API之打印函数 5.API之文本和字体函数 6.API之菜单函数 7.API之位图.图标和光栅运 ...

最新文章

  1. ASP.NET-Cookies的用法
  2. Qt-Creator编译pthread多线程程序的方法
  3. 数据库高级知识——索引优化分析(一)
  4. 屌丝就爱尝鲜头——java8初体验
  5. java中关于重写的说法_Java中有关“重载”和“重写”的说法正确的是()
  6. 使用ASP.NET Web API构建Restful API
  7. 微信公众平台可为市民鉴别万余药品真伪
  8. JSON-RPC是什么东西
  9. linux查看iozone安装目录,IOZone的基本使用
  10. 石墨烯的精细结构研究取得进展
  11. 齿轮仿真软件ROMAX DESIGNER安装教程
  12. uniapp使用uCharts制作趋势图
  13. APP推广:app推广的渠道有哪些既简单又有效的?
  14. Python使用在线接口SDK模块(baidu-aip)实现人脸识别
  15. 2022年武汉专精特新小巨人企业奖励补贴以及申报条件汇总
  16. 云大使推广的返利规则是什么
  17. 如何设计一个可扩展的优惠券功能
  18. java水果爆炸动画,这种水果叫炮弹果会爆炸?这是一起海关人员被媒体坑了的沙雕事件...
  19. Vmware配置共享文件功能(针对ubuntu的/mnt/hgfs无文件内容问题)
  20. python pptx库教程_Python-pptx库的运用

热门文章

  1. DNS MX记录一定要放在A记录之前
  2. Spark的RDD序列化
  3. Uva1595 对称轴
  4. 基于微信的图片放大预览
  5. 身份证第18位(校验码)的计算方法
  6. C# MD5加密工具方法
  7. 探究oracle clob字段是怎样存储的
  8. queue 队列优先级
  9. iOS开发CocoaPods使用
  10. iOS UINavigationController