(C#)Windows Shell 外壳编程系列7 - ContextMenu 注册文件右键菜单
原文 (C#)Windows Shell 外壳编程系列7 - ContextMenu 注册文件右键菜单
(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢~)
接上一节:(C#)Windows Shell 外壳编程系列6 - 执行
从本节起,我所要讲述的是对 Windows 系统的“Shell 扩展”。“Shell 扩展”从字面上分两个部分:Shell 与 Extension。Shell 指 Windows Explorer,而Extension 则指由你编写的当某一预先约定好的事件(如在以. doc 为后缀的文件图标上单击右键)发生时由 Explorer 调用执行的代码。因此一个“Shell 扩展”就是一个为 Explorer 添加功能的 COM 对象。
“Shell 扩展”有很多种类型,每种类型都在各自不同的事件发生时被调用运行,但也有一些扩展的类型和调用情形是非常相似的。
| 类型 | 何时被调用 | 应该作些什么 |
| Context menu 扩展处理器 |
用户右键单击文件或文件夹对象时, 或在一个文件夹窗口中的背景处单击右键时(要求shell版本为4.71+) |
添加菜单项到上下文菜单中 |
| Property sheet 扩展处理器 |
要显示一个文件对象的属性框时 | 添加定制属性页到属性表中 |
| Drag and drop 扩展处理器 |
用户用右键拖放文件对象到文件夹窗口或桌面时 | 添加菜单项到上下文菜单中 |
| Drop 扩展处理器 | 用户拖动Shell对象并将它放到一个文件对象上时 | 任何想要的操作 |
| QueryInfo扩展处理器(需要shell版本 4.71+) | 用户将鼠标盘旋于文件或其他Shell对象的图标上时 | 返回一个浏览器用于显示在提示框中的字符串 |
现在你可能想知道“Shell 扩展”到底是什么样的,不过我还是乐意把我后面所实现的技术效果直接展示出来。以下三副图片分别代表了三种“Shell 扩展”:
(1)实现类似 WinRAR 的右键菜单
(2)根据文本大小,显示不同的 TXT 文件图标
(3)当鼠标移动到 TXT 文件图标上的时候,显示内容预览。
好,废话不多说了,赶紧进入今天要讲述的内容: ContextMenu 注册文件右键菜单。
对于 WinRAR 所实现的效果,其实叫做上下文菜单。例如我们把扩展关联到 .TXT 文件,当用户右键单击文本文件对象时扩展就会被调用,然后向系统菜单增加菜单项,并响应相应的命令。由此可见,基本上每种 Shell 扩展,都需要做一些几乎一样的事情。
初始化接口
当我们的shell扩展被加载时,Explorer 将调用我们所实现的COM对象的 QueryInterface() 函数以取得一个IShellExtInit 接口指针。该接口仅有一个方法 Initialize(),其函数原型为:

[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute("000214e8-0000-0000-c000-000000000046")]
public interface IShellExtInit
{
[PreserveSig()]
int Initialize(IntPtr pidlFolder, IntPtr lpdobj, uint hKeyProgID);
}
Explorer 使用该方法传递给我们各种各样的信息.
pidlFolder 是用户所选择操作的文件所在的文件夹的 PIDL 变量. (一个 PIDL [指向ID 列表的指针] 是一个数据结构,它唯一地标识了在Shell命名空间的任何对象, 一个Shell命名空间中的对象可以是也可以不是真实的文件系统中的对象。)
lpdobj 是一个 IDataObject 接口指针,通过它我们可以获取用户所选择操作的文件名。
hKeyProgID 是一个HKEY 注册表键变量,可以用它获取我们的DLL的注册数据。
因此我们可以在这个方法中,获取到被右击选择的一个或多个文件/文件夹名。

;
fmt.tymed = TYMED.TYMED_HGLOBAL;
STGMEDIUM medium = new STGMEDIUM();
m_dataObject.GetData(ref fmt, ref medium);
m_hDrop = medium.hGlobal;
}
}
catch (Exception)
{
}
return S_OK;
}
IDataObject 是一个接口,包含了一些获取文件名的方法,后面可以用到。
与上下文菜单交互的接口
一旦 Explorer 初始化了扩展,它就会接着调用 IContextMenu 的方法让我们添加菜单项, 提供状态栏上的提示, 并响应执行用户的选择。IContextMenu 接口定义了以下几个方法:

[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute("000214e4-0000-0000-c000-000000000046")]
public interface IContextMenu
{
[PreserveSig()]
int QueryContextMenu(HMenu hmenu, uint iMenu, uint idCmdFirst, uint idCmdLast, CMF uFlags);
[PreserveSig()]
void InvokeCommand(IntPtr pici);
[PreserveSig()]
void GetCommandString(uint idcmd, GCS uflags, uint reserved, IntPtr commandstring, int cchMax);
}
第一个是 QueryContextMenu(), 它让我们可以修改上下文菜单。其参数:
hmenu 上下文菜单句柄。
uMenuIndex 是我们应该添加菜单项的起始位置。
uidFirstCmd 和 uidLastCmd 是我们可以使用的菜单命令ID值的范围。
uFlags 标识了Explorer 调用 QueryContextMenu() 的原因。
我们调用该方法,为上下文菜单增加几个菜单项:

, MFMENU.MF_BYPOSITION, bpHome.GetHbitmap(), bpHome.GetHbitmap());
}
return id;
}
在状态栏上显示提示帮助
下一个要被调用的IContextMenu 方法是 GetCommandString().。如果用户是在浏览器窗口中右击文本文件,或选中一个文本文件后单击文件菜单时,状态栏会显示提示帮助。我们的 GetCommandString() 函数将返回一个帮助字符串供浏览器显示。

, commandstring, data.Length);
}
break;
}
}
执行用户的选择
IContextMenu 接口的最后一个方法是 InvokeCommand()。当用户点击我们添加的菜单项时该方法将被调用。其参数:
CMINVOKECOMMANDINFO 结构带有大量的信息, 但我们只关心 lpVerb 和 hwnd 这两个成员。
lpVerb参数有两个作用 – 它或是可被激发的verb(动作)名, 或是被点击的菜单项的索引值。
hwnd 是用户激活我们的菜单扩展时所在的浏览器窗口的句柄。
我们可以根据被点击的菜单项索引,来执行相应的操作。

:
//调用浏览器,打开网页
Process proc = new Process();
proc.StartInfo.FileName = "IExplore.exe";
proc.StartInfo.Arguments = "http://lemony.cnblogs.com";
proc.Start();
break;
default:
break;
}
}
注册Shell扩展
现在我们已经实现了所有需要的COM接口. 可是我们怎样才能让浏览器使用我们的扩展呢?首先,我们要注册动态库。但仅仅这样是不够的,为了告诉浏览器使用我们的扩展, 我们需要在文本文件类型的注册表键下注册扩展。
(请原谅我未能抽出时间对注册扩展做详细的说明(如果以后有机会会补上),大家可以自行研究)

[System.Runtime.InteropServices.ComRegisterFunctionAttribute()]
static void RegisterServer(String str1)
{
try
{
//注册 DLL
RegistryKey root;
RegistryKey rk;
root = Registry.LocalMachine;
rk = root.OpenSubKey("Software//Microsoft//Windows//CurrentVersion//Shell Extensions//Approved", true);
rk.SetValue(GUID, KEYNAME);
rk.Close();
root.Close();
//注册文件
RegTXT();
}
catch{
}
}
[System.Runtime.InteropServices.ComUnregisterFunctionAttribute()]
static void UnregisterServer(String str1)
{
try
{
//注销动态库
RegistryKey root;
RegistryKey rk;
root = Registry.LocalMachine;
rk = root.OpenSubKey("Software//Microsoft//Windows//CurrentVersion//Shell Extensions//Approved", true);
rk.DeleteValue(GUID);
rk.Close();
root.Close();
//注销文件
UnRegTXT();
}
catch
{
}
}
private static void RegTXT()
{
RegistryKey root;
RegistryKey rk;
root = Registry.ClassesRoot;
rk = root.OpenSubKey(".txt");
string txtclass = (string)rk.GetValue("");
if (string.IsNullOrEmpty(txtclass))
{
txtclass = "TXT";
rk.SetValue("", txtclass);
}
rk.Close();
rk = root.CreateSubKey(txtclass + "//shellex//ContextMenuHandlers//" + KEYNAME);
rk.SetValue("", GUID);
rk.Close();
rk = root.CreateSubKey(txtclass + "//shellex//IconHandler");
rk.SetValue("", GUID);
rk.Close();
rk = root.CreateSubKey(txtclass + "//shellex//{00021500-0000-0000-C000-000000000046}");
rk.SetValue("", GUID);
rk.Close();
}
private static void UnRegTXT()
{
RegistryKey root;
RegistryKey rk;
root = Registry.ClassesRoot;
rk = root.OpenSubKey(".txt");
rk.Close();
string txtclass = (string)rk.GetValue("");
if (!string.IsNullOrEmpty(txtclass))
{
root.DeleteSubKey(txtclass + "//shellex//ContextMenuHandlers//" + KEYNAME);
root.DeleteSubKey(txtclass + "//shellex//IconHandler");
root.DeleteSubKey(txtclass + "//shellex//{00021500-0000-0000-C000-000000000046}");
}
}
注册动态库
.NET 开发的动态库有些特别,需要在 .NET SDK 中注册
regasm MyContextMenu.dll /CodeBase
反注册则是:regasm /unregister MyContextMenu.dll /CodeBase
代码:http://files.cnblogs.com/lemony/MyContextMenu.rar
关于代码:代码里面还包括了图标扩展和提示扩展的代码,如果有兴趣,可自行阅读。
题外话:还有相当多的关于 Shell 扩展的内容无法一一说明,如果有机会,以后会尽量补上。或大家查阅网上的“Windows Shell扩展编程完全指南”(虽然是VC版的,但内容相当丰富)
(C#)Windows Shell 外壳编程系列7 - ContextMenu 注册文件右键菜单的更多相关文章
- (C#)Windows Shell 外壳编程系列8 - 同后缀名不同图标?
原文 (C#)Windows Shell 外壳编程系列8 - 同后缀名不同图标? (本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows Shell 外壳 ...
- (C#)Windows Shell 外壳编程系列6 - 执行
原文(C#)Windows Shell 外壳编程系列6 - 执行 (本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows Shell 外壳编程系列5 - ...
- (C#)Windows Shell 外壳编程系列4 - 上下文菜单(iContextMenu)(二)嵌入菜单和执行命令
(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows Shell 外壳编程系列3 - 上下文菜单(iContextMenu)(一)右键菜单 上一节说到如 ...
- (C#)Windows Shell 外壳编程系列3 - 上下文菜单(iContextMenu)(一)右键菜单
(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows Shell 外壳编程系列2 - 解释,从“桌面”开始展开 这里解释上一节中获取名称的方法 GetD ...
- (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
原文 (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示 (本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows She ...
- (C#)Windows Shell 外壳编程系列5 - 获取图标
(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows Shell 外壳编程系列4 - 上下文菜单(iContextMenu)(二)嵌入菜单和执行命令 有 ...
- (C#)Windows Shell 外壳编程系列2 - 解释,从“桌面”开始展开
(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一篇:(C#)Windows Shell 外壳编程系列1 - 基础,浏览一个文件夹 让我们详细解释一下 Shell 编程中最基本 ...
- (C#)Windows Shell 外壳编程系列1 - 基础,浏览一个文件夹
1 - 基础,浏览一个文件夹 我们知道,在win32中是以外壳名字空间的形式来组织文件系统的,在外壳名字空间里的每一个对象(注)都实现了一个IShellFolder的接口,通过这个接口我们可以直接查询 ...
- 在windows系统的文件右键菜单中增加“命令提示符”
本实用小工具能够在windows系统的文件右键菜单中增加“命令提示符”,方便快速进入制定文件的命令提示窗口,避免逐层输入或复制文件夹路径,极其实用. 工具下载地址如下:360云盘(访问密码:5b71) ...
随机推荐
- JS关闭页面无提示
window.opener=null; window.open('','_self'); window.close();
- VS快捷方式小技巧
VS2005代码编辑器的展开和折叠代码确实很方便和实用.以下是展开代码和折叠代码所用到的快捷键,很常用: Ctrl + M + O: 折叠所有方法 Ctrl + M + M: 折叠或者展开当前方法 C ...
- Android基础-EditText键盘的显示与隐藏
场景一.点击EditText之外的空白区域隐藏键盘: how to hide soft keyboard on android after clicking outside EditText? 首先定 ...
- SQLite 终端相关命令
SQLite ALL Last login: Fri Dec 5 09:52:08 on ttys002 BeSilent:~ qianfeng$ sqlite3 data.db SQLite ve ...
- JavaSE学习总结第13天_API常用对象3
13.01 StringBuffer的概述 StringBuffer类概述:线程安全的可变字符序列.一个类似于 String 的字符串缓冲区,但不能修改.虽然在任意时间点上它都包含某种特定的字符序 ...
- Mysql 批量杀死进程
正常情况下kill id,即可,但是有时候某一异常连接特别多的时候如此操作会让人抓狂,下面记录下小方法: use information_schema; select concat('kill ',i ...
- Android ActionBar详解(三)--->ActionBar的Home导航功能
FirstActivity如下: package cc.testsimpleactionbar2; import android.os.Bundle; import android.app.Activ ...
- hadoop搭建杂记:Linux下JDK环境变量的设置(三种配置环境变量的方法)
Linux下JDK环境变量的设置(三种配置环境变量的方法) Linux下JDK环境变量的设置(三种配置环境变量的方法) ①修改/etc/profile文件 如果你的计算机仅仅作为开发使用时推荐使用这种 ...
- hdu 3572 Escape 网络流
题目链接 给一个n*m的图, 里面有一些点, '.'代表空地, '#'代表墙, 不可以走, '@'代表大门, 可以有多个, 'X'代表人, 问所有人都走出大门需要的最短时间, 每一时刻一个格子只能有一 ...
- SQL Server | Mysql 对表的unique 的实现方式
在ANSI SQL 标准中unique 有两种实现方式 1.是可以插入多个空值.也就是说多个null值看成是互不相同的. 2.是只可以插入一个空值,也主是说把所有的空值看也是相同的. 在SQL Ser ...