(C#)Windows Shell 外壳编程系列8 - 同后缀名不同图标?

简介: 原文 (C#)Windows Shell 外壳编程系列8 - 同后缀名不同图标? (本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢~)   接上一节:(C#)Windows Shell 外壳编程系列7 - ContextMenu 注册文件右键菜单   关于注册 动态库必须注册才能使用。

原文 (C#)Windows Shell 外壳编程系列8 - 同后缀名不同图标?

(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢~)

 

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

 

关于注册

动态库必须注册才能使用。除了使用 regasm 来注册 DLL 以外,还应该在代码中增加 RegisterServer  UnregisterServer 方法,以指导 DLL 注册时,在 Windows 注册表中增加什么键。关于具体键以下做简单说明:

1) 注册 DLL 的 Shell Extensions具体位置是 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved,增加以 GUID 为名称的键,值则是动态库说明。(此位置里面全是 Shell 扩展的动态库注册,许多相关软件就是从里面获取信息,例如 ShexView

2) 关联文件。Shell 扩展一般是针对文件或者文件夹的,因此必须关联。许多人都熟知“HKEY_CLASSES_ROOT\*”的作用,就是用来关联所有文件。而文件夹则是“HKEY_CLASSES_ROOT\Folder”。然而如果具体到某种文件类型。可能会稍微复杂一点。我们可以从代码中看到一些启示:

 


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

对于关联 .TXT,首先应该找寻“HKEY_CLASSES_ROOT\.txt”,但事情远远没那么简单,因为相当多的文本编辑工具,都会把改键重定向,例如EMEditor会把改键的默认值改为“emeditor.txt”。被重定向后,我们为了不破坏原有关联,应该到新的地方去注册(如果没有,我们就修改重定向至 TXT)。

 

无论是 *、文件夹还是具体文件类型,都会有 ShellEx 的键,为 Shell 扩展专用。具体不同的扩展,应该注册不同的键。例如 ContextMenuHandlers、IconHandler、或者{00021500-0000-0000-C000-000000000046}(其实这就是QueryInfo)。注册的方法很简单,把默认值改为 GUID 即可。

 

相同文件类型不同图标?

如果是以前,我会对这句话十分吃惊。但现在这种现象比比皆是。除了我们的例子外,.NET 程序员最熟悉的莫过于 Sln 解决方案文件了。不同版本的 Sln 图标不同,上面有个小版本号提示。

 

不过后来我了解到,原来不同 Exe 显示不同的图标,也是这种原理,我晕。。。

 

扩展接口
图标扩展处理器实现两个接口 IPersistFile  IExtractIcon
记得 IShellExtInit 接口用于一次有多个选择文件时的处理,而 IPersistFile 则用于初始化只涉及一个选择文件时的处理。

 


[ComImport(), ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute("0000010b-0000-0000-C000-000000000046")]
public interface IPersistFile
{
    [PreserveSig]
    
uint GetClassID(out Guid pClassID);

    [PreserveSig]
    
uint IsDirty();

    [PreserveSig]
    
uint Load([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName, [In] uint dwMode);

    [PreserveSig]
    
uint Save([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName, [In] bool fRemember);

    [PreserveSig]
    
uint SaveCompleted([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName);

    [PreserveSig]
    
uint GetCurFile([MarshalAs(UnmanagedType.LPWStr)] out string ppszFileName);
}

 

对于这个接口,我们只需要用到 Load 方法

 


public uint Load(string pszFileName, uint dwMode)
{
    szFileName 
= pszFileName;

    
return S_OK;
}

 

szFileName 是全局变量,用来记住当前操作的文件路径。

 

IExtractIcon 接口图标扩展处理器实现 IExtractIcon 接口,当浏览器需要为文件显示一个图标时将调用该接口。
因为我们的扩展用于文本文件,浏览器将在每次显示文本文件对象时调用 IExtractIcon 的方法。

IExtractIcon 有两个方法,它们的作用是告诉浏览器所使用的图标。记住:浏览器为显示的每一个文件都将创建一个COM 对象。
这就是说每一个文件都将有一个COM C++类对象对应. 因此在你的扩展中应该避免费时的操作以防止浏览界面反应迟滞。


[ComVisible(true), ComImport, Guid("000214eb-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IExtractIcon
{
    [PreserveSig]
    
int GetIconLocation([In] ExtractIconOptions uFlags, 
        [In] IntPtr szIconFile, 
        [In] 
uint cchMax,
        [Out] 
out int piIndex, 
        [Out] 
out ExtractIconFlags pwFlags);

    [PreserveSig]
    
int Extract([In, MarshalAs(UnmanagedType.LPWStr)] string pszFile, 
        
uint nIconIndex, 
        [Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef
=typeof(IconMarshaler))] out Icon phiconLarge, 
        [Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef
=typeof(IconMarshaler))] out Icon phiconSmall, 
        [In] 
uint nIconSize);

 

和许多教程上面说的一样,有两种方法可将图标返回给浏览器。但我在尝试第一种方式的时候,未能成功,十分奇怪。不过我还是应该把这种方法简单说明。

 

第一种是 GetIconLocation() 可以返回文件名/索引对以指出包含图标的文件,和图标在该文件中索引位置(以0为基)。Extract() 只需返回 S_FALSE 给浏览器让它自己来解析图标。该方法的特别之处在于浏览器在 GetIconLocation() 返回之后不一定会调用 Extract().。浏览器会保持一个图标缓存以存储最近使用的图标。
如果 GetIconLocation() 返回最近已使用的文件名/索引对,而且图标仍然在缓存中,浏览器就可以直接使用缓存中的图标而不会去调用Extract()

第二种方法是从GetIconLocation() 中返回不要查看缓冲的标志,这样会使浏览器去调用Extract()Extract() 则负责加载图标资源并将其句柄返回给浏览器。

 

这里具体介绍第二种方法。在这方法中,GetIconLocation() 作用仅仅是设置一些标志位,以及获取文件大小。

 


int IExtractIcon.GetIconLocation(ExtractIconOptions uFlags, IntPtr szIconFile, uint cchMax, out int piIndex, out ExtractIconFlags pwFlags)
{
    piIndex 
= -1;

    
try
    {
        FileInfo f 
= new FileInfo(szFileName);
        lngFileSize 
= f.Length;
        pwFlags 
= ExtractIconFlags.DontCache | ExtractIconFlags.NotFilename;

        
return S_OK;
    }
    
catch { }

    pwFlags 
= ExtractIconFlags.None;
    
return S_FALSE;
}

其参数为: uFlags 改变扩展行为的标志。
ExtractIconFlags.DONTCACHE 告诉浏览器不要检查图标缓冲而去使用最近的 szIconFile/piIndex 对。其结果是IExtractIcon::Extract() 将被调用.。
ExtractIconFlags.NOTFILENAME 根据 MSDN,该标志告诉浏览器当GetIconLocation()返回时忽略 szIconFile/piIndex 的内容。
szIconFile 是由shell 提供的一个缓冲要求我们填入包含所使用的图标的文件名.
cchMax 是该缓冲区的大小。
piIndex int 的指针,要求我们添入图标在文件中的索引。
pwFlags UINT 的指针,要求我们返回影响浏览器行为的标志。

 

使用第二种方法,我们并不需要填写 piIndex 和 szIconFile,而 IExtractIcon.Extract() 总被调用,并负责加载图标并返回两个图标句柄 HICON 给浏览器 – 一个是大图标, 一个是小图标。该方法的好处是你不必考虑你的图标资源在文件中的顺序位置。其缺陷在于它忽略了浏览器的图标缓冲,这会使显示速度减慢,特别是在有浏览有无数个文件的目录时。

 


int IExtractIcon.Extract(string pszFile, uint nIconIndex, out Icon phiconLarge, out Icon phiconSmall, uint nIconSize)
{
    phiconLarge 
= null;
    phiconSmall 
= null;

    
try
    {
        
if (lngFileSize > 16 * 1024)
        {
            phiconLarge 
= Resource1._2;
            phiconSmall 
= new Icon(Resource1._2, new Size(1616));
        }
        
else if (lngFileSize > 0)
        {
            phiconLarge 
= Resource1._1;
            phiconSmall 
= new Icon(Resource1._1, new Size(1616));
        }
        
else
        {
            phiconLarge 
= Resource1._0;
            phiconSmall 
= new Icon(Resource1._0, new Size(1616));
        }

        
return S_OK;
    }
    
catch { }

    
return S_FALSE;
}

其参数为:

pszFile/nIconIndex 文件名和索引指定图标位置。其值与从 GetIconLocation() 返回的一样。
phiconSmall HICON 的指针,由 Extract() 返回指向大图标和小图标的句柄数组。
nIconSize 指定要求的图标大小。高字为小图标的长度 (长宽一致),低字为大图标的长度。在一般情况下, 其值为0x00100020 (高字16, 低字 32) 表示小图标应该是 16x16,大图标为 32x32。在我们的扩展中, 我们并没有在 GetIconLocation() 里填写 pszFile 和 nIconIndex 所以在这忽略,我们只加载图标并返回给浏览器。

 

从代码可以看到,根据文件大小的不同,加载了相应的图标资源返回给浏览器。效果如下:

 

 

代码:http://files.cnblogs.com/lemony/MyContextMenu.rar

 

关于代码:代码里面还包括了提示扩展的代码,如果有兴趣,可自行阅读。

 

题外话:还有相当多的关于 Shell 扩展的内容无法一一说明,如果有机会,以后会尽量补上。或大家查阅网上的“Windows Shell扩展编程完全指南”(虽然是VC版的,但内容相当丰富)

目录
相关文章
|
2天前
|
C# 开发者
C# 一分钟浅谈:Code Contracts 与契约编程
【10月更文挑战第26天】本文介绍了 C# 中的 Code Contracts,这是一个强大的工具,用于通过契约编程增强代码的健壮性和可维护性。文章从基本概念入手,详细讲解了前置条件、后置条件和对象不变量的使用方法,并通过具体代码示例进行了说明。同时,文章还探讨了常见的问题和易错点,如忘记启用静态检查、过度依赖契约和性能影响,并提供了相应的解决建议。希望读者能通过本文更好地理解和应用 Code Contracts。
13 3
|
22天前
|
安全 C# 数据安全/隐私保护
实现C#编程文件夹加锁保护
【10月更文挑战第16天】本文介绍了两种用 C# 实现文件夹保护的方法:一是通过设置文件系统权限,阻止普通用户访问;二是使用加密技术,对文件夹中的文件进行加密,防止未授权访问。提供了示例代码和使用方法,适用于不同安全需求的场景。
|
2月前
|
API C#
C# 一分钟浅谈:文件系统编程
在软件开发中,文件系统操作至关重要。本文将带你快速掌握C#中文件系统编程的基础知识,涵盖基本概念、常见问题及解决方法。文章详细介绍了`System.IO`命名空间下的关键类库,并通过示例代码展示了路径处理、异常处理、并发访问等技巧,还提供了异步API和流压缩等高级技巧,帮助你写出更健壮的代码。
38 2
|
2月前
|
SQL 开发框架 安全
并发集合与任务并行库:C#中的高效编程实践
在现代软件开发中,多核处理器普及使多线程编程成为提升性能的关键。然而,传统同步模型在高并发下易引发死锁等问题。为此,.NET Framework引入了任务并行库(TPL)和并发集合,简化并发编程并增强代码可维护性。并发集合允许多线程安全访问,如`ConcurrentQueue<T>`和`ConcurrentDictionary<TKey, TValue>`,有效避免数据不一致。TPL则通过`Task`类实现异步操作,提高开发效率。正确使用这些工具可显著提升程序性能,但也需注意任务取消和异常处理等常见问题。
45 1
|
2月前
|
安全 程序员 编译器
C#一分钟浅谈:泛型编程基础
在现代软件开发中,泛型编程是一项关键技能,它使开发者能够编写类型安全且可重用的代码。C# 自 2.0 版本起支持泛型编程,本文将从基础概念入手,逐步深入探讨 C# 中的泛型,并通过具体实例帮助理解常见问题及其解决方法。泛型通过类型参数替代具体类型,提高了代码复用性和类型安全性,减少了运行时性能开销。文章详细介绍了如何定义泛型类和方法,并讨论了常见的易错点及解决方案,帮助读者更好地掌握这一技术。
67 11
|
2月前
|
安全 数据库连接 API
C#一分钟浅谈:多线程编程入门
在现代软件开发中,多线程编程对于提升程序响应性和执行效率至关重要。本文从基础概念入手,详细探讨了C#中的多线程技术,包括线程创建、管理及常见问题的解决策略,如线程安全、死锁和资源泄露等,并通过具体示例帮助读者理解和应用这些技巧,适合初学者快速掌握C#多线程编程。
75 0
|
3月前
|
存储 C#
揭秘C#.Net编程秘宝:结构体类型Struct,让你的数据结构秒变高效战斗机,编程界的新星就是你!
【8月更文挑战第4天】在C#编程中,结构体(`struct`)是一种整合多种数据类型的复合数据类型。与类不同,结构体是值类型,意味着数据被直接复制而非引用。这使其适合表示小型、固定的数据结构如点坐标。结构体默认私有成员且不可变,除非明确指定。通过`struct`关键字定义,可以包含字段、构造函数及方法。例如,定义一个表示二维点的结构体,并实现计算距离原点的方法。使用时如同普通类型,可通过实例化并调用其成员。设计时推荐保持结构体不可变以避免副作用,并注意装箱拆箱可能导致的性能影响。掌握结构体有助于构建高效的应用程序。
93 7
|
3月前
|
图形学 C# 开发者
全面掌握Unity游戏开发核心技术:C#脚本编程从入门到精通——详解生命周期方法、事件处理与面向对象设计,助你打造高效稳定的互动娱乐体验
【8月更文挑战第31天】Unity 是一款强大的游戏开发平台,支持多种编程语言,其中 C# 最为常用。本文介绍 C# 在 Unity 中的应用,涵盖脚本生命周期、常用函数、事件处理及面向对象编程等核心概念。通过具体示例,展示如何编写有效的 C# 脚本,包括 Start、Update 和 LateUpdate 等生命周期方法,以及碰撞检测和类继承等高级技巧,帮助开发者掌握 Unity 脚本编程基础,提升游戏开发效率。
69 0
|
3月前
|
安全 C# 开发者
【C# 多线程编程陷阱揭秘】:小心!那些让你的程序瞬间崩溃的多线程数据同步异常问题,看完这篇你就能轻松应对!
【8月更文挑战第18天】多线程编程对现代软件开发至关重要,特别是在追求高性能和响应性方面。然而,它也带来了数据同步异常等挑战。本文通过一个简单的计数器示例展示了当多个线程无序地访问共享资源时可能出现的问题,并介绍了如何使用 `lock` 语句来确保线程安全。此外,还提到了其他同步工具如 `Monitor` 和 `Semaphore`,帮助开发者实现更高效的数据同步策略,以达到既保证数据一致性又维持良好性能的目标。
42 0