This article is an extension of Jim Parsells' article "An All VB.NET Explorer Tree Control with ImageList Management". I have added two main classes to Jim's
ExpTreeLib assembly -
ExpList directly inherits from
ListView, and is partially based on the ListView in Jim's demo project, with a few additions like icons in the column headers and partially transparent icons for hidden files or folders. The
ExpCombo uses Jim's
CShItem classes to create an Explorer ComboBox like the one available in the
I haven't included many code examples in the article, as I didn't really know where to start. I have tried to keep the code well-commented, but if you have any questions about specific pieces, please just post them and I will answer as soon as possible.
The original need for these controls arose after I started writing add-ins for a program that works with SharePoint 2003 files. To my great surprise, none of the default dialogs (Open, Save, Browse etc.) worked with SharePoint files. I started developing these controls after I saw Jim's article and realised that a huge piece of these controls had just been written for me. Without Jim's classes, I never would have been able to finish this.
In this update, I have made a lot of changes to the code. A lot of these changes will break existing code (I have changed or completely removed a lot of methods and properties). One major change is the fact that the attached code has some .NET 2.0 dependencies, and hence there is no VS2003 project available. This was not my intention, but as I got further into things, it became too difficult to maintain two versions. For these reasons, the project has a new version number.
I have also added some replica Windows dialogs that I use with SharePoint apps. I haven't covered these in the article as they were only included to avoid having two different versions - one for myself and one for The Code Project (this is what I had done before, hence version 3.1).
Due to the fact that so much has changed in the controls since the last version, it's possible that I have forgotten to mention some things. If you find something missing from the article or have any questions about changes made, please post in the forum - if nobody says anything, then nothing's getting changed.
The version of ExplorerControls.dll in the demo project folder has been signed with a strong name. The Key used has been intentionally omitted from the posted code, meaning that if you want to recompile the project, you will have to either remove or change the
AssemblyKeyFile entry in the AssemblyInfo.vb file. This file is in the My Project folder (select the project in the Solution Explorer, and click the 'Show All Files' button to see this file in Visual Studio).
There are still a couple of bugs that I couldn't fix. The worst one is the refreshing of SharePoint folders - it just won't work. I did a lot of testing, and discovered that the
IShellFolder.EnumObjects API method returns the old state until I restart my app, and I couldn't find anyway to invalidate the object collection being returned.
Another bug was that the
ExpList.View property didn't work in the designer after I shadowed the base class property to add the Thumbnail view. Although this worked fine in .NET 1.1, I was forced to create the
ViewStyle property with the
EditorBrowsable(EditorBrowsableState.Never) attributes and add the
Browsable(False) attribute to the
View property. These properties are synchronized, meaning that at design-time, you manipulate the
ViewStyle property (that appears as 'View' in the properties window), and at runtime, you manipulate the
The last major problem I had was with the column widths being reset to their default value. To workaround this, I just temporarily stored the values before they were reset, and then restored them afterwards. I have commented this problem with the
TODO keyword in case anyone is interested.
In the posted code, there has been no consideration for COM clients. I thought about implementing interfaces for all of the classes, but there were several things that stopped me from doing this. Firstly, a bit of background knowledge: COM interfaces do not support overloaded methods, nor do they support parameterized constructors. This means that the
ExpListItemCollection.Add method will be seen by COM clients as
Add3. It also means that COM clients cannot instantiate the
CShItem class - a rather large hindrance in using these controls.
In order to fix this problem, we require a helper class with a default constructor and methods that construct new objects and pass them back as return values. We also need to implement interfaces in all the classes with COM friendly method names. The last step is to use the
ClassInterface(ClassInterfaceType.None) attribute on all the classes so that only explicitly implemented interfaces are visible to COM.
The problem with all of this is that, in VB.NET, when implementing an interface, all members must be explicitly implemented (as far as I know). This makes it difficult to include inherited methods from the base class in the interface, requiring the overriding or shadowing of the base class methods in the inherited class so as to enable explicit implementation of the interface members (!?!?). The long and the short of it is: this is all way too much work, and unless a few people ask for it, I'm not going to do it
Anyway, there's always late-binding...
The core class of the library. Required for file system information.
A class containing the required Shell32 API interfaces, functions etc.
Class required for obtaining a handle to the
SystemImageList and attaching it to the controls.
A TreeView representing the file system.
(See Jim's article for information on these and other classes, in the ExpTreeLib folder.)
A ListView representing the file system.
The item class for the
The column class for the
Collection classes for the items and columns in the
The default comparer class for the
ExpList, this sorts the standard
ExpListColumnTypes in the appropriate way.
A ComboBox representing the file system.
The item class for the
The collection class for the items in the
UserControls and Dialogs
A UserControl combining the
ExpCombo controls, with built-in navigation among other added features.
Replicas of the same controls provided with the .NET Framework (but with support for SharePoint files).
A UserControl combining the browser with a second
ExpList for file selection (batch processing etc.).
A class for extracting the Windows thumbnail from a
The class used to make the 'Click' sound when navigating in the controls (set the
Silent property to
True to turn the sounds off).
This class was created because the
ToolBarButton class didn't quite behave as I wanted for the browser control.
This is essentially a ListView like the one in Explorer. It has properties allowing it to attach to either an
ExpCombo or an
ExpTree so that the files and folders in the currently selected folder of the attached control are automatically added, and enables double-click navigation through the file system. By default, files and folders will be opened when the user double-clicks them. This action can be turned on/off by setting the
File/FolderOpenOnActivate properties. If it is off, the
File/FolderActivate events can be used to perform the desired action in the calling class.
In previous versions, I used the User32
SendMessage call to enable grouping, I have now removed this, however, due to the addition of grouping in the .NET 2.0
ListView. I still use
SendMessage to add icons to the column headers, and to show the selected column (this makes the background of the sorting column gray).
I have used Globalization for error messages and the context menu (the resource files for the default language - English - and neutral German are included in the source files). There is also a language property to allow the language to be fixed. This just sets the
System.Threading.Thread.CurrentThread.CurrentUICulture property to either German, English, or that of the Operating System. I use this if I want the language of the controls to stay constant even if they are being used on another OS.
Filter property can be used to limit the types of files that will be shown in the list. Only the characters after the last '.' will be taken into account, meaning that 'test.jpg', '*.jpg', '.jpg', and 'jpg' will all have the same effect. I used a
Public Boolean Function here, in case of the need to check an item against the filter without having to add the item to the list.
It is also possible to use drag and drop to add files from Explorer, an
ExpTree, or from another
ExpList. This will not work if the list is attached to an
ExpTree or an
ExpCombo, as it was only intended for file selection for batch processing etc.
In the second update, I added the
ExpListItem class to which I have made another code-breaking change (sorry!) as this class now has a
CShItem property and no longer the
ShItem property to store the associated
CShItem. This class also has a
State property to show the icon as
Normal - I have used this to correctly show hidden items in the
ExpList. In this version, I also added a
ColumnHeaderContextMenu. To do this, I used the fact that the
MouseDown event doesn't fire when the mouse is over a
ColumnHeader. I just overrode the
ContextMenu property to make sure it was always set to the
ColumnHeaderContextMenu, and changed this on the
MouseDown event, making sure that the
ColumnHeaderContextMenu was only used if this event was not fired.
In the latest version, I have changed the structure of the
ExpList to more closely reflect the behavior of the .NET
ListView control. I added new classes for the item collections, making it possible to use
ExpList.Items.Add etc. instead of
ExpList.AddItem as in the last version. I did this to reduce the chances of errors due to not using the correct methods, figuring that the closer I matched the
ListView, the easier it would be to use the controls. I also added the
ExpListColumnCollection, and enabled design-time support for the
ExpListColumn. To add an
ExpListColumn in the designer, you must set the
ColumnType property (the default value is
None). It is also possible to set the
Visible property to
False and then the column will be hidden at runtime (but still available through the
ColumnType enum has three 'Custom' types (
CustomNumeric). These can be used to show custom data in the list, and will be sorted appropriately depending on the type. When using one of these column types, you should add the
SubItem text to the list items in three places: the
Load event of the hosting form, the
ColumnAdded event of the column, and the
ItemsAdded event of the list (in the demo app, I have added a custom column to a browser control). The
ExpListColumn also has a
CustomGrouping property; when this is set to
True, the calling class can manually set the groups in an
Browser.Group) EventHandler - see the demo app for an example.
The last major change I made was to add a thumbnail view to the
ExpList. This uses the
OwnerDraw mode of the
ExpList to draw the files thumbnail, or the extra large icon when no thumbnail is available. This required the addition of the
hXLargeImageList property to the
SystemImageListManager, and also the addition of the
Thumbnail class with its single static
ExtractThumbnail method. This public method returns the thumbnail as a
These mutually exclusive properties attach the list to a 'Driving' control.
Whether or not hidden files or folders should be displayed.
The standard context menu for the control.
Sets whether the default action (open) will be performed when a file or folder in the list is activated. If set to
False, the corresponding event
File/FolderActivate will be raised.
Sets a filter for the file types to be displayed (see above for usage).
Used in the 'Details' view to switch between showing file size, type etc., or the path.
Whether or not the list is sortable.
Whether files and folders can be renamed.
Whether new folders can be created in the control.
The language to be used by the control.
False after checking the
CShItem passed against the filter string. Alternatively, you can pass an
CShItems, and it will filter and return this list.
Used for files that have a URL path. Removes "%20" and other URL encoding, and replaces them with the appropriate character.
Refreshes the associated
ExpCombo, or refreshes the
CShItem behind each of the list items, whichever is appropriate.
ExpCombo class inherits directly from
ComboBox, and therefore exposes all the normal properties and methods. It utilizes the
ExpComboItem class to create the hierarchy required to show the file system and to store the
CShItem properties for the related folder. Make sure that you only add
ExpComboItems to the ComboBox as there is a direct-cast made to this class in the
In the first version, I used an extra class to draw the selected
ExpCombo item. This was my workaround to avoid having a space next to any item selected in the dropdown list. Scott then pointed out in a forum post that the
OnDrawItem in a
ComboBox has a
State property. Using this, you can find out if any
Item is in the 'Edit' area and then just not draw the space (see below)... Thanks Scott, this has vastly simplified this control.
If (e.State And DrawItemState.ComboBoxEdit) = _
offset = 1
offset = (item.Offset * 16) + 1
iconBounds = New Rectangle(e.Bounds.Left + offset, e.Bounds.Top + _
CInt((Me.ItemHeight - item.ShIcon.Height) / 2), _
Due to this major change, I decided to make a couple of other changes before I updated the second time. I moved the initiation of the
ExpCombo from the
ExpCombo itself. I added two properties,
RootDirectory, that make use of the
ExpTree.StartDir enumeration. I also added the reasonably self-explanatory
BuildCombo methods. This basically made the control more independent, and means that you can add an
ExpList to a form, set a few properties, and Robert's your father's brother...
The overloaded method
SelectFolder allows the selection of an item in the
ExpCombo, this can be passed a
CShItem or one of the special folders in the
ExpTree.StartDir enumeration. There is also the possibility to pass the path of a folder. This will return a
Boolean value representing the success of the operation. The folder that the path represents is not required to exist in the
ExpCombo before the call. This function is basically an adapted version of the
In the latest update, I did similar things to the
ExpCombo as to the
ExpList, adding the
ExpComboItemCollection to enable the strong-typing of the
ExpComboItems and to keep the control's methods more in line with the .NET
The currently selected
Whether or not hidden folders should be displayed.
The special folder at the root of the control. If you pass the Desktop, the
ExpCombo will be rebuilt in the default style with all the folders in My Computer added.
The folder to be selected on start up.
Selects a specific folder, takes a path or
ExpCombo using the specified
CShItem as the root.
This is basically an
ExpCombo and an
ExpList combined with the standard navigation buttons for this type of a control.
I have tried to make sure that all the desired methods/functions/events have been opened up by this control (the
Browser has no
Refresh method, but the control will refresh when F5 is pressed and the
ExpList has the focus). All the appropriate
ExpList events have been included, with the addition of the
SelectedFolderChanged (fires when a new folder is selected in the
SelectedListIndexChanged (fires when the
SelectedIndex of the
ExpList changes) events.
The control also has
LoadState methods that require an XML file or a Registry Key to write to/read from (in the latest version, there is an overloaded
SaveState method that returns a
DataSet with the state settings). These methods store and restore the currently selected folder, view style, size, the column widths, and the sorting column and order.
Points of Interest
I have added support for two languages (German and English) in the source and demo applications. The default setting for the
Language property (
OperatingSystem) of the controls will just use the culture of the Operating System, and defaults to English if no match is found. All the
MessageBox strings, tooltips, and menu items get their strings from the .resx files in the assembly, using the
System.Resources.ResourceManager class. The addition of a new language just requires a copy of ControlStrings.resx to be updated with the correct string values and saved under the appropriate name - ControlStrings.[culture code].resx. E.g., ControlStrings.fr.resx for French, and ControlStrings.en-GB.resx for English (Great Britain).
I have used resx resource files here instead of a localised control in the IDE, for the simple reason that I can't set
MessageBox strings in a localized control. The main drawback from using resx resource files is that you cannot use the IDE to change the size of controls for different cultures, this must be done in code; this doesn't affect the controls in this library, making resx resource files the obvious choice here.
I also discovered that when getting the path for a file in a web folder, the URL that is returned is encoded (%20 instead of space etc.). I started doing a
String.Replace here until I realised that there are a lot of characters that will be encoded in a URL (ä, ö, ü, and ß were the ones that affected me). I looked into the encoding and came up with the following code (it may not be perfect, but it's better than the 8
Replace calls that I was doing before):
Public Shared Function DecodeURL(ByVal url As String) As String
If url.LastIndexOf("%"c) = -1 Then Return url
Dim index As Integer
Dim hexCharacter As String
index = url.IndexOf("%"c)
If index = -1 Then Return url
hexCharacter = "&" & url.Substring(index + 1, 2)
hexCharacter = Encoding.GetEncoding("ISO-8859-1").GetString(New _
url = url.Replace("%" & url.Substring(index + 1, 2), hexCharacter)
This is basically replacing the characters by getting the ISO 8859 character corresponding to the hexadecimal value after the % in the URL. This should work for all paths.
I have a lot of people to thank for getting me to the end of this, but in particular, I would like to thank Jim Parsells for the above mentioned article and code, and Daniel Presman for his CodeProject article on icons in a
ComboBox. I would also like to thank Scott for his forum post on the use of the
State property in the
DrawItemEventArgs to adjust the display of the
ExpCombo items when they are selected, and Dominique for quality feedback and improvements.
- 16 May 2005 - Posted first version of the article and code.
- 18 May 2005 - Implemented Jim Parsells' new version of the
SystemImageListManager, allowing the return of the selected icon for a
CShItem. This fixes a bug in
ExpComboSelectedItem that was caused by me modifying the System image list outside the
SystemImageListManager class. Thanks Jim.
- 13 June 2005 - Updated article and code.
ExpComboSelectedItem and used the
DrawItemEventArgs.State property to control the display of the currently selected item.
- Fixed an overload bug that occurred in the
ShowHidden() method in
- Added the
File/FolderActivate events to
- Made the default
ExpList context menu
Public, and added the
ListViewContextMenu property to the
- Improved the implementation of the
ExpCombo, allowing the control to be used better, independent of the
- Changed the default sorting of
ExpComboItem to sort by
- 19 September 2005 - Updated article and code.
- Added the
ExpListItem class - this has potentially code-breaking changes (see
ExpList section above).
- Fixed the display of hidden items using the
- Added grouping to
- Added the
- Added the display of the
- Extended the
SaveState functions of the
Browser control to accept a Registry Key.
- 02 August 2006 - Updated article and code.
- Completely redesigned
ExpList to use new
- Added design time support for
- Added thumbnail view to
- General improvements to all classes to better reflect the base class behavior.
- 09 August 2006 - Updated article and code.
Style property to
ExpCombo (see Object browser for details).
- Fixed the display of open and selected icons in
- Fixed the LabelEdit bug in the thumbnail view of
- Fixed the selected text display in the thumbnail view of