Creating an Internet Explorer Favorites Control






4.19/5 (11 votes)
Oct 2, 2002
9 min read

181737

3321
How to create an Internet Explorer favorites control.
Introduction
Almost four years after originally posting this article, I am finally updating it. I've decided to update this control to Visual Studio 2005 since that is the IDE I have been using for over a year now (including beta versions). I've added some performance improvements and a few enhancements (as well as a few bug fixes) to this new version. Although I'm posting the update now, it is still a work in progress, and I will be making further enhancements to it in the near future (e.g., fully implementing the context menu).
The original catalyst for this control came from an application I was working on that had a need to view web sites within the application itself. I wanted to make it easy for the users of the application to browse to their favorite web site, so I started looking for some type of "favorites" control, something that looked and worked similar to the Favorites explorer bar in Internet Explorer. After a bit of looking (including this site), I decided I would have to create my own control for displaying a user's favorites list.
Here are some of the features I wanted in my initial control:
- Similar look and feel of the Internet Explorer Favorites explorer bar.
- Ability to automatically load the favorites of the current user.
- Ability to refresh itself automatically if any of the favorites are modified via an outside source (e.g., Internet Explorer, Windows Explorer, etc.).
On with the code
For the most part, this is a very simple user control. Rather than detail every step, I will touch on the more interesting aspects of the control. You can download and view the source for more details.
To start with, we create a UserControl
and add a TreeView
control. We set the Dock
property of the TreeView
to Fill
so it will stretch to fill the entire size of the user control. Next, we set the HotTracking
property to true
so the TreeView
nodes will be underlined as the mouse moves over them, giving them the appearance of hyperlinks.
We add an image list to the control that contains at least three icons. The icons are used as follows:
- The first icon (index 0) is used as the shortcut icon.
- The second icon is used to denote an open folder.
- The third icon is used to denote a closed folder.
The source code for this project includes these three icons.
That takes care of the initial setup tasks. Now, let's see some code.
One of the first things we have to do is get the favorites path for the current user. The original version of this control retrieved the favorites path from the Windows registry. This has been updated to use the built-in Environment
class. The following snippet shows how this is done:
private void _GetFavoritesPath()
{
_favoritesPath =
Environment.GetFolderPath(Environment.SpecialFolder.Favorites);
}
Loading the Favorites
Next, we have to load the favorites into the TreeView
. This next code example is kind of long, but I'll explain what's going on, after you've had a chance to look it over.
private void LoadFavorites()
{
// Suppress repainting the TreeView until the nodes are added
tvFavorites.BeginUpdate();
// Clear the Favorites list
tvFavorites.Nodes.Clear();
// Load favorites from all sub-directories
LoadFavoritesFromFolder(new System.IO.DirectoryInfo(_strFavoritesPath), null);
// Load the favorites from the favorites folder
LoadFavoritesFromPath(_strFavoritesPath, null);
// Repaint the TreeView
tvFavorites.EndUpdate();
// Select the first node in the list
tvFavorites.SelectedNode = null;
if (tvFavorites.Nodes.Count > 0)
tvFavorites.SelectedNode = tvFavorites.Nodes[0];
}
// Load each sub-folder's favorites. This method is called
// recursivley for each sub-directory.
private void LoadFavoritesFromFolder(
System.IO.DirectoryInfo aobjDirInfo, TreeNode aobjNode)
{
System.Windows.Forms.TreeNode objNode;
foreach (System.IO.DirectoryInfo objDir in dirInfo.GetDirectories())
{
if (currentNode == null)
objNode = tvFavorites.Nodes.Add(objDir.Name, objDir.Name, 2, 1);
else objNode = currentNode.Nodes.Add(objDir.Name, objDir.Name, 2, 1);
// Set the full path of the folder
objNode.Tag = objDir.FullName;
if (objDir.GetDirectories().Length == 0)
// This node has no further sub-directories
LoadFavoritesFromPath(objDir.FullName, objNode);
else
{
// Add this folder to the current node and continue
// processing sub-directories.
LoadFavoritesFromFolder(objDir, objNode);
LoadFavoritesFromPath(objDir.FullName, objNode);
}
}
}
// Loads the favorites from the specified path.
private void LoadFavoritesFromPath(string astrPath, TreeNode aobjNode)
{
IniFile objINI = new IniFile();
string name;
System.IO.DirectoryInfo objDir = new System.IO.DirectoryInfo(astrPath);
// Process each URL in the path (URL files end with a ".url" extension
foreach (System.IO.FileInfo objFile in objDir.GetFiles("*.url"))
{
// Set the Text property to the "Friendly" name
name = Path.GetFileNameWithoutExtension(objFile.Name);
if (currentNode == null)
tvFavorites.Nodes.Add(name, name, 0, 0).Tag =
objINI.IniReadValue("InternetShortcut", "URL",
objFile.FullName);
else
currentNode.Nodes.Add(name, name, 0, 0).Tag =
objINI.IniReadValue("InternetShortcut", "URL",
objFile.FullName);
}
}
The first method, RefreshFavorites
, is the main driver for loading the favorites. We call the TreeView
's BeginUpdate
method to suspend any painting until we have completed populating the TreeView
control. This will prevent any flickering during population, and makes for faster loading.
We then clear the TreeView
control, and call the LoadFavoritesFromFolder
method, which is used to recursively add all subdirectories in the favorites root directory to the TreeView
control. This method will be recursively called for each subdirectory found. Once in the LoadFavoritesFromFolder
method, if no subdirectories are found, the LoadFavoritesFromPath
method is called, which will load all shortcuts for the current subdirectory.
Note how the shortcuts are stored as text files with a .url extension. The contents of the file are in standard Windows INI format. To read the URL for the shortcut, you will need to use the Windows API calls for reading INI files. I have chosen to use an INI class that I found at The Code Project for this purpose. Once the URL is extracted from the INI file, it is stored in the TreeView
's current node's Tag
property. The Text
property is set to the filename of the shortcut sans the .url extension.
That takes care of loading the shortcuts into the TreeView
control, now let's customize some of the features of the control.
Let's customize
There are a couple of minor features that we need to implement to give our control a similar look and feel to that of Internet Explorer's Favorites explorer bar.
- The cursor should be an arrow when over folders, and should change to a hand when over a shortcut.
- Only one folder should be allowed to be open at any given level (i.e., if you click on a folder in the favorites list to open it and then click on another folder at the same level (or any level above it), then any other open folders should be collapsed). This will make more sense once you have a chance to see it in action.
// Change the cursor to a hand
// for URL links or to an arrow for folders.
private void tvFavorites_MouseMove(object sender,
System.Windows.Forms.MouseEventArgs e)
{
// Get a reference to the TreeView control
TreeView objTreeView = (TreeView)sender;
// Get a reference to the node under the mouse
// pointer (if there is one)
TreeNode objNode = objTreeView.GetNodeAt(e.X, e.Y);
if (objNode != null)
{
if (objNode.Tag != null)
// We're over a URL
objTreeView.Cursor = Cursors.Hand;
else
// We're over a folder
objTreeView.Cursor = Cursors.Default;
}
else
// We're over an empty region in the TreeView
objTreeView.Cursor = Cursors.Default;
}
The code above is the event handler for the TreeView
's MouseMove
event. The code first gets a reference to the TreeView
control. It then attempts to get a reference to the node under the mouse pointer. If there is no node under the mouse pointer, the reference is set to null
and the method is exited.
If the current node is a folder, the cursor is changed to an arrow (the default setting). If it is a shortcut, the cursor is changed to a hand.
// Processes node clicks and expands or collapses nodes accordingly.
private void tvFavorites_Click(object sender, System.EventArgs e)
{
if ((_intX != -1) && (_intY != -1))
{
TreeView objTreeView = (TreeView)sender;
TreeNode objNode = objTreeView.GetNodeAt(_intX, _intY);
if (objNode != null)
{
if (objNode.ImageIndex == 0)
{
// A URL was clicked
_currentURL = (string)objNode.Tag;
_currentURLName = (string)objNode.Text;
if (objNode.Parent == null)
{
_currentFolderName = "";
_currentFolder = _favoritesPath;
}
else
{
_currentFolderName = (string)objNode.Parent.Text;
_currentFolder = (string)objNode.Parent.Tag;
}
// Raise an event notifying the owner that a URL was clicked
UrlClick(this, new UrlClickEventArgs(_currentURL));
}
else
{
tvFavorites.BeginUpdate();
// A folder node was clicked
_currentFolderName = (string)objNode.Text;
_currentFolder = (string)objNode.Tag;
_currentURL = "";
_currentURLName = "";
// Collapse all sibling nodes so only
// one folder is open at any given level
CollapseSiblings(objNode);
// Toggle the folder
if (!objNode.IsExpanded)
objNode.Expand();
else objNode.Collapse();
tvFavorites.EndUpdate();
}
}
}
}
The code above is the event handler for the TreeView
's Click
event. I used the Click
event instead of the AfterSelect
event because, once a node has been selected, the AfterSelect
event won't be called again until you click on a different node. Since I want the node to expand/collapse each time I click on it (without necessarily having to leave the node), I placed the code here.
First, we get a reference to the TreeView
control, similar to the tvFavorites_MouseMove
method. We then get a reference to the clicked node. However, notice the use of the instance variables _intX
and _intY
. These variables are set in the tvFavorites_MouseDown
method, since the Click
event does not pass any mouse-related arguments.
We then check the Tag
property of the clicked node to determine if a folder or a shortcut was clicked. Folders do not have anything in their Tag
property, so if it is equal to null
, then we know it's a folder. If a shortcut was clicked, we store the shortcut's URL and the name, and then raise an event notifying any subscribers that a shortcut has been clicked.
If a folder was clicked, we store the current folder name, and then collapse any sibling nodes so that only one folder per level is open. We collapse the sibling folders using the following code:
// Collapses all siblings nodes.
private void CollapseSiblings(TreeNode aobjNode)
{
TreeNode objNode = currentNode.PrevNode;
while (objNode != null)
{
if (objNode.IsExpanded)
{
objNode.Collapse();
// Since only one folder can be expanded
.. at a time, we can go ahead and exit the loop as
// soon as an expanded folder has been collapsed
break;
}
objNode = objNode.PrevNode;
}
// If an expanded folder was not found
// in the above loop, go ahead
// and search the opposite direction
if (objNode == null)
{
objNode = currentNode.NextNode;
while (objNode != null)
{
if (objNode.IsExpanded)
{
objNode.Collapse();
// Since only one folder can be expanded
// at a time, we can go ahead and exit the loop as
// soon as an expanded folder has been collapsed
break;
}
objNode = objNode.NextNode;
}
}
}
After collapsing the sibling nodes, the folder is either expanded or collapsed depending on its current state.
Who's messing with My Favorites!
The last piece we need to implement is the ability to refresh the TreeView
if any of the favorites are modified (e.g., renamed, deleted, added, etc.). Fortunately, this has been made very simple for us to accomplish with C# in .NET, by using the FileSystemWatcher
class. This class can be used to monitor various actions on a file system and raise events accordingly.
Here is the code we're going to use for our purposes:
private void InitializeFileSystemWatcher()
{
// Create a new file system watcher object
_objFSW = new FileSystemWatcher();
// Set the path to be watched (the current user's Favorites)
_objFSW.Path = _strFavoritesPath;
// Set the modification filters
_objFSW.NotifyFilter = NotifyFilters.LastAccess |
NotifyFilters.LastWrite |
NotifyFilters.DirectoryName | NotifyFilters.FileName;
// Set the filter mask (i.e. watch all files)
_objFSW.Filter = "*.*";
// We want to watch subdirectories as well
_objFSW.IncludeSubdirectories = true;
// Setup the event handlers
_objFSW.Changed += new FileSystemEventHandler(FSW_OnChanged);
_objFSW.Created += new FileSystemEventHandler(FSW_OnChanged);
_objFSW.Deleted += new FileSystemEventHandler(FSW_OnChanged);
_objFSW.Renamed += new RenamedEventHandler(FSW_OnRenamed);
// Let's start watching
_objFSW.EnableRaisingEvents = true;
}
// This event handler is called when a file/folder has been modified.
private void fsw_OnChanged(object source, FileSystemEventArgs e)
{
// We must invoke a delegate on the underlying control's thread
// to refresh the favorites list.
this.Invoke(new EventHandler(fsw_Reload));
}
// This event handler is called when a file/folder has been renamed.
private void fsw_OnRenamed(object source, RenamedEventArgs e)
{
// We must invoke a delegate on the underlying control's thread
// to refresh the favorites list.
this.Invoke(new EventHandler(fsw_Reload));
}
// This event is called when the favorites list needs to be refreshed.
private void fsw_Reload(object source, EventArgs args)
{
LoadFavorites();
}
First, we create a FileSystemWatcher
class and set its various properties. For example, the Path
property tells the FileSystemWatcher
class which folder to monitor. The IncludeSubdirectories
property tells the FileSystemWatcher
to include any subdirectories while it's monitoring for events. The other properties are mostly self-explanatory. If you have questions about any of them, they're readily available in the MSDN help.
Next, we set the event handlers for each of the four events - Changed
, Created
, Deleted
, and Renamed
. The first three events are handled by the same event handler, while the Renamed
event is handled by a separate handler - only because of a different method signature.
Each of the event handler's only purpose is to refresh the favorites list since something has changed in the file system. However, since the FileSystemWatcher
executes on a separate thread from that of the TreeView
control, we have to call the LoadFavorites
method using the Invoke
method of the control. This method takes a reference to a delegate that will be executed on the same thread of execution as the control.
Conclusion
Well, that pretty much covers the important parts of the control. There have been performance and usability improvements added to this release that weren't specifically covered in the article, but suffice it to say that this version is easier and more responsive to use than the previous version. Give the demo project a try, play around, and let me know what you think. Maybe, you will find a good use for this control as well.
To Do
There are several enhancements that could be made to this control to make it more complete. I may add some of these features in a future release, and then again, I may not :)
- Complete the context menu for URL/folder properties (e.g., Open, Rename, Delete, etc.). This release has the context menu, but it is not yet functional.
- Implement the ability to display a "favicon" for a URL, if one exists.
- Modify the
LoadFavorites
method to refresh only those nodes affected by a change in the favorites (e.g., if a URL is deleted from the favorites list, then delete that node from theTreeView
instead of refreshing the entireTreeView
). - Add exception handling.
I'm sure there are other features that I have not yet thought of. If you think of anything cool you would like to see added to the control, let me know, and I'll see what I can do. Likewise, if you see any way of improving what I have done so far (remember, I'm relatively new to C#), then please let me know as I would like to learn from everyone else as well.