DirTreeViewHelper (for Windows.Forms)
A lazy-loaded directory that will display in an existing treeview. It's like FolderBrowserDialog when you don't want a dialog. See DirectoryView for a UserControl that uses it.. You'll need to hook your treeView to an imageList (image indexes from 0: folder image; drive; disabled drive; computer/desktop).
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
namespace Library.Forms
{
/// <summary>
/// Helper class to update a treeview with a directory (insert path via <see cref="PopulateTreeView"/>)
/// </summary>
/// <remarks>
/// <list type="table">
/// <listheader>
/// <term>TreeView event</term>
/// <description>Call this method</description>
/// </listheader>
/// <item>
/// <term>Constructor</term>
/// <description>Ctor with Treeview reference, then <see cref="PopulateTreeView"/></description> with directoryInfo
/// </item>
/// <item>
/// <term>NodeMouseClick</term>
/// <description><see cref="GetDirectory"/></description>
/// </item>
/// <item>
/// <term>BeforeExpand</term>
/// <description><see cref="ExpandNode"/></description>
/// </item>
/// </list>
/// </remarks>
public class DirTreeViewHelper
{
#region Public methods
public DirTreeViewHelper(TreeView tv)
{
TreeView = tv;
TreeView.HideSelection = false; //show selection if control does not have focus
}
public TreeView TreeView { get; set; }
/// <summary>
/// Populates the tree view for a specific directory
/// </summary>
/// <param name="dir">The directory.</param>
public void PopulateTreeView(DirectoryInfo dir)
{
// Suppress repainting the TreeView until all the objects have been created.
TreeView.BeginUpdate();
if (TreeView.Nodes.Count == 0)
InitTreeView(dir);
else
UpdateTree(dir);
TreeView.EndUpdate();
}
/// <summary>
/// Gets the directory from a treeNode. Forces a lazy load of child subdirectories
/// </summary>
/// <param name="node">The node.</param>
/// <returns></returns>
public DirectoryInfo GetDirectory(TreeNode node)
{
DirectoryInfo dir = node.Tag as DirectoryInfo;
if (dir == null)
{
DriveInfo drive = node.Tag as DriveInfo;
if (drive != null && drive.IsReady)
dir = drive.RootDirectory;
}
if (dir != null)
{
dir.Refresh();
if (!dir.Exists)
{
node.Remove();
return null;
}
//lazy load
DirectoryInfo[] subSubDirs = dir.GetDirectories();
if (subSubDirs.Length > 0 && node.Nodes.Count == 0)
{
GetDirectories(subSubDirs, node);
}
}
return dir;
}
/// <summary>
/// Expands the TreeNode. Forces a lazy load of the grandchild nodes.
/// </summary>
/// <param name="node">The node.</param>
public void ExpandNode(TreeNode node)
{
DirectoryInfo di = node.Tag as DirectoryInfo;
di.Refresh();
if (!di.Exists)
{
node.Remove();
return;
}
Debug.WriteLine("Expanding " + ((di != null) ? di.FullName : null));
foreach (TreeNode child in node.Nodes)
{
//check all child nodes are populated
if (child.Nodes.Count > 0) continue;
DirectoryInfo dir = child.Tag as DirectoryInfo;
if (dir == null) continue;
dir.Refresh();
if (!dir.Exists)
{
child.Remove();
continue;
}
Debug.WriteLine("Expanding " + dir.FullName);
DirectoryInfo[] subDirs = dir.GetDirectories();
if (subDirs.Length > 0)
{
GetDirectories(subDirs, child);
}
}
}
#endregion
/// <summary>
/// Updates the treeview (nodes already exist).
/// Add the new folders (if doesn't already exist)
/// Expand treenodes down to the directory
/// </summary>
/// <param name="dir">The directory.</param>
private void UpdateTree(DirectoryInfo dir)
{
TreeNode treeRoot = TreeView.Nodes[0];
treeRoot.Expand(); //in case the root node isn't expanded
DirectoryInfo driveRoot = dir.Root;
TreeNode driveRootNode = FindSubNode(treeRoot, driveRoot.Name);
if (driveRootNode == null)
{
AddFolderInNewDrive(dir, treeRoot, driveRoot);
return;
}
UpdateTreeFromRoot(dir, driveRootNode);
}
/// <summary>
/// Updates the tree from node that contains the drive.
/// </summary>
/// <param name="dir">The directory.</param>
/// <param name="driveRootNode">The drive root node.</param>
private void UpdateTreeFromRoot(FileSystemInfo dir, TreeNode driveRootNode)
{
driveRootNode.Expand(); //expand the root eg C:\
//from root go up the parents until we find one that exists, and join it up
string[] folders = dir.FullName.Split(Path.DirectorySeparatorChar);
TreeNode node = driveRootNode; //keep track of the node level
for (int i = 1; i < folders.Length; i++) //loop down each subnode
{
string folder = folders[i];
TreeNode dirNode = FindSubNode(node, folder);
if (dirNode != null) //already exists, ensure expanded
{
node = dirNode;
node.Expand();
continue;
}
//create new node
Debug.WriteLine("Creating new subNode");
DirectoryInfo subDir =
new DirectoryInfo(string.Join(Path.DirectorySeparatorChar.ToString(), folders, 0, i));
node = new TreeNode(subDir.Name, 0, 0);
node.Tag = subDir;
node.Expand();
}
TreeView.SelectedNode = node;
}
/// <summary>
/// Finds the sub TreeNode which has the specified name string.
/// </summary>
/// <param name="root">The TreeNode root.</param>
/// <param name="name">The name (folder name in this case.</param>
/// <returns></returns>
private static TreeNode FindSubNode(TreeNode root, string name)
{
TreeNode found = null;
foreach (TreeNode subNode in root.Nodes)
{
if (subNode.Text.Equals(name, StringComparison.OrdinalIgnoreCase))
{
found = subNode;
break;
}
}
return found;
}
/// <summary>
/// Adds the folder into a new drive that has been added (eg a USB drive)
/// </summary>
/// <param name="dir">The directory.</param>
/// <param name="rootNode">The root node.</param>
/// <param name="rootDir">The root directory.</param>
private void AddFolderInNewDrive(DirectoryInfo dir, TreeNode rootNode, FileSystemInfo rootDir)
{
//add this drive
TreeNode node = new TreeNode(rootDir.Name, 1, 1);
rootNode.Nodes.Add(node);
node.Tag = rootDir;
if (!IsSame(rootDir, dir)) PopulateToPath(node, dir);
node.Expand();
}
/// <summary>
/// Initialize the tree view.
/// The structure is desktop/drives/ and then expands down to specified directory
/// </summary>
/// <param name="dir">The directory.</param>
private void InitTreeView(DirectoryInfo dir)
{
DirectoryInfo desktop = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Desktop));
TreeNode treeRoot = new TreeNode(desktop.Name, 3, 3);
treeRoot.Tag = desktop;
TreeView.Nodes.Add(treeRoot);
foreach (DriveInfo drive in DriveInfo.GetDrives())
{
int img = drive.IsReady ? 1 : 2;
TreeNode node = new TreeNode(drive.Name, img, img);
treeRoot.Nodes.Add(node);
if (!drive.IsReady)
{
node.Tag = drive;
continue;
}
node.Tag = drive.RootDirectory;
//if this drive is the root of the directory we're showing
if (IsSame(drive.RootDirectory, dir.Root))
{
//if the drive root is the directory we want, no need to populate down
if (!IsSame(drive.RootDirectory, dir))
PopulateToPath(node, dir);
else
GetDirectories(drive.RootDirectory.GetDirectories(), node);
}
else
{
GetDirectories(drive.RootDirectory.GetDirectories(), node);
}
}
treeRoot.Expand();
}
/// <summary>
/// Determines whether the specified directories are same by name
/// </summary>
/// <param name="d1">The first directory.</param>
/// <param name="d2">The second directory.</param>
/// <returns>
/// <c>true</c> if the specified directory is same; otherwise, <c>false</c>.
/// </returns>
private static bool IsSame(FileSystemInfo d1, FileSystemInfo d2)
{
return d1.Name.Equals(d2.Name, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Populates TreeView down to a specific path.
/// </summary>
/// <param name="node">The node.</param>
/// <param name="dir">The directory.</param>
private void PopulateToPath(TreeNode node, DirectoryInfo dir)
{
if (!dir.Exists) return;
TreeNode baseNode;
//look up
DirectoryInfo root = dir.Root;
if (root == dir)
baseNode = node;
else
baseNode = PopulateParents(dir, root, node);
GetDirectories(dir.GetDirectories(), baseNode);
baseNode.Expand();
TreeView.SelectedNode = baseNode;
}
/// <summary>
/// Populates the parents of a directory until it joins the root directory (i.e. the drive root)
/// </summary>
/// <param name="startDir">The start directory.</param>
/// <param name="rootDir">The root directory.</param>
/// <param name="node">The node.</param>
/// <returns></returns>
private static TreeNode PopulateParents(DirectoryInfo startDir,
FileSystemInfo rootDir,
TreeNode node)
{
TreeNode baseNode = MakeNode(startDir);
TreeNode child = baseNode;
DirectoryInfo parent = startDir.Parent;
while (parent != null && !IsSame(parent, rootDir))
{
TreeNode aNode = MakeNode(parent);
foreach (DirectoryInfo subDir in parent.GetDirectories())
{
if (subDir.Name != child.Text)
aNode.Nodes.Add(MakeParentNode(subDir));
else
aNode.Nodes.Add(child);
}
aNode.Expand();
parent = parent.Parent;
child = aNode;
}
node.Nodes.Add(child);
node.Expand();
return baseNode;
}
/// <summary>
/// Makes a TreeNode for a folder.
/// </summary>
/// <param name="dir">The directory.</param>
/// <returns></returns>
private static TreeNode MakeNode(FileSystemInfo dir)
{
Debug.WriteLine("Node " + dir.FullName);
TreeNode node = new TreeNode(dir.Name, 0, 0);
node.Tag = dir;
return node;
}
/// <summary>
/// Makes the parent TreeNode.
/// </summary>
/// <param name="dir">The directory.</param>
/// <returns></returns>
private static TreeNode MakeParentNode(DirectoryInfo dir)
{
TreeNode node = MakeNode(dir);
DirectoryInfo[] subSubDirs = dir.GetDirectories();
if (subSubDirs.Length != 0)
{
foreach (DirectoryInfo subDir in subSubDirs)
{
Debug.WriteLine("ParentNode " + subDir.FullName);
node.Nodes.Add(MakeNode(subDir));
}
}
return node;
}
/// <summary>
/// Add the subdirectories to a TreeNode. Not recursive so we can lazy load.
/// </summary>
/// <param name="subDirs">The sub dirs.</param>
/// <param name="nodeToAddTo">The node to add to.</param>
private static void GetDirectories(IEnumerable<DirectoryInfo> subDirs, TreeNode nodeToAddTo)
{
foreach (DirectoryInfo subDir in subDirs)
{
Debug.WriteLine(subDir.FullName);
if ((subDir.Attributes & FileAttributes.Hidden) != 0) continue;
TreeNode node = new TreeNode(subDir.Name, 0, 0);
node.Tag = subDir;
//recurse with a depth marker
//DirectoryInfo[] subSubDirs = subDir.GetDirectories();
//if (subSubDirs.Length != 0 && depth < 3)
// GetDirectories(subSubDirs, node, depth + 1);
nodeToAddTo.Nodes.Add(node);
}
}
}
}