Click here to Skip to main content
15,886,110 members
Articles / Programming Languages / C#

C# File Browser

Rate me:
Please Sign up or sign in to vote.
4.93/5 (173 votes)
21 Aug 200628 min read 2.4M   81.5K   587  
A file browser written in C#, very much like Windows Explorer.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Collections;
using System.Globalization;
using System.Runtime.InteropServices;
using ShellDll;
using System.IO;

namespace FileBrowser
{
    #region Delegates

    // This delegate is used for the ContextMenuHoverEvent
    public delegate void ContextMenuMouseHoverEventHandler(object sender, ContextMenuMouseHoverEventArgs e);

    // This delegate is used for the SelectedFolderChangedEvent
    public delegate void SelectedFolderChangedEventHandler(object sender, SelectedFolderChangedEventArgs e);

    #endregion

    #region SpecialFolders

    /// <summary>
    /// This enum is used to indicate which folder should be opened when initialising the browser.
    /// When 'Other' is chosen, the browser will start in the directory given by the StartUpDirectoryOther
    /// property.
    /// </summary>
    public enum SpecialFolders : uint
    {
        UserProfiles = 0x003e,
        DesktopDir_All = 0x0019,
        ApplicationData_All = 0x0023,
        MyDocuments_All = 0x002e,
        MyFavorites_All = 0x001f,
        MyMusic_All = 0x0035,
        MyPictures_All = 0x0036,
        StartMenu_All = 0x0016,
        MyVideos_All = 0x0037,

        Desktop = 0x0000,
        DekstopDir = 0x0010,
        MyComputer = 0x0011,
        MyFavorites = 0x0006,
        ApplicationData = 0x001c,
        MyDocuments = 0x000c,
        MyMusic = 0x000d,
        MyPictures = 0x0027,
        MyVideos = 0x000e,
        MyNetworkPlaces = 0x0012,
        MyDocumentsDir = 0x0005,
        StartMenu = 0x000b,

        ControlPanel = 0x0003,
        Printers = 0x0004,
        ProgramFiles = 0x0026,
        SendTo = 0x0009,
        System = 0x0025,
        Windows = 0x0024,
        RecycleBin = 0x000a,

        Other = 0x1000
    }

    #endregion

    /// <summary>
    /// This control is used to browse the files and folders on your computer as with Windows Explorer
    /// </summary>
    public partial class Browser : UserControl
    {
        #region Fields

        // The shellbrowser used by this browser to get the shellitems of all files and folders
        private ShellBrowser shellBrowser;

        // These wrappers are used for accepting drops on the browser
        private BrowserTVDropWrapper tvDropWrapper;

        // These wrappers are used to allow dragging from the browser
        private BrowserTVDragWrapper tvDragWrapper;

        // These wrappers are used to create the standard context menu's, like in Windows Explorer
        private BrowserTVContextMenuWrapper tvContextWrapper;

        // This field is used to store the control from where dragging has started
        private Control dragStartControl;

        // When this bool is true, selecting a node will change the current directory, 
        // otherwise the current directory won't change
        private bool selectionChange = true, newItemCreated;
        
        // These fields are used to determine the directory to start the browser in
        private SpecialFolders startupDir = SpecialFolders.MyComputer;
        private string otherStartupDir = string.Empty;

        // Selected node and item are the TreeNode and ShellItem of the current directory
        private TreeNode selectedNode;
        private ShellItem selectedItem;

        // These TreeNodes are used very often, so need own fields. They are for the Root (Desktop) and My Computer.
        private TreeNode desktopNode, myCompNode;

        private Thread updateThread;
        private UpdateInvoker updateInvoker;
        private bool updating;

        private bool handleCreated;

        private delegate void UpdateInvoker(object sender, ShellItemUpdateEventArgs e);

        #region Events

        /// <summary>
        /// These event will be raised when the mouse moves over a contextmenu item. This event is used to show the 
        /// help text with that contextmenu item (just like the help text in the statusbar of Windows Explorer).
        /// </summary>
        public event ContextMenuMouseHoverEventHandler ContextMenuMouseHover;
        
        /// <summary>
        /// This event will be raised every time the current directory changes. It will include the new current TreeNode,
        /// ShellItem and the full path to that directory.
        /// </summary>
        public event SelectedFolderChangedEventHandler SelectedFolderChanged;

        #endregion

        #endregion

        public Browser()
        {
            InitializeComponent();

            InitBrowser();
            InitFolderView();
        }

        #region Init Browser

        #region At Constructor

        /// <summary>
        /// Inits the shellbrowser and registeres some events
        /// </summary>
        private void InitBrowser()
        {
            updateInvoker = new UpdateInvoker(ShellItemUpdateInvoke);

            HandleCreated += new EventHandler(FileBrowser_HandleCreated);
            HandleDestroyed += new EventHandler(FileBrowser_HandleDestroyed);
        }

        /// <summary>
        /// Registers the needed events of the TreeView control
        /// </summary>
        private void InitFolderView()
        {
            folderView.BeforeExpand += new TreeViewCancelEventHandler(folderView_BeforeExpand);
            folderView.BeforeSelect += new TreeViewCancelEventHandler(folderView_BeforeSelect);
            folderView.AfterSelect += new TreeViewEventHandler(folderView_AfterSelect);

            folderView.SetSorting(true);
        }

        #endregion

        #region After Handle Created

        /// <summary>
        /// Initialises the ContextMenu wrappers
        /// </summary>
        private void InitContextMenu()
        {
            tvContextWrapper = new BrowserTVContextMenuWrapper(this);
        }

        /// <summary>
        /// If the browser allows dropping, the wrappers for drag/drop are initialised and the events are registered
        /// </summary>
        private void InitDragDrop()
        {
            if (AllowDrop)
            {
                tvDropWrapper = new BrowserTVDropWrapper(this);
                tvDropWrapper.Drop += new DropEventHandler(DropWrapper_Drop);
                tvDragWrapper = new BrowserTVDragWrapper(this);
                tvDragWrapper.DragStart += new DragEnterEventHandler(DragWrapper_DragStart);
                tvDragWrapper.DragEnd += new EventHandler(DragWrapper_DragEnd);
            }
        }

        /// <summary>
        /// Selects the startup directory of the browser
        /// </summary>
        private void InitStartUp()
        {
            if (startupDir != SpecialFolders.Other)
                SelectPath(startupDir, true);
            else
                SelectPath(otherStartupDir, true);

            folderView.Focus();
        }

        /// <summary>
        /// Initialises the base ShellItems, including the Desktop and all it's children and the children of My Computer.
        /// These items are also added to the TreeView and the navigation bar.
        /// </summary>
        private void InitBaseItems()
        {
            if (ShellBrowser == null)
                ShellBrowser = new ShellBrowser();

            desktopNode = new TreeNode(
                ShellBrowser.DesktopItem.Text,
                ShellBrowser.DesktopItem.ImageIndex,
                ShellBrowser.DesktopItem.SelectedImageIndex);
            desktopNode.Tag = ShellBrowser.DesktopItem;
            desktopNode.Name = desktopNode.Text;

            folderView.Nodes.Add(desktopNode);

            selectedNode = desktopNode;
            selectedItem = ShellBrowser.DesktopItem;

            ShellBrowser.DesktopItem.Expand(false, true, IntPtr.Zero);

            foreach (ShellItem desktopChild in ShellBrowser.DesktopItem.SubFolders)
            {
                TreeNode desktopChildNode = new TreeNode(
                    desktopChild.Text,
                    desktopChild.ImageIndex,
                    desktopChild.SelectedImageIndex);
                desktopChildNode.Tag = desktopChild;
                desktopChildNode.Name = desktopChildNode.Text;

                if (desktopChildNode.Text == ShellBrowser.MyComputerName)
                {
                    myCompNode = desktopChildNode;
                    desktopChild.Expand(false, true, IntPtr.Zero);

                    foreach (ShellItem myCompChild in desktopChild.SubFolders)
                    {
                        TreeNode myCompChildNode = new TreeNode(
                            myCompChild.Text,
                            myCompChild.ImageIndex,
                            myCompChild.SelectedImageIndex);
                        myCompChildNode.Tag = myCompChild;
                        myCompChildNode.Name = myCompChildNode.Text;

                        if (myCompChild.HasSubfolder)
                            myCompChildNode.Nodes.Add(string.Empty);

                        desktopChildNode.Nodes.Add(myCompChildNode);
                    }
                }
                else if (desktopChild.HasSubfolder)
                    desktopChildNode.Nodes.Add(string.Empty);

                desktopNode.Nodes.Add(desktopChildNode);
            }
        }

        private void InitUpdate()
        {
            updateThread = new Thread(new ThreadStart(UpdateLoop));
            updateThread.IsBackground = true;
            updating = true;

            ShellBrowser.ShellItemUpdate += new ShellItemUpdateEventHandler(shellBrowser_ShellItemUpdate);
            updateThread.Start();
        }

        #endregion

        #endregion

        #region Events

        #region Browser Events

        /// <summary>
        /// When the handle of the browser is created all wrappers are initialised and the startup directory is selected.
        /// Also the update thread will be started
        /// </summary>
        void FileBrowser_HandleCreated(object sender, EventArgs e)
        {
            InitBaseItems();
            InitContextMenu();
            InitDragDrop();
            InitStartUp();
            InitUpdate();

            GC.Collect();

            handleCreated = true;
        }

        /// <summary>
        /// When the handle is destroyed the update thread must be aborted
        /// </summary>
        void FileBrowser_HandleDestroyed(object sender, EventArgs e)
        {
            if (handleCreated)
            {
                handleCreated = false;

                folderView.Nodes.Clear();

                updating = false;

                ShellBrowser.UpdateCondition.ContinueUpdate = false;

                updateThread.IsBackground = false;
                updateThread.Join(5000);

                GC.Collect();
            }
        }

        #endregion

        #region FolderView Events

        /// <summary>
        /// If selectionChange is true, the current directory is changed after a TreeNode is selected, if that happens
        /// the ListView will be cleared and filled with the contents of the new directory and the 
        /// SelectedFolderChangeEvent will be raised
        /// </summary>
        void folderView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            if (selectionChange)
            {
                ShellItem oldItem = selectedItem;
                ShellItem newItem = e.Node.Tag as ShellItem;

                if (!ShellItem.Equals(oldItem, newItem))
                {
                    OnSelectedFolderChanged(new SelectedFolderChangedEventArgs(folderView.SelectedNode));
                }
            }
        }

        /// <summary>
        /// If selectionChange is true, before a node is selected, the node will be expanded if it's selected by
        /// Mouse or the ShellItem will be expanded and all nodes will be added
        /// </summary>
        void folderView_BeforeSelect(object sender, TreeViewCancelEventArgs e)
        {
            if (selectionChange)
            {
                ShellItem nodeItem = (ShellItem)e.Node.Tag;

                if (e.Action == TreeViewAction.ByMouse && !e.Node.IsExpanded)
                    e.Node.Expand();
                else
                    ExtendTreeNode(e.Node, false);
            }
        }

        /// <summary>
        /// Before a node is expanded, if the ShellItem hasn't been expanded or the child nodes haven't been added,
        /// this method will add all the children to the node.
        /// </summary>
        void folderView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            ShellItem nodeItem = (ShellItem)e.Node.Tag;

            Cursor.Current = Cursors.WaitCursor;
            e.Cancel = !ExtendTreeNode(e.Node, false);
            Cursor.Current = Cursors.Default;

            if (e.Cancel)
                e.Node.Nodes.Clear();
        }

        #endregion

        #endregion

        #region Generated Events

        /// <summary>
        /// This method will raise the OnSelectedFolderChangedEvent. Before raising it the navigation bar item is changed
        /// to match the new current directory and the selectedNode property is set to the new node.
        /// </summary>
        /// <param name="e">The SelectedFolderChangedEventArgs to pass on</param>
        private void OnSelectedFolderChanged(SelectedFolderChangedEventArgs e)
        {
            selectedNode = e.Node;

            if (SelectedFolderChanged != null)
                SelectedFolderChanged(this, e);
        }

        /// <summary>
        /// This method will raise the OnContextMenuMouseHoverEvent.
        /// </summary>
        /// <param name="e">The ContextMenuMouseHoverEventArgs to pass on</param>
        internal void OnContextMenuMouseHover(ContextMenuMouseHoverEventArgs e)
        {
            if (ContextMenuMouseHover != null)
                ContextMenuMouseHover(this, e);
        }

        #endregion

        #region Update File/Folder Changes

        private void UpdateLoop()
        {
            while (updating)
            {
                if (selectedItem != null)
                {
                    ShellBrowser.UpdateCondition.ContinueUpdate = true;
                    selectedItem.Update(true, true);
                }

                Thread.Sleep(500);
            }
        }

        void shellBrowser_ShellItemUpdate(object sender, ShellItemUpdateEventArgs e)
        {
            if (updating)
            {
                if (this.InvokeRequired)
                    Invoke(updateInvoker, sender, e);
                else
                    ShellItemUpdateInvoke(sender, e);
            }
        }

        private void ShellItemUpdateInvoke(object sender, ShellItemUpdateEventArgs e)
        {
            switch (e.UpdateType)
            {
                case ShellItemUpdateType.Created:
                    #region Created
                    {
                        if (e.NewItem.IsFolder)
                        {
                            ShellItem parent = sender as ShellItem;
                            TreeNode parentNode;

                            if (folderView.GetTreeNode(parent, out parentNode))
                            {
                                TreeNode newNode = new TreeNode(
                                    e.NewItem.Text,
                                    e.NewItem.ImageIndex,
                                    e.NewItem.SelectedImageIndex);
                                newNode.Tag = e.NewItem;

                                if (e.NewItem.HasSubfolder)
                                    newNode.Nodes.Add(string.Empty);

                                newNode.Name = newNode.Text;

                                parentNode.Nodes.Add(newNode);
                            }
                        }
                    }
                    #endregion
                    break;

                case ShellItemUpdateType.Deleted:
                    #region Deleted
                    {
                        if (e.OldItem.IsFolder)
                        {
                            ShellItem parent = sender as ShellItem;
                            TreeNode parentNode;

                            if (folderView.GetTreeNode(parent, out parentNode))
                            {
                                parentNode.Nodes.RemoveByKey(e.OldItem.Text);
                            }
                        }
                    }
                    #endregion
                    break;

                case ShellItemUpdateType.Renamed:
                    #region Renamed
                    {
                        if (e.NewItem.IsFolder)
                        {
                            TreeNode node;

                            if (folderView.GetTreeNode(e.NewItem, out node))
                            {
                                node.Text = e.NewItem.Text;
                                node.Name = node.Text;
                                
                                TreeNode parent = node.Parent;
                                parent.Nodes.Remove(node);
                                parent.Nodes.Add(node);
                            }
                        }
                    }
                    #endregion 
                    break;

                case ShellItemUpdateType.IconChange:
                    #region IconChange
                    {
                        if (e.NewItem.IsFolder)
                        {
                            TreeNode node;

                            if (folderView.GetTreeNode(e.NewItem, out node) && node.ImageIndex != e.NewItem.ImageIndex)
                            {
                                node.ImageIndex = e.NewItem.ImageIndex;
                            }
                        }
                    }
                    #endregion
                    break;

                case ShellItemUpdateType.MediaChange:
                    #region MediaChange
                    {
                        TreeNode node;
                        if (folderView.GetTreeNode(e.NewItem, out node))
                        {
                            node.Collapse(true);

                            if (node.Equals(selectedNode) || folderView.IsParentNode(node, selectedNode))
                                folderView.SelectedNode = node.Parent;

                            node.Nodes.Clear();
                            node.ImageIndex = e.NewItem.ImageIndex;

                            if (e.NewItem.HasSubfolder)
                                node.Nodes.Add(string.Empty);
                        }
                    }
                    #endregion
                    break;
            }
        }

        #endregion

        #region Select Path

        /// <summary>
        /// This method is used by PathExists and SelectPath to convert a string (path to a directory)
        /// to another string which is easier to use.
        /// </summary>
        /// <param name="path">The path to a directory to convert</param>
        /// <returns>The converted string</returns>
        private string ConvertPath(string path)
        {
            if (string.IsNullOrEmpty(path))
                return path;

            string newPath = path.Trim();

            if (newPath.StartsWith(
                    string.Format(@"{0}\", ShellBrowser.MyComputerName),
                    false,
                    CultureInfo.InstalledUICulture) && newPath.Length > 12)
                newPath = newPath.Substring(path.IndexOf('\\') + 1);

            if (!newPath.EndsWith(@":\") && newPath.EndsWith(@"\"))
                newPath = newPath.Substring(0, newPath.Length - 1);

            if (newPath.EndsWith(@"\"))
                newPath = newPath.Substring(0, newPath.Length - 1);

            return newPath;
        }

        /// <summary>
        /// This method uses SHGetFileInfo to check whether a path to a directory exists.
        /// </summary>
        /// <param name="path">The path to check</param>
        /// <returns>true if it exists, false otherwise</returns>
        private bool PathExists(string path)
        {
            string realPath = ConvertPath(path);

            if (string.IsNullOrEmpty(realPath))
                return false;
            else if (string.Compare(path, "desktop", true) == 0)
                return true;

            string[] pathParts = realPath.Split('\\');

            for (int i = 0; i < pathParts.Length; i++)
            {
                bool found = false;
                if (ShellBrowser.DesktopItem.SubFolders.Contains(pathParts[i]))
                {
                    pathParts[i] = ShellItem.GetRealPath(
                        ShellBrowser.DesktopItem.SubFolders[pathParts[i]]);

                    found = true;
                }
                else
                {
                    ShellItem myComp =
                        ShellBrowser.DesktopItem.SubFolders[ShellBrowser.MyComputerName];

                    if (myComp.SubFolders.Contains(pathParts[i]))
                    {
                        pathParts[i] = ShellItem.GetRealPath(
                            myComp.SubFolders[pathParts[i]]);

                        found = true;
                    }
                }

                if (!found)
                    break;
            }

            realPath = string.Join("\\", pathParts);

            if (realPath.EndsWith(":"))
                realPath += "\\";

            ShellAPI.SHFILEINFO info = new ShellAPI.SHFILEINFO();
            IntPtr ptr = ShellAPI.SHGetFileInfo(realPath, 0, ref info, ShellAPI.cbFileInfo, ShellAPI.SHGFI.DISPLAYNAME);
            bool exists = (ptr != IntPtr.Zero);

            Marshal.FreeCoTaskMem(ptr);
            return exists;
        }

        /// <summary>
        /// Selects a path from a string, this can be a direct path, or something like 
        /// "My Documents/My Music". It will set the directory as the current directory.
        /// </summary>
        /// <param name="path">The path to select</param>
        /// <returns>The TreeNode of the directory which was selected, this will be null if the directory
        /// doesn't exist</returns>
        public TreeNode SelectPath(string path, bool expandNode)
        {
            if (string.IsNullOrEmpty(path))
                return null;

            if (PathExists(path))
            {
                string converted = ConvertPath(path);
                string[] pathParts = converted.Split('\\');

                TreeNode currentNode = null;

                #region Get Start Node
                // Change .Expand() to function which extends the node without expanding it

                if (string.Compare(pathParts[0], "desktop", true) == 0)
                    currentNode = desktopNode;
                else if (desktopNode.Nodes.ContainsKey(pathParts[0]))
                {
                    currentNode = desktopNode.Nodes[pathParts[0]];
                    ExtendTreeNode(currentNode, false);
                }
                else
                {
                    if (string.Compare(pathParts[0], myCompNode.Text, true) == 0)
                        currentNode = myCompNode;
                    else
                    {
                        if (pathParts[0][pathParts[0].Length - 1] == ':')
                            pathParts[0] += "\\";

                        foreach (TreeNode node in myCompNode.Nodes)
                        {
                            if (string.Compare(
                                    pathParts[0],
                                    ((ShellItem)node.Tag).Path, true) == 0)
                            {
                                currentNode = node;
                                ExtendTreeNode(currentNode, false);
                                break;
                            }
                        }
                    }
                }

                #endregion

                if (currentNode == null)
                {
                    folderView.EndUpdate();
                    return null;
                }

                #region Iterate

                for (int i = 1; i < pathParts.Length; i++)
                {
                    if (pathParts[i][pathParts[i].Length - 1] == ':')
                        pathParts[i] += "\\";

                    bool found = false;
                    foreach (TreeNode child in currentNode.Nodes)
                    {
                        if (string.Compare(pathParts[i], child.Text, true) == 0)
                        {
                            currentNode = child;
                            found = true;
                            break;
                        }
                    }

                    if (!found)
                    {
                        folderView.EndUpdate();
                        return null;
                    }

                    ExtendTreeNode(currentNode, false);
                }

                #endregion

                if (expandNode)
                    currentNode.Expand();

                folderView.SelectedNode = currentNode;

                return currentNode;
            }
            else
                return null;
        }

        /// <summary>
        /// Selects a path from a value from the SpecialFolders enumeration.
        /// </summary>
        /// <param name="specialFolder">The SpecialFolder to select</param>
        /// <returns>The TreeNode of the directory which was selected, this will be null if the directory
        /// doesn't exist</returns>
        public TreeNode SelectPath(SpecialFolders specialFolder, bool expandNode)
        {
            StringBuilder path = new StringBuilder(256);
            IntPtr pidl = IntPtr.Zero;

            if (specialFolder == SpecialFolders.Desktop)
                return SelectPath("Desktop", expandNode);
            else if (ShellAPI.SHGetFolderPath(
                    IntPtr.Zero, (ShellAPI.CSIDL)specialFolder,
                    IntPtr.Zero, ShellAPI.SHGFP.TYPE_CURRENT, path) == ShellAPI.S_OK)
            {
                path.Replace(ShellBrowser.MyDocumentsPath, ShellBrowser.MyDocumentsName);
                return SelectPath(path.ToString(), expandNode);
            }
            else
            {
                #region Get Pidl

                if (specialFolder == SpecialFolders.MyDocuments)
                {
                    uint pchEaten = 0;
                    ShellAPI.SFGAO pdwAttributes = 0;
                    ShellBrowser.DesktopItem.ShellFolder.ParseDisplayName(
                        IntPtr.Zero,
                        IntPtr.Zero,
                        "::{450d8fba-ad25-11d0-98a8-0800361b1103}",
                        ref pchEaten,
                        out pidl,
                        ref pdwAttributes);
                }
                else
                {
                    ShellAPI.SHGetSpecialFolderLocation(
                        IntPtr.Zero,
                        (ShellAPI.CSIDL)specialFolder,
                        out pidl);
                }

                #endregion

                #region Make Path

                if (pidl != IntPtr.Zero)
                {
                    IntPtr strr = Marshal.AllocCoTaskMem(ShellAPI.MAX_PATH * 2 + 4);
                    Marshal.WriteInt32(strr, 0, 0);
                    StringBuilder buf = new StringBuilder(ShellAPI.MAX_PATH);

                    if (ShellBrowser.DesktopItem.ShellFolder.GetDisplayNameOf(
                                    pidl,
                                    ShellAPI.SHGNO.FORADDRESSBAR | ShellAPI.SHGNO.FORPARSING,
                                    strr) == ShellAPI.S_OK)
                    {
                        ShellAPI.StrRetToBuf(strr, pidl, buf, ShellAPI.MAX_PATH);
                    }

                    Marshal.FreeCoTaskMem(pidl);
                    Marshal.FreeCoTaskMem(strr);

                    if (!string.IsNullOrEmpty(buf.ToString()))
                        return SelectPath(buf.ToString(), expandNode);
                    else
                        return null;
                }
                else
                    return null;

                #endregion
            }
        }

        /// <summary>
        /// Selects a path from an existing ShellItem, this ShellItem must be present in the browsers 
        /// ShellBrowser, otherwise it can't be selected.
        /// </summary>
        /// <param name="specialFolder">The ShellItem to select</param>
        /// <returns>The TreeNode of the directory which was selected, this will be null if the directory
        /// doesn't exist</returns>
        public TreeNode SelectPath(ShellItem item, bool expandNode)
        {
            if (item == null)
                return null;

            ShellItem[] path = ShellBrowser.GetPath(item);

            if (path != null)
            {
                TreeNode currentNode = desktopNode;
                for (int i = 1; i < path.Length; i++)
                {
                    ExtendTreeNode(currentNode, false);
                    foreach (TreeNode subNode in currentNode.Nodes)
                    {
                        if (path[i].Equals(subNode.Tag))
                        {
                            currentNode = subNode;
                            break;
                        }
                    }
                }

                if (expandNode)
                    currentNode.Expand();

                folderView.SelectedNode = currentNode;

                return currentNode;
            }
            else
                return null;
        }

        #endregion

        #region Utilities

        /// <summary>
        /// This method will fill a TreeNode with the folders from it's ShellItem
        /// </summary>
        /// <param name="node">The TreeNode to extend</param>
        private bool ExtendTreeNode(TreeNode node, bool overwrite, IntPtr handle)
        {
            if (overwrite || !IsExtended(node))
            {
                ShellItem nodeItem = (ShellItem)node.Tag;
                ShellBrowser.UpdateCondition.ContinueUpdate = false;

                if (nodeItem.Expand(false, true, handle))
                {
                    folderView.BeginUpdate();
                    node.Nodes.Clear();

                    TreeNode[] newNodesArray = new TreeNode[nodeItem.SubFolders.Count];
                    for (int i = 0; i < newNodesArray.Length; i++)
                    {
                        newNodesArray[i] = new TreeNode(
                            nodeItem.SubFolders[i].Text,
                            nodeItem.SubFolders[i].ImageIndex,
                            nodeItem.SubFolders[i].SelectedImageIndex);
                        newNodesArray[i].Tag = nodeItem.SubFolders[i];

                        if (nodeItem.SubFolders[i].HasSubfolder)
                            newNodesArray[i].Nodes.Add(string.Empty);

                        newNodesArray[i].Name = newNodesArray[i].Text;
                    }

                    node.Nodes.AddRange(newNodesArray);

                    folderView.EndUpdate();
                    return true;
                }
                else
                    return false;
            }
            else
                return true;
        }

        private bool ExtendTreeNode(TreeNode node, bool overwrite)
        {
            return ExtendTreeNode(node, overwrite, IntPtr.Zero);
        }

        private bool IsExtended(TreeNode node)
        {
            if (node.Nodes.Count == 1 && string.IsNullOrEmpty(node.Nodes[0].Text))
                return false;
            else
                return true;
        }

        #endregion

        #region Properties

        #region Non Browsable

        #region Public

        [Browsable(false)]
        public ShellItem SelectedItem
        {
            get { return selectedItem; }
            set
            {
                if (value != null)
                    SelectPath(value, false);
            }
        }

        [Browsable(false)]
        public TreeNode SelectedNode
        {
            get { return folderView.SelectedNode; }
            set 
            {
                if (value != null)
                    folderView.SelectedNode = value;
            }
        }

        #endregion

        #region Internal

        [Browsable(false)]
        internal BrowserTreeView FolderView { get { return folderView; } }

        [Browsable(false)]
        internal bool NewItemCreated
        {
            get { return newItemCreated; }
            set { newItemCreated = value; }
        }

        /// <summary>
        /// The Property of selectionChange. When this bool is true the current directory will change when
        /// a TreeNode is selected, otherwise no change is made to the current directory. This is used to
        /// allow dropping on a TreeNode without changing the current directory.
        /// </summary>
        [Browsable(false)]
        internal bool SelectionChange
        {
            get { return selectionChange; }
            set { selectionChange = value; }
        }

        #endregion

        #endregion

        #region Browsable

        [Category("Options"),
         Description("Sets the Initial Directory of the Tree"),
         DefaultValue(SpecialFolders.MyComputer),
         Browsable(true)]
        public SpecialFolders StartUpDirectory
        {
            get { return startupDir; }
            set 
            {
                if (startupDir != value)
                {
                    startupDir = value;
                }
            }
        }

        [Category("Options"),
         Description("Sets the Initial Directory of the Tree when StartUpDirectory is set to \"Other\""),
         DefaultValue(""),
         Browsable(true)]
        public string StartUpDirectoryOther
        {
            get { return otherStartupDir; }
            set 
            {
                if (otherStartupDir != value)
                {
                    otherStartupDir = value;
                }
            }
        }

        [Category("Options"),
         Description("Sets the ShellBrowser for the control, if null the control will create it's own."),
         DefaultValue(null),
         Browsable(true)]
        public ShellBrowser ShellBrowser
        {
            get { return shellBrowser; }
            set
            {
                if (!ShellDll.ShellBrowser.Equals(ShellBrowser, value))
                {
                    if (ShellBrowser != null)
                        ShellBrowser.Browsers.Remove(this);

                    if (handleCreated) FileBrowser_HandleDestroyed(this, new EventArgs());
                    shellBrowser = value;
                    if (handleCreated) FileBrowser_HandleCreated(this, new EventArgs());

                    if (!ShellBrowser.Browsers.Contains(this))
                        ShellBrowser.Browsers.Add(this);
                }
            }
        }

        #endregion

        #endregion

        #region Public

        public bool CreateNewFolder()
        {
            if (selectedItem.IsFileSystem)
            {
                IntPtr newMenuPtr;
                IContextMenu newMenu;

                if (ContextMenuHelper.GetNewContextMenu(selectedItem, out newMenuPtr, out newMenu))
                {
                    lock (ShellBrowser)
                    {
                        NewItemCreated = true;
                    }

                    ContextMenuHelper.InvokeCommand(newMenu, "NewFolder", ShellItem.GetRealPath(selectedItem), new Point(0, 0));

                    Marshal.ReleaseComObject(newMenu);
                    Marshal.Release(newMenuPtr);

                    return true;
                }
                else
                    return false;
            }
            else
                return false;
        }

        #endregion

        #region Drag/Drop

        /// <summary>
        /// When an item is being dragged from the browser this method will set the dragStartControl to the
        /// Control from which it is being dragged. And the drop wrappers will also be informed.
        /// </summary>
        void DragWrapper_DragStart(object sender, DragEnterEventArgs e)
        {
            dragStartControl = e.DragStartControl;

            tvDropWrapper.ParentDragItem = e.Parent;
        }

        void DragWrapper_DragEnd(object sender, EventArgs e)
        {
            tvDropWrapper.ParentDragItem = null;
        }

        /// <summary>
        /// When an item is being dropped on the browser while holding the right mouse button, the context
        /// menu should not be showed. This method will take care of that problem.
        /// </summary>
        void DropWrapper_Drop(object sender, DropEventArgs e)
        {
            if (Control.Equals(dragStartControl, e.DragStartControl) && (e.MouseButtons & ShellAPI.MK.RBUTTON) != 0)
            {
                if (Control.Equals(dragStartControl, folderView))
                    tvContextWrapper.SuspendContextMenu = true;
            }

            dragStartControl = null;
        }

        #endregion
    }

    #region Custom EventArgs

    public class ContextMenuMouseHoverEventArgs : EventArgs
    {
        private string info;

        public ContextMenuMouseHoverEventArgs(string info)
        {
            this.info = info;
        }

        // The help info of the contextmenu item
        public string ContextMenuItemInfo { get { return info; } }
    }

    public class SelectedFolderChangedEventArgs : EventArgs
    {        
        private TreeNode node;
        private ShellItem item;

        public SelectedFolderChangedEventArgs(TreeNode node)
        {
            this.node = node;
            item = (ShellItem)node.Tag;
        }

        // The TreeNode of the new current selected directory
        public TreeNode Node { get { return node; } }

        // The ShellItem of the new current selected directory
        public ShellItem Item { get { return item; } }

        // The full path to the new current selected directory
        public string Path { get { return ShellItem.GetRealPath(item); } }
    }

    #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.

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


Written By
Netherlands Netherlands
I'm a student in Amsterdam (The Netherlands). I study Artificial Intelligence at the University of Amsterdam and I'm very fond of programming.

I discovered .Net programming a few years ago and immediately liked the Visual Studio environment. Since then I experimented a lot with .Net.

Comments and Discussions