Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Explorer ComboBox and ListView in VB.NET

0.00/5 (No votes)
18 Aug 2006 1  
A ComboBox (and ListView) displaying the file system, using the system image list.

Sample Image - ExpCombo/ExpCombo.jpg

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/SaveFileDialogs.

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 CShItems, 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.

  • File/FolderActivate

    These events will only be raised when FileOpenOnActivate or FolderOpenOnActivate is set to False.

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 ExpComboItems 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.

'Set the offset if the item is not in the Edit area 

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 ExpComboItems 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.

  • RefreshCombo

    Rebuilds the ComboBox in its current state.

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 
    'Return if there are no special characters 

    If url.LastIndexOf("%"c) = -1 Then Return url 
    'Integer to hold character index 

    Dim index As Integer 
    'String to hold Hexidecimal character code 

    Dim hexCharacter As String 
    Do 
      index = url.IndexOf("%"c) 
      'Return if there are no more special characters 

      If index = -1 Then Return url 
      'The two characters after the '%' in a URL is the hexidecimal 

      'ISO 8859 character code 

      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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here