|
|||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionAlmost 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:
On with the codeFor 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 We add an image list to the control that contains at least three icons. The icons are used as follows:
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 private void _GetFavoritesPath()
{
_favoritesPath =
Environment.GetFolderPath(Environment.SpecialFolder.Favorites);
}
Loading the FavoritesNext, we have to load the favorites into the 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, We then clear the 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 That takes care of loading the shortcuts into the Let's customizeThere 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.
// 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 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 First, we get a reference to the We then check the 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 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 Next, we set the event handlers for each of the four events - 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 ConclusionWell, 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 DoThere 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 :)
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.
|
||||||||||||||||||||||||||||||