Click here to Skip to main content
15,886,049 members
Articles / Programming Languages / XML

FolderTreeView Control

Rate me:
Please Sign up or sign in to vote.
4.87/5 (33 votes)
25 Oct 20023 min read 390.8K   5K   129  
A simple explorer-like FolderTreeView control for C#
/*

	Windows Forms Folder Tree View control for .Net
	Version 1.1, posted 20-Oct-2002
	(c)Copyright 2002 Furty (furty74@yahoo.com). All rights reserved.
	Free for any use, so long as copyright is acknowledged.
	
	This is an all-new version of the FolderTreeView control I posted here at CP some weeks ago.
	The control now starts in the Desktop namespace, and a new DrillToFolder method has been added
	so the startup folder can be specified. Please note that this control is not intended to have 
	all of the functionality of the actual Windows Explorer TreeView - it is a light-weight control 
	designed for use in projects where you want to supply a treeview for folder navigation, without supporting
	windows shell extensions. If you are looking for a control that supports shell extensions
	you should be looking at the excellent �xplorerTreeControl submitted by Carlos H Perez at the CP website.
	
	The 3 classes that make up the control have been merged into the one file here for ease of
	integration into your own projects. The reason for separate classes is that this code has been
	extracted from a much larger project I'm working on, and the code that is not required for this
	control has been removed.	
	
	Acknowledgments:
	Substantial portions of the ShellOperations and ExtractIcons classes were borrowed from the 
	FTPCom article written by Jerome Lacaille, available on the www.codeproject.com website.
	
	If you improve this control, please email me the updated source, and if you have any 
	comments or suggestions, please post your thoughts in the feedback section on the 
	codeproject.com page for this control.
	
	Version 1.11 Changes:
	Updated the GetDesktopIcon method so that the small (16x16) desktop icon is returned instead of the large version
	Added code to give the Desktop root node a FolderItem object tag equal to the DesktopDirectory SpecialFolder,
	this ensures that the desktop node returns a file path.
 
 */
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;
using System.IO;

using System.Diagnostics;

namespace Furty.Windows.Forms
{
	#region FolderTreeView Class

	public class FolderTreeView : System.Windows.Forms.TreeView
	{
		private System.Windows.Forms.ImageList folderTreeViewImageList;
		private System.Globalization.CultureInfo cultureInfo = System.Globalization.CultureInfo.CurrentCulture;

		#region Constructors

		public FolderTreeView()
		{
			this.BeforeExpand += new System.Windows.Forms.TreeViewCancelEventHandler(this.TreeViewBeforeExpand);
		}

		public void InitFolderTreeView()
		{
			InitImageList();
			ShellOperations.PopulateTree(this, base.ImageList);
			if(this.Nodes.Count > 0)
			{
				this.Nodes[0].Expand();
			}
		}

		private void InitImageList()
		{
			// setup the image list to hold the folder icons
			folderTreeViewImageList = new System.Windows.Forms.ImageList();
			folderTreeViewImageList.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit;
			folderTreeViewImageList.ImageSize = new System.Drawing.Size(16, 16);
			folderTreeViewImageList.TransparentColor = System.Drawing.Color.Transparent;

			// add the Desktop icon to the image list
			try
			{
				folderTreeViewImageList.Images.Add(ExtractIcons.GetDesktopIcon());
			}
			catch
			{
				// Create a blank icon if the desktop icon fails for some reason
				Bitmap bmp = new Bitmap(16,16);
				Image img = (Image)bmp;
				folderTreeViewImageList.Images.Add((Image)img.Clone());
				bmp.Dispose();
			}
			this.ImageList = folderTreeViewImageList;
		}

		#endregion

		#region Event Handlers

		private void TreeViewBeforeExpand(object sender, System.Windows.Forms.TreeViewCancelEventArgs e)
		{
			this.BeginUpdate();
			ShellOperations.ExpandBranch(e.Node, this.ImageList);
			this.EndUpdate();
		}

		#endregion

		#region Furty.Windows.Forms.FolderTreeView Properties & Methods

		public string GetSelectedNodePath()
		{
			return ShellOperations.GetFilePath(SelectedNode);
		}

		public bool DrillToFolder(string folderPath)
		{
			bool folderFound = false;
			if(Directory.Exists(folderPath)) // don't bother drilling unless the directory exists
			{
				this.BeginUpdate();
				// if there's a trailing \ on the folderPath, remove it unless it's a drive letter
				if(folderPath.Length > 3 && folderPath.LastIndexOf("\\") == folderPath.Length -1)
					folderPath = folderPath.Substring(0, folderPath.Length -1);
				//Start drilling the tree
				DrillTree(this.Nodes[0].Nodes, folderPath.ToUpper(cultureInfo), ref folderFound);
				this.EndUpdate();
			}
			if(!folderFound)
				this.SelectedNode = this.Nodes[0];
			return folderFound;
		}

		private void DrillTree(TreeNodeCollection tnc, string path, ref bool folderFound)
		{
			foreach(TreeNode tn in tnc)
			{
				if(!folderFound)
				{
					this.SelectedNode = tn;
					string tnPath = ShellOperations.GetFilePath(tn).ToUpper(cultureInfo);
					if(path == tnPath && !folderFound)
					{
						this.SelectedNode = tn;
						tn.EnsureVisible();
						folderFound = true;
						break;
					}
					else if(path.IndexOf(tnPath) > -1 && !folderFound)
					{
						tn.Expand();
						DrillTree(tn.Nodes, path, ref folderFound);
					}
				}
			}
		}


		#endregion
        
		#region System.Windows.Forms.TreeView Properties

        public override System.Drawing.Color BackColor
        {
            get
            { return base.BackColor; }
            set
            { base.BackColor = value; }
        }
        
        public override System.Drawing.Image BackgroundImage
        {
            get
            { return base.BackgroundImage; }
            set
            { base.BackgroundImage = value; }
        }
        
        public override System.Drawing.Color ForeColor
        {
            get
            { return base.ForeColor; }
            set
            { base.ForeColor = value; }
        }
        
        public override string Text
        {
            get
            { return base.Text; }
            set
            { base.Text = value; }
        }
        
        public override bool AllowDrop
        {
            get
            { return base.AllowDrop; }
            set
            { base.AllowDrop = value; }
        }
        
        public override System.Windows.Forms.AnchorStyles Anchor
        {
            get
            { return base.Anchor; }
            set
            { base.Anchor = value; }
        }
        
        public override System.Windows.Forms.BindingContext BindingContext
        {
            get
            { return base.BindingContext; }
            set
            { base.BindingContext = value; }
        }
        
        public override System.Windows.Forms.ContextMenu ContextMenu
        {
            get
            { return base.ContextMenu; }
            set
            { base.ContextMenu = value; }
        }
        
        public override System.Windows.Forms.Cursor Cursor
        {
            get
            {  return base.Cursor; }
            set
            {  base.Cursor = value; }
        }
        
        public override System.Drawing.Rectangle DisplayRectangle
        {
            get
            { return base.DisplayRectangle; }
        }
        
        public override System.Windows.Forms.DockStyle Dock
        {
            get
            { return base.Dock;  }
            set
            { base.Dock = value; }
        }
        
        public override bool Focused
        {
            get
            { return base.Focused; }
        }
        
        public override System.Drawing.Font Font
        {
            get
            { return base.Font; }
            set
            { base.Font = value; }
        }
        
        public override System.Windows.Forms.RightToLeft RightToLeft
        {
            get
            { return base.RightToLeft; }
            set
            { base.RightToLeft = value; }
        }
        
        public override System.ComponentModel.ISite Site
        {
            get
            { return base.Site; }
            set
            { base.Site = value; }
        }

		#endregion

		#region System.Windows.Forms.TreeView Overrides
 
        public override void ResetText()
        {
            base.ResetText();
        }
        
        public override void Refresh()
        {
            base.Refresh();
        }
        
        public override void ResetRightToLeft()
        {
            base.ResetRightToLeft();
        }
        
        public override void ResetForeColor()
        {
            base.ResetForeColor();
        }
        
        public override void ResetFont()
        {
            base.ResetFont();
        }
        
        public override void ResetCursor()
        {
            base.ResetCursor();
        }
        
        public override void ResetBackColor()
        {
            base.ResetBackColor();
        }
        
        public override bool PreProcessMessage(ref System.Windows.Forms.Message msg)
        {
            return base.PreProcessMessage(ref msg);
        }
        
        public override System.Runtime.Remoting.ObjRef CreateObjRef(System.Type requestedType)
        {
            return base.CreateObjRef(requestedType);
        }
        
        public override object InitializeLifetimeService()
        {
            return base.InitializeLifetimeService();
        }
        
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
        
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }
        
        public override string ToString()
        {
            return base.ToString();
        }

		#endregion

    }

	#endregion

	#region ShellOperations Class

	public class ShellOperations
	{

		#region ShellFolder Enums
		// Enums for standard Windows shell folders
		public enum ShellFolder
		{
			Desktop = Shell32.ShellSpecialFolderConstants.ssfDESKTOP, 
			DesktopDirectory = Shell32.ShellSpecialFolderConstants.ssfDESKTOPDIRECTORY,
			MyComputer = Shell32.ShellSpecialFolderConstants.ssfDRIVES,
			MyDocuments = Shell32.ShellSpecialFolderConstants.ssfPERSONAL,
			MyPictures = Shell32.ShellSpecialFolderConstants.ssfMYPICTURES,
			History = Shell32.ShellSpecialFolderConstants.ssfHISTORY,
			Favorites = Shell32.ShellSpecialFolderConstants.ssfFAVORITES,
			Fonts = Shell32.ShellSpecialFolderConstants.ssfFONTS,
			ControlPanel = Shell32.ShellSpecialFolderConstants.ssfCONTROLS,
			TemporaryInternetFiles = Shell32.ShellSpecialFolderConstants.ssfINTERNETCACHE,
			MyNetworkPlaces = Shell32.ShellSpecialFolderConstants.ssfNETHOOD,
			NetworkNeighborhood = Shell32.ShellSpecialFolderConstants.ssfNETWORK,
			ProgramFiles = Shell32.ShellSpecialFolderConstants.ssfPROGRAMFILES,
			RecentFiles = Shell32.ShellSpecialFolderConstants.ssfRECENT,
			StartMenu = Shell32.ShellSpecialFolderConstants.ssfSTARTMENU,
			Windows = Shell32.ShellSpecialFolderConstants.ssfWINDOWS,
			Printers = Shell32.ShellSpecialFolderConstants.ssfPRINTERS,
			RecycleBin = Shell32.ShellSpecialFolderConstants.ssfBITBUCKET,
			Cookies = Shell32.ShellSpecialFolderConstants.ssfCOOKIES,
			ApplicationData = Shell32.ShellSpecialFolderConstants.ssfAPPDATA,
			SendTo = Shell32.ShellSpecialFolderConstants.ssfSENDTO,
			StartUp = Shell32.ShellSpecialFolderConstants.ssfSTARTUP
		}
		#endregion
		
		#region FolderTreeView Methods

		#region GetFilePath
		public static string GetFilePath(TreeNode tn)
		{
			try
			{
				Shell32.FolderItem folderItem = (Shell32.FolderItem)tn.Tag;
				string folderPath = folderItem.Path;
				if(Directory.Exists(folderPath))
					return folderPath;
				else
					return "";
			}
			catch
			{
				return "";
			}
		}
		#endregion

		#region Populate Tree
		public static void PopulateTree(TreeView tree, ImageList imageList)
		{
			int imageCount = imageList.Images.Count -1;
			tree.Nodes.Clear();
			AddRootNode(tree, ref imageCount, imageList, ShellFolder.Desktop, true);
			if(tree.Nodes.Count > 1)
			{
				tree.SelectedNode = tree.Nodes[1];
				ExpandBranch(tree.Nodes[1], imageList);
			}
		}
		#endregion

		#region Add Root Node
		private static void AddRootNode(TreeView tree, ref int imageCount, ImageList imageList, ShellFolder shellFolder, bool getIcons)
		{
			Shell32.Shell shell32 = new Shell32.ShellClass();
			Shell32.Folder shell32Folder = shell32.NameSpace(shellFolder);
			Shell32.FolderItems items = shell32Folder.Items();

			tree.Nodes.Clear();
			TreeNode desktop = new TreeNode("Desktop", 0, 0);
	
			// Added in version 1.11
			// add a FolderItem object to the root (Desktop) node tag that corresponds to the DesktopDirectory namespace
			// This ensures that the GetSelectedNodePath will return the actual Desktop folder path when queried.
			// There's possibly a better way to create a Shell32.FolderItem instance for this purpose, 
			// but I surely don't know it

			Shell32.Folder dfolder = shell32.NameSpace(ShellFolder.DesktopDirectory);
			foreach(Shell32.FolderItem fi in dfolder.ParentFolder.Items())
			{
				if(fi.Name == dfolder.Title)
				{
					desktop.Tag = fi;
					break;
				}
			}

			// Add the Desktop root node to the tree
			tree.Nodes.Add(desktop);
			
			// iterate through the Desktop namespace and populate the first level nodes
			foreach(Shell32.FolderItem item in items)
			{
				if(item.IsFolder) // this ensures that desktop shortcuts etc are not displayed
				{
					TreeNode tn = AddTreeNode(item, ref imageCount, imageList, getIcons);
					desktop.Nodes.Add(tn);
					CheckForSubDirs(tn, imageList);
				}
			}
			
		}
		#endregion

		#region Fill Sub Dirs
		private static void FillSubDirectories(TreeNode tn, ref int imageCount, ImageList imageList, bool getIcons)
		{
			Shell32.FolderItem folderItem = (Shell32.FolderItem)tn.Tag;
			Shell32.Folder folder = (Shell32.Folder)folderItem.GetFolder;

			foreach(Shell32.FolderItem item in folder.Items())
			{
				if(item.IsFileSystem && item.IsFolder && !item.IsBrowsable)
				{
					TreeNode ntn = AddTreeNode(item, ref imageCount, imageList, getIcons);
					tn.Nodes.Add(ntn);
					CheckForSubDirs(ntn, imageList);
				}
			}
		}
		#endregion

		#region Create Dummy Node
		private static void CheckForSubDirs(TreeNode tn, ImageList imageList)
		{
			if(tn.Nodes.Count == 0)
			{
				try
				{
					// create dummy nodes for any subfolders that have further subfolders
					Shell32.FolderItem folderItem = (Shell32.FolderItem)tn.Tag;
					Shell32.Folder folder = (Shell32.Folder)folderItem.GetFolder;

					bool hasFolders = false;
					foreach(Shell32.FolderItem item in folder.Items())
					{
						if(item.IsFileSystem && item.IsFolder && !item.IsBrowsable)
						{
							hasFolders = true;
							break;
						}
					}
					if(hasFolders)
					{
						TreeNode ntn = new TreeNode();
						ntn.Tag = "DUMMYNODE";
						tn.Nodes.Add(ntn);
					}
				}
				catch {}
			}
		}
		#endregion

		#region Expand Branch
		public static void ExpandBranch(TreeNode tn, ImageList imageList)
		{
			// if there's a dummy node present, clear it and replace with actual contents
			if(tn.Nodes.Count == 1 && tn.Nodes[0].Tag.ToString() == "DUMMYNODE")
			{
				tn.Nodes.Clear();
				Shell32.FolderItem folderItem = (Shell32.FolderItem)tn.Tag;
				Shell32.Folder folder = (Shell32.Folder)folderItem.GetFolder;
				int imageCount = imageList.Images.Count - 1;
				foreach(Shell32.FolderItem item in folder.Items())
				{
					if(item.IsFileSystem && item.IsFolder && !item.IsBrowsable)
					{
						TreeNode ntn = AddTreeNode(item, ref imageCount, imageList, true);
						tn.Nodes.Add(ntn);
						CheckForSubDirs(ntn, imageList);
					}
				}
			}
		}
		#endregion

		#region Add Tree Node
		private static TreeNode AddTreeNode(Shell32.FolderItem item, ref int imageCount, ImageList imageList, bool getIcons)
		{
			TreeNode tn = new TreeNode();
			tn.Text = item.Name;
			tn.Tag = item;

			if(getIcons)
			{
				try
				{
					imageCount++;
					tn.ImageIndex = imageCount;
					imageCount++;
					tn.SelectedImageIndex = imageCount;
					imageList.Images.Add(ExtractIcons.GetIcon(item.Path, false, imageList)); // normal icon
					imageList.Images.Add(ExtractIcons.GetIcon(item.Path, true, imageList)); // selected icon
				}
				catch // use default 
				{
					tn.ImageIndex = 1;
					tn.SelectedImageIndex = 2;
				}
			}
			else // use default
			{
				tn.ImageIndex = 1;
				tn.SelectedImageIndex = 2;
			}
			return tn;
		}

		#endregion

		#endregion
	}

	#endregion

	#region ExtractIcons Class

	public class ExtractIcons
	{
		#region Structs & Enum

		[StructLayout(LayoutKind.Sequential)]
			private struct SHFILEINFO
		{
			public SHFILEINFO(bool b)
			{
				hIcon=IntPtr.Zero;iIcon=0;dwAttributes=0;szDisplayName="";szTypeName="";
			}
			public IntPtr hIcon;
			public int iIcon;
			public uint dwAttributes;
			[MarshalAs(UnmanagedType.LPStr, SizeConst=260)]
			public string szDisplayName;
			[MarshalAs(UnmanagedType.LPStr, SizeConst=80)]
			public string szTypeName;
		};

		private enum SHGFI
		{
			SHGFI_ICON =             0x000000100,     // get icon
			SHGFI_DISPLAYNAME =      0x000000200,     // get display name
			SHGFI_TYPENAME =         0x000000400,     // get type name
			SHGFI_ATTRIBUTES =       0x000000800,     // get attributes
			SHGFI_ICONLOCATION =     0x000001000,     // get icon location
			SHGFI_EXETYPE =          0x000002000,     // return exe type
			SHGFI_SYSICONINDEX =     0x000004000,     // get system icon index
			SHGFI_LINKOVERLAY =      0x000008000,     // put a link overlay on icon
			SHGFI_SELECTED =         0x000010000,     // show icon in selected state
			SHGFI_ATTR_SPECIFIED =   0x000020000,     // get only specified attributes
			SHGFI_LARGEICON =        0x000000000,     // get large icon
			SHGFI_SMALLICON =        0x000000001,     // get small icon
			SHGFI_OPENICON =         0x000000002,     // get open icon
			SHGFI_SHELLICONSIZE =    0x000000004,     // get shell size icon
			SHGFI_PIDL =             0x000000008,     // pszPath is a pidl
			SHGFI_USEFILEATTRIBUTES = 0x000000010     // use passed dwFileAttribute
		}

		#endregion

		#region Get Folder Icons

		[DllImport("Shell32.dll")]
		private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, 
			out SHFILEINFO psfi, uint cbfileInfo, SHGFI uFlags );

		public static Icon GetIcon(string strPath, bool selected, ImageList imageList)
		{
			SHFILEINFO info = new SHFILEINFO(true);
			int cbFileInfo = Marshal.SizeOf(info);
			SHGFI flags;
			if (!selected)
				flags = SHGFI.SHGFI_ICON|SHGFI.SHGFI_SMALLICON;
			else
				flags = SHGFI.SHGFI_ICON|SHGFI.SHGFI_SMALLICON|SHGFI.SHGFI_OPENICON;

			SHGetFileInfo(strPath, 256, out info,(uint)cbFileInfo, flags);
			return Icon.FromHandle(info.hIcon);
		}

		#endregion

		#region Get Desktop Icon

		// Retreive the desktop icon from Shell32.dll - it always appears at index 34 in all shell32 versions.
		// This is probably NOT the best way to retreive this icon, but it works - if you have a better way
		// by all means let me know..

//		[DllImport("Shell32.dll", CharSet=CharSet.Auto)]
//		public static extern IntPtr ExtractIcon(int hInst, string lpszExeFileName, int nIconIndex);
//
//		public static Icon GetDesktopIcon()
//		{
//			IntPtr i = ExtractIcon(0, Environment.SystemDirectory + "\\shell32.dll", 34);
//			return Icon.FromHandle(i);
//		}

		// Updated this method in v1.11 so that the icon returned is a small icon, not a large icon as
		// returned by the old method above

		[DllImport("Shell32.dll", CharSet=CharSet.Auto)]
		public static extern uint ExtractIconEx(
			string lpszFile, int nIconIndex, IntPtr[] phiconLarge, IntPtr[] phiconSmall, uint nIcons );

		public static Icon GetDesktopIcon()
		{
			IntPtr[] handlesIconLarge = new IntPtr[1];
			IntPtr[] handlesIconSmall = new IntPtr[1];
			uint i = ExtractIconEx(Environment.SystemDirectory + "\\shell32.dll", 34, 
				handlesIconLarge, handlesIconSmall, 1);

			return Icon.FromHandle(handlesIconSmall[0]);
		}
		
		#endregion

	}

	#endregion


}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.


Written By
Web Developer
Thailand Thailand
Furty will code for food.

Comments and Discussions