Introduction
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 - ExpCombo
and ExpList
. The 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 SystemImageListManager
and CShItem
classes to create an Explorer ComboBox like the one available in the Open
/SaveFileDialog
s.
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.
Background
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.
Update Information
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).
Bugs
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 DisplayName("View")
and 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 View
property.
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.
COM Interop
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 Add
, Add2
, and 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...
Class Overview
ExpTreeLib
CShItem
The core class of the library. Required for file system information.
ShellDll
A class containing the required Shell32 API interfaces, functions etc.
SystemImageListManager
Class required for obtaining a handle to the SystemImageList
and attaching it to the controls.
ExpTree
A TreeView representing the file system.
(See Jim's article for information on these and other classes, in the ExpTreeLib folder.)
ExpList
ExpList
A ListView representing the file system.
ExpListItem
The item class for the ExpList
.
ExpListColumn
The column class for the ExpList
ExpListItemCollection
, SelectedExpListItemCollection
, ExpListColumnCollection
Collection classes for the items and columns in the ExpList
.
ExpListComparer
The default comparer class for the ExpList
, this sorts the standard ExpListColumnTypes
in the appropriate way.
ExpCombo
ExpCombo
A ComboBox representing the file system.
ExpComboItem
The item class for the ExpCombo
.
ExpComboItemCollection
The collection class for the items in the ExpCombo
.
UserControls and Dialogs
Browser
A UserControl combining the ExpList
and ExpCombo
controls, with built-in navigation among other added features.
BrowseForFolder
, OpenFileDialog
, SaveFileDialog
Replicas of the same controls provided with the .NET Framework (but with support for SharePoint files).
FileSelector
A UserControl combining the browser with a second ExpList
for file selection (batch processing etc.).
Miscellaneous Classes
Thumbnail
A class for extracting the Windows thumbnail from a CShItem
.
Sounds
The class used to make the 'Click' sound when navigating in the controls (set the Silent
property to True
to turn the sounds off).
FlatButton
This class was created because the ToolBarButton
class didn't quite behave as I wanted for the browser control.
ExpList
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.
The 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 Cut
or 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 ColumnHeaderContextMenu
). The ColumnType
enum has three 'Custom' types (CustomText
, CustomeDate
, and 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 ExpList.Group
(or 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 Bitmap
.
ExpList Summary
ExpCombo
and ExpTree
These mutually exclusive properties attach the list to a 'Driving' control.
ShowHiddenFiles
Whether or not hidden files or folders should be displayed.
StandardContextMenu
The standard context menu for the control.
File/FolderOpenOnActivate
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.
Filter
Sets a filter for the file types to be displayed (see above for usage).
ShowPaths
Used in the 'Details' view to switch between showing file size, type etc., or the path.
Sortable
Whether or not the list is sortable.
AllowRename
Whether files and folders can be renamed.
AllowNewFolders
Whether new folders can be created in the control.
Language
The language to be used by the control.
Filter
Returns True
or False
after checking the CShItem
passed against the filter string. Alternatively, you can pass an ArrayList
of CShItem
s, and it will filter and return this list.
DecodeURL
Used for files that have a URL path. Removes "%20" and other URL encoding, and replaces them with the appropriate character.
RefreshList
Refreshes the associated ExpTree
or ExpCombo
, or refreshes the CShItem
behind each of the list items, whichever is appropriate.
ExpCombo
The 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 ExpComboItem
s to the ComboBox as there is a direct-cast made to this class in the OnDrawItem
method.
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 EventArgs
for 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) = _
DrawItemState.ComboBoxEdit Then
offset = 1
Else
offset = (item.Offset * 16) + 1
End If
iconBounds = New Rectangle(e.Bounds.Left + offset, e.Bounds.Top + _
CInt((Me.ItemHeight - item.ShIcon.Height) / 2), _
item.ShIcon.Width, item.ShIcon.Height)
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 Browser
to ExpCombo
itself. I added two properties, StartUpDirectory
and RootDirectory
, that make use of the ExpTree.StartDir
enumeration. I also added the reasonably self-explanatory SelectFolder
and BuildCombo
methods. This basically made the control more independent, and means that you can add an ExpCombo
and 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 ExpTree.ExpandANode
function.
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 ExpComboItem
s and to keep the control's methods more in line with the .NET ComboBox
.
ExpCombo Summary
SelectedFolder
The currently selected ExpComboItem
.
ShowHiddenFolders
Whether or not hidden folders should be displayed.
RootDirectory
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.
StartUpDirectory
The folder to be selected on start up.
SelectFolder
Selects a specific folder, takes a path or CShItem
.
BuildCombo
Rebuilds the ExpCombo
using the specified CShItem
as the root.
Browser
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 ExpCombo
) and SelectedListIndexChanged
(fires when the SelectedIndex
of the ExpList
changes) events.
The control also has SaveState
and 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
Globalisation
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
Do
index = url.IndexOf("%"c)
If index = -1 Then Return url
hexCharacter = "&" & url.Substring(index + 1, 2)
hexCharacter = Encoding.GetEncoding("ISO-8859-1").GetString(New _
Byte() {CByte(hexCharacter)})
url = url.Replace("%" & url.Substring(index + 1, 2), hexCharacter)
Loop
End Function
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.
Credits
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.
History
- 16 May 2005 - Posted first version of the article and code.
- 18 May 2005 - Implemented Jim Parsells' new version of the
GetIconIndex
Sub
in 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.
- Removed
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 ExpList
.
- Added the
File/FolderActivate
events to ExpList
.
- Made the default
ExpList
context menu Public
, and added the ListViewContextMenu
property to the Browser
.
- Improved the implementation of the
ExpCombo
, allowing the control to be used better, independent of the Browser
.
- Changed the default sorting of
ExpComboItem
to sort by CShItem
.
- 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
ExpListItem.State
property.
- Added grouping to
ExpList
.
- Added the
ColumnHeaderContextMenu
to ExpList
.
- Added the display of the
SelectedColumn
in ExpList
.
- Extended the
LoadState
and SaveState
functions of the Browser
control to accept a Registry Key.
- 02 August 2006 - Updated article and code.
- Completely redesigned
ExpList
to use new ExpListItem
collections.
- Added design time support for
ExpListColumns
.
- Added thumbnail view to
ExpList
.
- General improvements to all classes to better reflect the base class behavior.
- 09 August 2006 - Updated article and code.
- Added
Style
property to ExpCombo
(see Object browser for details).
- Fixed the display of open and selected icons in
ExpCombo
.
- Fixed the LabelEdit bug in the thumbnail view of
ExpList
.
- Fixed the selected text display in the thumbnail view of
ExpList
.