|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis 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. The MSDN article explains how functions from Shell32 and User32 were wrapped in detail, but here's a short clip from the article:
The code was left largely unchanged from the original article, although only 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. 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. 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 Top Level ViewThe 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 A couple of additional enumerations were also included to make the library a little more .NET-esque. IconReader - GetFileIcon ExplanationGetFileIcon is used to obtain icons for files, and uses three parameters:
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 public static System.Drawing.Icon GetFileIcon(string name, IconSize size,
bool linkOverlay)
{
Shell32.SHFILEINFO shfi = new Shell32.SHFILEINFO();
uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES;
if (true == linkOverlay) flags += Shell32.SHGFI_LINKOVERLAY;
/* Check the size specified for return. */
if (IconSize.Small == size)
{
flags += Shell32.SHGFI_SMALLICON ; // include the small icon flag
}
else
{
flags += Shell32.SHGFI_LARGEICON ; // include the large icon flag
}
Shell32.SHGetFileInfo( name,
Shell32.FILE_ATTRIBUTE_NORMAL,
ref shfi,
(uint) System.Runtime.InteropServices.Marshal.SizeOf(shfi),
flags );
// Copy (clone) the returned icon to a new object, thus allowing us
// to call DestroyIcon immediately
System.Drawing.Icon icon = (System.Drawing.Icon)
System.Drawing.Icon.FromHandle(shfi.hIcon).Clone();
User32.DestroyIcon( shfi.hIcon ); // Cleanup
return icon;
}
Firstly, a [StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public const int NAMESIZE = 80;
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_PATH)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=NAMESIZE)]
public string szTypeName;
};
The Once the Once the various parameters have been finalised, its time to call DWORD_PTR SHGetFileInfo( LPCTSTR pszPath,
DWORD dwFileAttributes,
SHFILEINFO* psfi,
UINT cbFileInfo,
UINT uFlags
);
Which translated to managed code is: [DllImport("Shell32.dll")]
public static extern IntPtr SHGetFileInfo(
string pszPath,
uint dwFileAttributes,
ref SHFILEINFO psfi,
uint cbFileInfo,
uint uFlags
);
Once the Originally, I didn't use the The function then returns with the specified icon. IconReader - GetFolderIconThe code for 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
Firstly, there are some member fields which are declared as: private Hashtable _extensionList = new Hashtable();
//will hold ImageList objects
private System.Collections.ArrayList _imageLists = new ArrayList();
private IconHelper.IconReader.IconSize _iconSize;
//flag, used to determine whether to create two ImageLists.
bool ManageBothSizes = false;
The The The first constructor looks like: public IconListManager(System.Windows.Forms.ImageList imageList,
IconReader.IconSize iconSize )
{
// Initialise the members of the class that will hold the image list we're
// targeting, as well as the icon size (32 or 16)
_imageLists.Add( imageList ); // add ImageList reference to the array list
_iconSize = iconSize; // specify what size to retrieve
}
This stores icons only for a single size in a single The second constructor (which fill allow the type to be used for both large and small icons) looks like: public IconListManager(System.Windows.Forms.ImageList smallImageList,
System.Windows.Forms.ImageList largeImageList )
{
//add both our image lists
_imageLists.Add( smallImageList );
_imageLists.Add( largeImageList );
//set flag
ManageBothSizes = true;
}
This adds both The class has a few internal functions which are used to make the code a little cleaner, the first of which is
public int AddFileIcon( string filePath )
{
// Check if the file exists, otherwise, throw exception.
if (!System.IO.File.Exists( filePath ))
throw new System.IO.FileNotFoundException("File does not exist");
// Split it down so we can get the extension
string[] splitPath = filePath.Split(new Char[] {'.'});
string extension = (string)splitPath.GetValue( splitPath.GetUpperBound(0) );
//Check that we haven't already got the extension, if we have, then
//return back its index
if (_extensionList.ContainsKey( extension.ToUpper() ))
{
// it already exists
return (int)_extensionList[extension.ToUpper()]; //return existing index
}
else
{
// It's not already been added, so add it and record its position.
//store current count -- new item's index
int pos = ((ImageList)_imageLists[0]).Images.Count;
if (ManageBothSizes == true)
{
//managing two lists, so add it to small first, then large
((ImageList)_imageLists[0]).Images.Add(
IconReader.GetFileIcon( filePath,
IconReader.IconSize.Small,
false ) );
((ImageList)_imageLists[1]).Images.Add(
IconReader.GetFileIcon( filePath,
IconReader.IconSize.Large,
false ) );
}
else
{
//only doing one size, so use IconSize as specified in _iconSize.
//add to image list
((ImageList)_imageLists[0]).Images.Add(
IconReader.GetFileIcon( filePath,
_iconSize, false ) );
}
AddExtension( extension.ToUpper(), pos ); // add to hash table
return pos; // return its position
}
}
The code is pretty well covered through comments but works as follows. Firstly, it splits the If it doesn't exist in the Once this has been done, the extension can then be added to the
public void ClearLists()
{
foreach( ImageList imageList in _imageLists )
{
imageList.Images.Clear(); //clear current imagelist.
}
_extensionList.Clear(); //empty hashtable of entries too.
}
Firstly it iterates through the That covers the classes. I had originally wanted to produce a FileIconImageList control that derived from In the end, a calling application only needs to create an object of type The demo application shows how to use the classes, and includes a public class Form1 : System.Windows.Forms.Form
{
private ImageList _smallImageList = new ImageList();
private ImageList _largeImageList = new ImageList();
private IconListManager _iconListManager;
.
.
.
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
_smallImageList.ColorDepth = ColorDepth.Depth32Bit;
_largeImageList.ColorDepth = ColorDepth.Depth32Bit;
_smallImageList.ImageSize = new System.Drawing.Size( 16, 16 );
_largeImageList.ImageSize = new System.Drawing.Size( 32, 32 );
_iconListManager = new IconListManager( _smallImageList, _largeImageList );
listView1.SmallImageList = _smallImageList;
listView1.LargeImageList = _largeImageList;
}
.
.
.
private void addButton_Click(object sender, System.EventArgs e)
{
OpenFileDialog dlgOpenFile = new OpenFileDialog();
if(dlgOpenFile.ShowDialog() == DialogResult.OK)
{
listView1.Items.Add( dlgOpenFile.FileName,
_iconListManager.AddFileIcon( dlgOpenFile.FileName ) );
}
}
Important NotesIt'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. ThanksWell 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. If you have any questions about this article (particularly if I've done something in a bad way), please feel free to email me. | ||||||||||||||||||||