Obtaining (and managing) file and folder icons using SHGetFileInfo in C#
作者:Paul Ingles 翻译:小新0574 审校:Allen Lee
原文链接(及代码):http://www.codeproject.com/csharp/fileicon.asp
Introduction
This article is based upon code from the MSDN Cold Rooster Consulting case study. Included in part of the CRC Rich Client is support for file icons, something I wanted to do myself. This article and classes are the result of my attempts to use the MSDN code in my own application.
这篇文章基于MSDN Cold Rooster Consulting case study上的代码(译注:我打不开文章提供的链接L)。(代码)包含在CRC Rich Client的一部分里,用来支持文件图标,这也正是我自己想做的事。这篇文章和相关类是我尝试使用MSDN里的代码到我自己的程序里去的结果。
The MSDN article explains how functions from Shell32 and User32 were wrapped in detail, but here's a short clip from the article:
那篇MSDN article详细解释了怎么包装Shell32和User32里的函数,这是那篇文章里的一小段:
"Interoperability with interfaces exposed by COM objects and the .NET Framework is handled via a proxy called the Runtime Callable Wrapper (RCW). The majority of the marshalling work is handled automatically by the .NET Framework.
“在.NET Framework中,与COM对象所提供的接口的互操作性是通过一个叫做“运行时可调用封装器” (Runtime Callable Wrapper (RCW))的代理单元完成的。主要的数据封送工作都是由.NET Framework自动完成的。”
C-style functions exported from an unmanaged library are a different matter. They are not wrapped automatically because information about the parameters required by the function is not as rich as the information provided by a COM type library. To call C-style functions exported from an unmanaged library, such as the Microsoft Windows® Shell32 API, you use the Platform Invocation Services (PInvoke)..."
“非托管类库提供的C风格函数却是另外一回事。由于函数需要的参数并不像COM类型库提供的信息那么丰富,所以它们并不能自动被封装。为了调用非托管类库提供的C风格的函数,比如说Microsoft Windows® Shell32 API,你就要使用Platform Invocation Services (PInvoke)…”
The code was left largely unchanged from the original article, although only SHGetFileInfo and DestroyIcon were retained.
尽管只有SHGetFileInfo 和 DestroyIcon 被保留了,其实从原始文章拿来的代码大多数都没有变过。
I personally found it quite hard to incorporate the MSDN code in my own application and after a few hours of wrestling with the masses of code and still getting errors when trying to build my own project I decided I would try and build up some classes around the Shell32 and User32 wrapped functions that I could use myself.
从我个人的角度讲,我发现要使MSDN里的代码融入到我自己的程序很困难。与大量代码挣扎了几小时后,还是发现产生错误。当我尝试建立自己的程序的时候,我决定我要试试建一些包装Shell32和User32里的函数的类,那样我自己就可以用了。
After looking back at the MSDN article the architecture of my solution and theirs is pretty similar, however I found it easier to develop my own classes and incorporate them into my own project.
在回顾MSDN的文章和我自己的解决方案的结构以后,发现它们是这么相似,然而,我发现开发自己的类然后让他们融入到自己的工程却来得更简单。
This article explains how I modified the MSDN article's code so that it can be used to retrieve icons as a stand-alone class in the form of the IconReader type, and then the IconListManager type which can be used to maintain ImageLists of file icons. It shields you from having to call the IconReader type's members directly, instead adding file icons to specified image lists. To prevent icons for the same file type being added more than once, a HashTable is used to store the file's extension at the time of adding the icon to the ImageList.
这篇文章解释了我怎么修改MSDN的文章的代码,以便它以IconReader 类型的形式作为一个单独的类能用来取得图标,而IconListManager 类型可以用来保存文件图保的图象列表。它可以使你免于直接调用IconReader类型的成员,取而代之的是增加文件图标到一个指定的图像列表。为了防止多次加入同样文件类型的图标,就用一个HashTable 在增加图标到ImageList的时候储存文件的扩展名。
Top Level View
The end result is two classes which make use of .NET's Interoperability to call the Win32 API to obtain icons for specified files and or folders. The IconReader class enables the caller to obtain icons directly (which may be all you need). However, an IconListManager class is then created which maintains icons within two ImageList types and shields you from retrieving icons directly.
最后的结果是两个类,这两个类使用.NET的互操作性来调用Win32API为特定的文件或者文件夹取得图标。 IconReader 类允许调用者直接取得图标(也许你们需要)。然而,可以创建一个IconListManager类,这样图标就保存在两个ImageList类型里,避免你直接取得图标。
IconReader - GetFileIcon Explanation
GetFileIcon is used to obtain icons for files, and uses three parameters:
GetFileIcon方法用来取得文件图标,使用三个参数:
It is a static member function since it doesn't need to store any state, and is intended primarily as an added layer of abstraction. If I needed to obtain a file's icon in the future (and not store it in an ImageList etc.) then I could do so using this class. Once I had a type that wrapped up the necessary API functions to obtain file icons I would then build another type to manage large and small ImageLists that would enable me to make a single call to add an icon, and if it was already added, return the index that the icon was in the ImageList.
这是一个静态成员函数,因此它不需要保存任何状态,主要作为一个增加的抽象层。如果我将来需要得到一个文件的图标(不储存在一个ImageList里等),我就可以使用这个类来做到。一旦我拥有了一个用来取得文件图标的必要API函数,我就建立另一个类型来管理大小图象列表,这能使我能够通过一个单独的调用来添加图标,如果它已经被加进来了,就返回这个图标在ImageList里的索引。
Firstly, a SHFILEINFO structure is created from the following definition:
首先,一个SHFILEINFO结构使用以下定义创建:
The SHFILEINFO struct includes an attribute to define a formatted type, which is "a structure or class member annotated with the StructLayoutAttribute to ensure predictable layout information to its members." This ensures that the unmanaged code we call receives the struct as it is intended - i.e. in the order that the members are declared. More details on passing structures are on MSDN
SHFILEINFO 结构包含一个属性来定义一个被格式化的类型,这个类型是“一个结构或者一个类使用StructLayoutAttribute 来评注,这个属性保证结构或者类的成员以可预知它们的成员布局信息。”这样就保证我们调用的非托管代码得到就像它原来期望得到的结构。关于传递结构的更多细节在MSDN上。
Once the SHFILEINFO struct is created, flags are then set specifying how SHGetFileInfo should behave and what type of icon to retrieve. The code for this part is pretty self explanatory.
一旦创建了SHFILEINFO 结构,flags就被设置,来指出SHGetFileInfo 该怎么表现以及该得到哪种类型的图标。这部分的代码具有很强的自解释性。
Once the various parameters have been finalised, its time to call Shell32.SHGetFileInfo. The code for the Shell32 class was written entirely as part of the MSDN article, and so I cannot take credit for it (and so if you would like more info on how this was done I recommend you take a look at the original CRC article). However as a quick example of how simple it is the unmanaged function is declared as:
一旦完成了各种各样的参数,就是调用Shell32.SHGetFileInfo的时候了。Shell32 类的代码都是那篇MSDN文章里面的,所以没什么好感激我的(所以,如果你想获得更多关于这些代码怎么运作的信息,我强烈建议你看一下原来的那篇CRC文章)。然而,下面的例子展示了声明一个非托管函数是如何的简单:
Which translated to managed code is:
被转化成托管代码就是这样:
Once the SHFILEINFO struct had been populated, its then time to get the hIcon that points to the file's icon. This hIcon can then be passed as a parameter of System.Drawing.Icon.FromHandle() which returns the file's icon. After looking through the original code I noticed that a DestroyIcon function was also included, so I looked it up on MSDN and found it was used to (funnily enough) "destroys an icon and frees any memory the icon occupied". I decided it would be a good idea to do this immediately after the icon had been retrieved (since this class was intended to be used in any number of ways). The icon could then be cleaned up as soon as necessary by the GC, or stored in an ImageList. If this isn't necessary then please let me know.
一旦SHFILEINFO 被移植,那是时候获得指向文件图标的hIcon了(译注:指向文件图标的句柄)。这个hIcon 能够作为参数传递给System.Drawing.Icon.FromHandle() ,(这个方法)返回文件图标。在查看原始代码以后我注意到还包括一个DestroyIcon 函数,所以我查阅MSDN,发现它以前一般用于(真够有趣的)“销毁一个图标,释放这个图标占用的内存”。我觉得在一个图标被获得以后就立即这样做(销毁图标)真是个好主意(因为这个类将以各种各样不同地方式使用)。一旦需要GC就清理掉这个图标,或者被存于一个ImageList。如果这是不必要的,请通知我。
Originally, I didn't use the Clone member function to obtain a copy of the icon, and just left it at FromHandle. However, the call to DestroyIcon immediately after then meant that the returned Icon was now useless and generated an exception. Since I thought this class could be used in any number of ways, I decided to stick with a static call which would obtain a copy of the icon, and then call DestroyIcon immediately after. It suited what I needed to do, and this was something different to the original MSDN code.
本来,我没有用Clone成员函数来获得一个图标,而把这个任务交给了FromHandle。不过,之后立即调用DestroyIcon 函数意味着Icon 就是不可用的了,会得到一个异常。我想,因为这个类将以各种各样不同地方式被使用,我决定使用一个静态调用来得到一个图标的副本,然后再调用DestroyIcon。这样符合我的需求,这是不同于原始MSDN代码的地方。
The function then returns with the specified icon.
于是这个函数返回一个指定的图标。
IconReader - GetFolderIcon
The code for GetFolderIcon is very similar to GetFileIcon, except that the dwFileAttributes parameter is passed Shell32.FILE_ATTRIBUTE_DIRECTORY as opposed to Shell32.FILE_ATTRIBUTE_NORMAL for files.
GetFolderIcon的代码跟GetFileIcon的代码很相似,不同之处仅为前者的dwFileAttributes 参数为Shell32.FILE_ATTRIBUTE_DIRECTORY ,而后者为Shell32.FILE_ATTRIBUTE_NORMAL。
It also requires fewer parameters, specifying whether a large or small icon is desired, and whether to retrieve the open or closed version.
还要要另外一些参数,用于指定要一个大图标还是小图标,要一个打开还是关闭的版本(译注:文件夹图标有打开和关闭两种版本)。
IconListManager - Overview
IconListManager was created after I had produced IconReader, and was designed to manage up to two ImageList types with file icons. The type requires itself to be instantiated, and can be passed up to two parameters when constructed - specifying ImageList objects.
在我制作IconReader以后,我又制作了 IconListManager 。这个类设计用于管理两个(储存)文件图标的ImageList 类型。(ImageList)类型自己需要被实例化,能够在(IconListManager)构造时传递给两个参数 – 指定ImageList 对象。
Firstly, there are some member fields which are declared as:
首先,一些成员字段被声明为:
The HashTable is used to contain a list of extensions that have been added to the ImageList. We only need to store each icon once, so a HashTable can be used to look up whether an extension exists, if so, whereabouts the icon is in the ImageList.
HashTable(哈希表)用来保存已经被加入ImageList的一组扩展名。我们只需要保存每一个图标一次就行了,因为HashTable可以查找到是不是这个扩展名已经存在了,如果存在了,可以找出这个图标在ImageList的哪里。
The ArrayList is used to contain references to ImageList objects, this is so that two constructors can be provided. The first allows the caller to manage a single ImageList with a specified size. The second constructor uses two ImageList parameters, allowing the type to manage both large and small icons.
ArrayList 用来保存指向ImageList对象的引用,要达到这样的目的,我们需要提供两个构造器。第一个允许调用者管理一个指定大小的ImageList。第二个构造器使用两个ImageList 参数,允许类型管理大图表和小图标。
The first constructor looks like:
第一个构造器是这样的:
This stores icons only for a single size in a single ImageList.
这样只使用单独的大小和单独的ImageList保存图标。
The second constructor (which fill allow the type to be used for both large and small icons) looks like:
第二个构造器(允许类型同时管理大图表和小图标那个)是这样的:
This adds both ImageList types to the ArrayList, and then sets a flag specifying that calls to IconReader class's member functions should retrieve both sizes. Its not the neatest way to do it, but it worked, and if I have enough time I'll go through and tidy a few things up.
这样加了两个ImageList 类型到ArrayList,然后指定了一个标签用来指明调用IconReader 类的成员函数时要获取两种大小的图标。这并不是什么巧妙的办法,但是这个方法可行,如果我有足够的时间,我会再稍加整理。
The class has a few internal functions which are used to make the code a little cleaner, the first of which is AddExtension. This adds a file extension to the HashTable, along with a number which is used to hold the icon's position in the ImageList.
这个类有一些内部的函数用来使代码清晰简洁,第一个是AddExtension。这个函数用来增加一个文件扩展名到哈希表里,同时有一个数字用来保存图标在ImageList里的位置。
AddFileIcon adds a file's icon to the ImageList, and forms the majority of the code for IconListManager:
AddFileIcon增加一个文件图标到ImageList里,里面是IconListManager的主要代码:
The code is pretty well covered through comments but works as follows. Firstly, it splits the filePath so that the extension can be obtained (string after the final period - ".", i.e. the string at the highest position in the array). Once this has been done, a check is done on the HashTable to determine whether that extension has already been added. If it has, then return the contents of the HashTable for the given key (the file extension). So, if "TXT" exists, the "TXT" key is looked up and the contents returned, which is the position of the icon in the ImageList.
代码已经很好的使用注释解释了,是像下面说明的那样运作的。第一,它分割了文件的路径以得到文件的扩展名(扩展名是“.”后面的那部分,也是数组的最大位置上的字符串)。一旦完成了这个,就对HashTable 做一个检查,看看是不是当前扩展名已经加过了。如果已经有了,就使用已知的键(文件扩展名)返回HashTable 的内容(译注:也就是与这个扩展名同时保存的索引值)。所以,如果”TXT”已经有了,那么就查找键”TXT”,返回内容,也就是图标在ImageList中的位置。
If it doesn't exist in the HashTable it hasn't been added, so obtain the current count of items (and thus determine the index the new icon will be inserted at). Then, if it's managing both large and small ImageList objects, then call GetFileIcon twice. If it isn't for both sizes, then just retrieve the specified size icon.
如果它不在HashTable 里,它还没被加进来过(废话,呵呵),那么就取得当前的项目数(以便决定要插入的新图标的索引值)。那么如果同时管理大图标和小图标的ImageList 对象,那么就调用GetFileIcon两次。如果不需要两个大小的图标,那就只要获取指定大小的图标就可以了。
Once this has been done, the extension can then be added to the ImageList with its position, and the position then returned to the caller. This position can then be used when adding icons to ListView or TreeView types when specifying the icon index.
一旦完成了,扩展名就被加到ImageList 的相应位置里,这个位置就返回给调用者。这个位置可以被用来指定图标的索引,然后加到ListView 或者 TreeView 里。
ClearList is included in case its necessary to start over,
ClearList在需要重新开始的时候包括进来,
Firstly it iterates through the ArrayList and clears the respective ImageList, and then clears the HashTable that contained the file extensions.
首先它遍历ArrayList 来清除(clear)相应的ImageList,然后清除保存着文件扩展名的HashTable。
That covers the classes. I had originally wanted to produce a FileIconImageList control that derived from ImageList. This would have incorporated the functionality that IconListManager did, but would have been a slightly neater way of doing it (i.e. instantiate an ImageList, and then call AddFileIcon to add the icon like with IconListManager). However, when I tried this I found I couldn't derive from ImageList and so this wasn't possible. Producing IconListManager was the next best thing I could do.
这就是这个类的所有了。我其实本来想从ImageList继承做一个FileIconImageList控件。这样就可以包括进IconListManager 的功能,同时也具有一个更加轻便灵巧的方式来完成它(IconListManager 的功能)(也就是实例化一个ImageList,然后就像使用IconListManager那样调用AddFileIcon来增加图标)。然后,正当我尝试的时候,我发现我并不能继承ImageList。制作 IconListManager 是我下一个最想做的事情。
In the end, a calling application only needs to create an object of type IconListManager, pass it the ImageList references you are using, and then use the AddFileIcon method. I haven't yet added an AddFolderIcon member, since there are only a couple of folder icons (and they would probably go in a separate ImageList to file icons) the calls to obtain them could be made directly from IconReader. However, if this is something people would like added its very easy to do.
最后,调用(IconListManager类)的程序只要创建一个IconListManager的对象,传递一个你在使用的ImageList 引用给它,然后使用AddFileIcon 方法。我没有加AddFolderIcon这个成员,因为只有一对文件夹图标(他们很可能跟文件图标在一个不同的ImageList里),可以直接调用IconReader来获得它们。然而,如果你们需要的话,加一个(AddFolderIcon)是很方便的。
The demo application shows how to use the classes, and includes a ListView and Button. When you click the Button anOpenFileDialog is displayed. The filename is then retrieved, and the icon added to the ListView. The snippet below gives you the basic code. Note that I set color depth to 32-bit to ensure support for alpha channel smoothing.
演示程序教你怎么使用这些类,其中包含一个ListView 和一个 Button。当你点击Button就显示出一个OpenFileDialog。然后就得到了文件名(译注:包含路径的文件名),图标就被加到ListView。下面的小片段给你基本的代码。注意看我把色深设为32-bit来保证支持alpha channel smoothing。
Important Notes
It's taken me a long time to figure this out, but gave me real grief at one point. Windows XP introduced Visual Styles, enabling you to use icons with alpha channel blending to produce nice smooth icons. However, to include support for this you must include a manifest file. Without one, you get a really ugly black border. For more information on including visual styles support, you ought to read the MSDN Article "Using Windows XP Visual Styles With Controls on Windows Forms". As I said, I forgot to include a manifest and it drove me crazy for weeks.
完成它花了我很长时间,但是在某一点上让我真的很难过。Windows XP 引进了Visual Styles,可以让你使用alpha channel blending来制作出漂亮平滑的图标。然而,要提供这种功能你必须包含一个清单文件。如果没有,你就获得到一个难看的黑色边框。要得到关于包含visual styles支持的更多信息,你应该读这篇MSDN文章"Using Windows XP Visual Styles With Controls on Windows Forms"。就像我说的,我忘了包含一个清单文件,让我好几个星期几乎发狂。(呵呵,看来程序员总为一些小疏忽抓狂)
Thanks
Well this is my first article to CodeProject (finally), although I've not been a registered member here long I've been a quiet lurking one, and even used CodeGuru in the good old days for my MFC learning. I'm not a massively accomplished programmer, but I hope this has been of help to you. Reading file icons is something I've noticed being mentioned a few times on the MS Newsgroups, and so the included classes should help you on your way.
这是我在CodeProject第一篇文章,虽然我很长没有注册成为会员,而只是一个潜水(在论坛游逛,却不发言的人^^)者,以前是使用CodeGuru学习MFC的。我并不算是一个非常熟练的程序员,但我希望这篇文章对你有帮助。读取文件图标是在MS新闻组被提醒了好几次的事情,那么文章附带的类(请到原文链接下载)应该对你有帮助。
If you have any questions about this article (particularly if I've done something in a bad way), please feel free to email me.
如果对这篇文章有什么疑问(特别是我使用了很差的方式做的事情),那么请随便email me。
其实这篇文章也算是出于我自己的需要用Google搜到的,我的一个小程序里需要获得系统的一些图标(因为用自己找的图标总觉得不是很好看^^),首先是通过网上一些文章了解到文章中提到的那个API函数可以达到这样的目的,然后用MSDN里的演示代码,在我自己的程序里写了一个类获得了系统图标,但是MSDN给出的演示代码只能获得一个图标,然后我用了一个很笨的办法,也就是用ArrayList保存得到的图标,然后又重复的图标也不检查就增加上去,想想也知道占的内存会越来越多,后来试了一下检查重复图标,不过效率太低,速度一下子降下去了。其实从文中可以看出,作者是用HashTable解决这个问题的,而且封装的比较好。
不过作者提供的类还是不能满足我的所有需要,那就是不能获得软驱跟光驱的图标,然后特别纳闷的是我最先用的MSDN的代码可以得到这些图标,然后我比较了半天都没看出什么名堂来。比较郁闷^^!!其实我还花了点时间看了一下那个API,不过最后没有成果。(也许只是当作玩玩吧,没太认真)
----------------------------------------------------------------
原文及代碼連接更新:
http://www.codeproject.com/KB/files/fileicon.aspx