分享
 
 
 

C#+Windows API操纵系统菜单

王朝c#·作者佚名  2006-11-24
窄屏简体版  字體: |||超大  

一、前言

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

二、系统菜单简介

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

修改系统菜单的好处:

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

·在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;

//

[1] [2] 下一页

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有