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

Creating an Internet Explorer Favorites Control

0.00/5 (No votes)
17 Aug 2006 1  
How to create an Internet Explorer favorites control.

DTFavorites

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:

  1. Similar look and feel of the Internet Explorer Favorites explorer bar.
  2. Ability to automatically load the favorites of the current user.
  3. 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:

  1. The first icon (index 0) is used as the shortcut icon.
  2. The second icon is used to denote an open folder.
  3. 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.

  1. The cursor should be an arrow when over folders, and should change to a hand when over a shortcut.
  2. 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 :)

  1. 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.
  2. Implement the ability to display a "favicon" for a URL, if one exists.
  3. 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 the TreeView instead of refreshing the entire TreeView).
  4. 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.

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