`

用C#和本地Windows API操纵系统菜单

    博客分类:
  • .NET
阅读更多

 一、前言

  .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.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 类代码分析

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下调试通过。
分享到:
评论

相关推荐

    C#_Win32API操纵系统菜单

    本文将详细介绍如何使用 C# 和 Win32 API 来实现对于系统菜单的操作。 #### 二、系统菜单简介 系统菜单是在用户单击窗口图标或右键点击窗口标题栏时出现的菜单。不同的窗口类型会有不同的系统菜单外观,比如一个...

    C# Windows API操纵系统菜单讲解

    ### C# 与 Windows API:操纵系统菜单详解 #### 一、引言 在 C# .NET 开发中,有时我们需要直接操作系统的底层功能,比如系统菜单。然而,.NET 框架本身并没有直接提供类似 `SystemMenu` 的属性或者 `...

    C#130用 调用Win32 API来操纵系统菜单 源代码

    本主题将深入探讨如何在C#中调用Win32 API来操纵系统菜单,这对于开发需要自定义系统菜单或者增强现有菜单功能的应用程序来说是至关重要的。 C#作为.NET框架的一部分,本身并不直接支持对Win32 API的调用,但通过P/...

    c#API一些类的实现

    接下来,"用C#和本地Windows API操纵系统菜单"涉及的是如何在C#程序中修改或扩展系统级别的菜单操作。这通常需要调用像LoadMenu、AppendMenu和InsertMenu这样的API函数,以动态加载、添加或修改窗口的菜单项。这为...

    基于c#windows API编程

    使用C#语言结合Windows API进行编程,可以实现对Windows系统底层服务和资源的直接调用,从而执行包括窗口控制、系统信息获取、硬件操作等多种功能。 首先,要进行基于C#的Windows API编程,需要了解和引用Windows ...

    C# 通过Win32的API来调用系统菜单

    标题“C# 通过Win32的API来调用系统菜单”涉及到的就是如何在C#应用中使用Win32 API来操作系统的系统菜单。 系统菜单通常指的是窗口的顶部菜单,包含“文件”、“编辑”等标准菜单项,以及像“最小化”、“最大化”...

    C# Windows API

    在C#中使用Windows API通常包括以下步骤: 1. **定义API函数原型**:在C#代码中,你需要声明一个静态方法,这个方法的签名应该与Windows API函数的参数和返回类型相匹配。 2. **添加DllImport特性**:使用`...

    C#操作窗体的系统菜单

    总的来说,C#提供了一套丰富的API来帮助开发者灵活地操作窗体的系统菜单,从而增强用户体验并满足各种应用程序的需求。无论你是创建简单的工具,还是复杂的桌面应用,理解和掌握这些技巧都将对你的开发工作大有裨益...

    Microsoft.WindowsAPICodePack和SHell的DLL

    在.NET框架的开发环境中,有时候我们需要调用Windows操作系统底层的功能,比如管理文件、控制桌面图标、操作任务栏等,这时就离不开对Windows API的使用。Microsoft.WindowsAPICodePack(简称APICodePack)是一个...

    C#中调用API,介绍API的使用

    - **操作系统特用API**:每种操作系统都有自己的通用API和特定API,后者只能在该操作系统中运行。 - **Windows NT**支持MS-DOS, Win16, Win32, POSIX (Portable Operating System Interface), OS/2 console API等。...

    利用C#添加,修改,移除软件窗口系统菜单

    在C#编程中,添加、修改和移除软件窗口的系统菜单是一项常见的需求,它可以为应用程序提供自定义的用户交互体验。系统菜单,通常是指在窗口左上角的图标旁边或者用户右键点击窗口标题栏时出现的菜单,它包含了如最小...

    C#禁止系统菜单按钮命令

    总结来说,C#禁止系统菜单按钮命令涉及对Windows API的使用,通过P/Invoke调用`GetSystemMenu`和`EnableMenuItem`函数来实现。理解这些API的工作原理和使用方法,是进行此类操作的关键。同时,开发者还可以通过...

    C#语言下的windows操作系统记事本

    在Windows操作系统中创建一个记事本程序,主要会使用到C#中的.NET Framework或.NET Core框架,这两个框架提供了丰富的类库和API,使得开发者可以方便地创建桌面应用。 1. **窗口界面设计**: - 使用Windows ...

    C#实现左侧菜单效果

    在本文中,我们将深入探讨如何使用C#编程语言来实现一个炫酷且实用的左侧菜单效果,这将有助于提升用户界面的交互性和美观性。在实际的软件开发中,左侧菜单通常作为应用的主要导航工具,帮助用户快速访问不同的功能...

    WindowsAPICodePack

    Windows API Code Pack提供了C#接口,如`JumpList`类,使得开发者可以轻松地创建和管理应用程序的跳转列表。 2. **任务栏进度条**:开发者可以使用`TaskbarProgressBar`类来显示应用程序的进度信息在任务栏上,增强...

    C#使用低级别的Windows API勾子阻止个别按键

    Windows API提供了丰富的函数,可以让我们与操作系统底层交互,包括设置键盘钩子来监控和处理键盘事件。 标题中的“C#使用低级别的Windows API勾子阻止个别按键”指的是利用Windows API中的键盘钩子(Keyboard Hook...

    C#获取系统托盘信息

    这个过程涉及对Windows API的深入理解和使用,以及C#中P/Invoke技术的应用。对于初学者来说,这可能是一个挑战,但一旦掌握了这种方法,就可以更灵活地操控Windows系统级别的功能。在实际项目中,这可能会被用来监控...

    基于Visual C#的Windows API 程序设计

    Windows API中包含很多函数,约有1500多个,根据其类别主要分为:窗口及窗口过程、声音及音频控制、位图及位图处理、INI及注册表操作、窗口效果及管理类、字体相关、错误处理、通用对话框、压缩及加密控制、文件及...

    c#中使用API帮助程序

    在C#编程中,Windows API(应用程序接口)是与操作系统直接交互的重要手段。API提供了丰富的功能,例如系统级操作、硬件访问以及许多无法通过.NET Framework自身实现的功能。本篇文章将详细探讨如何在C#中使用API...

Global site tag (gtag.js) - Google Analytics