创建Internet Explorer收藏夹控件






4.19/5 (11投票s)
2002 年 10 月 2 日
9分钟阅读

182437

3321
如何创建 Internet Explorer 收藏夹控件。
引言
在最初发布这篇文章近四年后,我终于对其进行了更新。我决定将此控件更新到 Visual Studio 2005,因为一年多来我一直在使用这个 IDE(包括测试版)。我在新版本中添加了一些性能改进和一些增强功能(以及一些错误修复)。尽管我现在发布了更新,但它仍处于开发中,我将在不久的将来对其进行进一步的增强(例如,完全实现右键菜单)。
创建此控件的最初催化剂来自我正在开发的一款应用程序,该应用程序需要在应用程序本身内查看网站。我想让应用程序用户能够轻松浏览他们喜欢的网站,因此我开始寻找某种“收藏夹”控件,一种看起来和工作方式类似于 Internet Explorer 中的收藏夹浏览器栏的东西。经过一番查找(包括本网站),我决定自己创建一个控件来显示用户的收藏夹列表。
以下是我在初始控件中想要的一些功能:
- 与 Internet Explorer 收藏夹浏览器栏相似的外观和感觉。
- 能够自动加载当前用户的收藏夹。
- 如果收藏夹通过外部源(例如 Internet Explorer、Windows Explorer 等)被修改,能够自动刷新自身。
开始编写代码
在大多数情况下,这是一个非常简单的用户控件。我将重点介绍控件的更有趣方面,而不是详细介绍每一步。您可以下载并查看源代码以获取更多详细信息。
首先,我们创建一个 UserControl
并添加一个 TreeView
控件。我们将 TreeView
的 Dock
属性设置为 Fill
,使其能够拉伸以填充用户控件的整个大小。接下来,我们将 HotTracking
属性设置为 true
,这样当鼠标悬停在 TreeView
节点上时,它们就会被下划线,使其看起来像超链接。
我们在控件中添加了一个图像列表,其中至少包含三个图标。图标的使用方式如下:
- 第一个图标(索引 0)用作快捷方式图标。
- 第二个图标用于表示已打开的文件夹。
- 第三个图标用于表示已关闭的文件夹。
此项目的源代码包含这三个图标。
这样就完成了初始设置任务。现在,让我们看一些代码。
我们要做的第一件事是获取当前用户的收藏夹路径。此控件的原始版本从 Windows 注册表中检索收藏夹路径。这已被更新为使用内置的 Environment
类。以下代码片段显示了如何执行此操作:
private void _GetFavoritesPath()
{
_favoritesPath =
Environment.GetFolderPath(Environment.SpecialFolder.Favorites);
}
加载收藏夹
接下来,我们必须将收藏夹加载到 TreeView
中。下一个代码示例有点长,但在您有机会查看之后,我会解释其工作原理。
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);
}
}
第一个方法 RefreshFavorites
是加载收藏夹的主要驱动程序。我们调用 TreeView
的 BeginUpdate
方法来暂停任何绘图,直到我们完成填充 TreeView
控件。这将防止在填充过程中出现闪烁,并使加载速度更快。
然后我们清空 TreeView
控件,并调用 LoadFavoritesFromFolder
方法,该方法用于递归地将收藏夹根目录中的所有子目录添加到 TreeView
控件。此方法将为找到的每个子目录递归调用。一旦进入 LoadFavoritesFromFolder
方法,如果没有找到子目录,则调用 LoadFavoritesFromPath
方法,该方法将加载当前子目录的所有快捷方式。
请注意,快捷方式是如何存储为带有 .url 扩展名的文本文件的。文件内容采用标准的 Windows INI 格式。要读取快捷方式的 URL,您需要使用 Windows API 调用来读取 INI 文件。我选择为此目的使用我在 The Code Project 上找到的一个INI 类。一旦从 INI 文件中提取了 URL,它就被存储在 TreeView
的当前节点的 Tag
属性中。Text
属性设置为快捷方式的文件名(不带 .url 扩展名)。
这样就完成了将快捷方式加载到 TreeView
控件中,现在让我们自定义一些控件的功能。
让我们自定义
我们需要实现几个小的功能,使我们的控件具有与 Internet Explorer 的收藏夹浏览器栏相似的外观和感觉。
- 当鼠标悬停在文件夹上时,光标应为箭头,当鼠标悬停在快捷方式上时,光标应更改为手形。
- 在任何给定级别只允许打开一个文件夹(即,如果您单击收藏夹列表中的一个文件夹以打开它,然后单击同一级别(或任何更高级别)的另一个文件夹,那么任何其他打开的文件夹都将被折叠)。一旦您有机会看到它运行,这会更有意义。
// 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;
}
上面的代码是 TreeView
的 MouseMove
事件的处理程序。代码首先获取对 TreeView
控件的引用。然后它尝试获取鼠标指针下方的节点的引用。如果鼠标指针下方没有节点,则引用被设置为 null
,并且方法退出。
如果当前节点是文件夹,则光标更改为箭头(默认设置)。如果它是快捷方式,则光标更改为手形。
// 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();
}
}
}
}
上面的代码是 TreeView
的 Click
事件的处理程序。我使用了 Click
事件而不是 AfterSelect
事件,因为一旦选择了节点,AfterSelect
事件将不会再次调用,直到您单击另一个节点。由于我希望每次单击节点时(而不必离开节点)都展开/折叠节点,因此我将代码放在此处。
首先,我们获取对 TreeView
控件的引用,类似于 tvFavorites_MouseMove
方法。然后我们获取对单击节点的引用。但是,请注意实例变量 _intX
和 _intY
的使用。这些变量在 tvFavorites_MouseDown
方法中设置,因为 Click
事件不传递任何与鼠标相关的参数。
然后我们检查所单击节点的 Tag
属性以确定单击的是文件夹还是快捷方式。文件夹在其 Tag
属性中没有任何内容,因此如果它等于 null
,那么我们就知道它是一个文件夹。如果单击了一个快捷方式,我们会存储快捷方式的 URL 和名称,然后引发一个事件通知所有订阅者已单击一个快捷方式。
如果单击了一个文件夹,我们会存储当前文件夹的名称,然后折叠任何同级节点,以便每个级别只有一个文件夹是打开的。我们使用以下代码折叠同级文件夹:
// 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;
}
}
}
折叠同级节点后,文件夹将根据其当前状态被展开或折叠。
谁在捣乱我的收藏夹!
我们需要实现的最后一部分是刷新 TreeView
的能力,如果任何收藏夹被修改(例如,重命名、删除、添加等)。幸运的是,使用 C# 和 .NET 中的 FileSystemWatcher
类,这已经变得非常简单。此类别可用于监视文件系统上的各种操作并相应地引发事件。
这是我们将用于此目的的代码:
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();
}
首先,我们创建一个 FileSystemWatcher
类并设置其各种属性。例如,Path
属性告诉 FileSystemWatcher
类要监视哪个文件夹。IncludeSubdirectories
属性告诉 FileSystemWatcher
在监视事件时包括任何子目录。其他属性大多是不言自明的。如果您对其中任何一个有疑问,可以在 MSDN 帮助中找到。
接下来,我们为四个事件 - Changed
、Created
、Deleted
和 Renamed
设置事件处理程序。前三个事件由同一个事件处理程序处理,而 Renamed
事件由单独的处理程序处理 - 仅由于方法签名不同。
每个事件处理程序的唯一目的是刷新收藏夹列表,因为文件系统已发生更改。但是,由于 FileSystemWatcher
在与 TreeView
控件不同的线程上执行,因此我们必须使用控件的 Invoke
方法调用 LoadFavorites
方法。此方法采用指向委托的引用,该委托将在与控件相同的执行线程上执行。
结论
好了,这基本涵盖了控件的重要部分。此版本中添加了性能和可用性改进,这些改进在本文中没有特别介绍,但可以说,此版本比以前的版本更容易使用且响应更灵敏。尝试一下演示项目,随意玩玩,让我知道您的想法。也许您也会发现此控件的用途。
待办事项
可以对该控件进行几项增强,使其更加完善。我可能会在将来的版本中添加其中一些功能,也可能不会 :)
- 完成 URL/文件夹属性的右键菜单(例如,打开、重命名、删除等)。此版本具有右键菜单,但尚未启用。
- 实现显示 URL 的“favicon”的能力(如果存在)。
- 修改
LoadFavorites
方法,仅刷新受收藏夹更改影响的节点(例如,如果从收藏夹列表中删除了一个 URL,则将该节点从TreeView
中删除,而不是刷新整个TreeView
)。 - 添加异常处理。
我确信还有我还没有想到的其他功能。如果您想到任何您想添加到控件中的很棒的功能,请告诉我,我会看看我能做什么。同样,如果您想到任何可以改进我迄今为止所做的工作的方法(请记住,我对 C# 还比较陌生),请告诉我,因为我也想向大家学习。