您的位置:首页 > 运维架构 > Shell

(C#)Windows Shell 外壳编程系列7 - ContextMenu 注册文件右键菜单

2013-10-30 15:53 741 查看
原文 (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 的右键菜单

");

//将子菜单插入到上下文菜单中
Helpers.InsertMenu(hMenu, 1, MFMENU.MF_BYPOSITION | MFMENU.MF_POPUP, submenu.handle, "MyContextMenu(&Y)");

//为菜单增加图标
Bitmap bpCopy = Resource1.copy;
Helpers.SetMenuItemBitmaps(submenu, 0, MFMENU.MF_BYPOSITION, bpCopy.GetHbitmap(), bpCopy.GetHbitmap());
Helpers.SetMenuItemBitmaps(submenu, 1, MFMENU.MF_BYPOSITION, bpCopy.GetHbitmap(), bpCopy.GetHbitmap());
Bitmap bpHome = Resource1.home;
Helpers.SetMenuItemBitmaps(submenu, 2, MFMENU.MF_BYPOSITION, bpHome.GetHbitmap(), bpHome.GetHbitmap());
}
return id;
}

在状态栏上显示提示帮助

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

void IContextMenu.GetCommandString(uint idcmd, GCS uflags, uint reserved, IntPtr commandstring, int cchMax)
{
string tip = "";

switch (uflags)
{
case GCS.VERB:
break;
case GCS.HELPTEXTW:
switch (idcmd)
{
case 0:
tip = "把选中的文件/文件夹的全路径复制到剪切板";
break;
case 1:
tip = "把选中的 TXT 文本内容复制到剪切板";
break;
case 2:
tip = "访问柠檬的博客 http://lemony.cnblogs.com"; break;
default:
break;
}
if (!string.IsNullOrEmpty(tip))
{
byte[] data = new byte[cchMax * 2];
Encoding.Unicode.GetBytes(tip, 0, tip.Length, data, 0);
Marshal.Copy(data, 0, commandstring, data.Length);
}
break;
}
}

执行用户的选择

IContextMenu 接口的最后一个方法是 InvokeCommand()。当用户点击我们添加的菜单项时该方法将被调用。其参数:

CMINVOKECOMMANDINFO 结构带有大量的信息, 但我们只关心 lpVerb 和 hwnd 这两个成员。
lpVerb参数有两个作用 – 它或是可被激发的verb(动作)名, 或是被点击的菜单项的索引值。

hwnd 是用户激活我们的菜单扩展时所在的浏览器窗口的句柄。

我们可以根据被点击的菜单项索引,来执行相应的操作。

void IContextMenu.InvokeCommand(IntPtr pici)
{
INVOKECOMMANDINFO ici = (INVOKECOMMANDINFO)Marshal.PtrToStructure(pici, typeof(ShellLib.INVOKECOMMANDINFO));
StringBuilder sb = new StringBuilder(1024);
StringBuilder sbAll = new StringBuilder();
uint nselected;

switch (ici.verb)
{
case 0:
//复制文件名
nselected = Helpers.DragQueryFile(m_hDrop, 0xffffffff, null, 0);
for (uint i = 0; i < nselected; i++)
{
ShellLib.Helpers.DragQueryFile(m_hDrop, i, sb, sb.Capacity + 1);
sbAll.Append(sb.ToString() + "/n");
}
Clipboard.Clear();
Clipboard.SetDataObject(sbAll.ToString(), true);
break;
case 1:
//复制文件内容
nselected = Helpers.DragQueryFile(m_hDrop, 0xffffffff, null, 0);
for (uint i = 0; i < nselected; i++)
{
ShellLib.Helpers.DragQueryFile(m_hDrop, i, sb, sb.Capacity + 1);
StreamReader sr = new StreamReader(sb.ToString(), Encoding.GetEncoding("gb2312"));
sbAll.Append(sr.ReadToEnd());
sr.Close();
}
Clipboard.Clear();
Clipboard.SetDataObject(sbAll.ToString(), true);
break;
case 2:
//调用浏览器,打开网页
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版的,但内容相当丰富)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐