static void

DirListView

Another user control, this time wrapping a ListView to show files in a specified directory. Allows navigation by clicking folders. Uses BackgroundWorker and a timer for large directories. Uses FileListViewSorter - an IComparer for FileSystemInfos in ListViewItems, and FileSizeFormatProvider - An ICustomFormatter for file sizes. The DirectoryChanged event uses DirectoryClickedEventArgs.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
 
namespace Library.Forms
{
    /// <summary>
    /// Encapsulates a ListView for showing a directory.
    /// </summary>
    /// <remarks>
    /// Large directories are loaded with a BackgroundWorker and (Forms.)Timer.
    /// If the timer fires, the directory is not fully loaded.
    /// </remarks>
    [ToolboxBitmap(typeof(ListView))]
    [DefaultEvent("DirectoryChanged"), Description("A listview for directories")]
    public class DirListView : UserControl
    {
        public event EventHandler<DirectoryClickedEventArgs> DirectoryChanged;
        public event EventHandler<FileSelectedEventArgs> Selected;
 
        private ListView listView1;
        private DirectoryInfo _currentDirectory;
        private const string directoryType = "File Folder";
        private FileListViewSorter _columnSorter = new FileListViewSorter();
        private FileSizeFormatProvider _sizeFormatter = new FileSizeFormatProvider();
        private BackgroundWorker bw = new BackgroundWorker();
        private Timer timer = new Timer();
 
        public DirListView()
        {
            InitializeComponent();
            listView1.SelectedIndexChanged += listView_SelectedIndexChanged;
            listView1.MouseDoubleClick += listView_MouseDoubleClick;
            listView1.KeyUp += listView_KeyUp;
            InitSorting();
            InitBackgroundWorker();
        }
 
        private void InitBackgroundWorker()
        {
            //loading large directories is slow, so we use a timer and background worker
            bw.DoWork += bw_DoWork;
            bw.WorkerSupportsCancellation = true;
            bw.RunWorkerCompleted += bw_RunWorkerCompleted;
            timer.Interval = 1000; //1 second for any operation
            timer.Tick += timer_Tick;
        }
 
        private void InitSorting()
        {
            listView1.ColumnClick += listView_ColumnClick;
            listView1.Sorting = SortOrder.None;
            listView1.ListViewItemSorter = _columnSorter;
        }
 
        /// <summary>
        /// Define columns and internal event.
        /// Do this in InitializeComponent for designer support.
        /// </summary>
        private void Init()
        {
            listView1.Columns.Clear();
            listView1.Columns.Add("Name", -1);
            listView1.Columns.Add("Type", -1);
            listView1.Columns.Add("Last Modified", -1);
            listView1.Columns.Add("Size", -1);
            listView1.View = View.Details;
 
            if (_smallImageList != null)
                listView1.SmallImageList = _smallImageList;
        }
 
        private ImageList _smallImageList = null;
 
        [Browsable(true), Description("The imagelist (key 0 = folder image, key 4= file image)"), Category("Behavior")]
        public ImageList SmallImageList
        {
            get { return _smallImageList; }
            set
            {
                _smallImageList = value;
                listView1.SmallImageList = _smallImageList;
            }
        }
        [Browsable(true), Description("The current directory"), Category("Behavior")]
        public string CurrentDirectory
        {
            get
            {
                if (_currentDirectory == null)
                    return string.Empty;
                else
                    return _currentDirectory.FullName;
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                    value = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                DirectoryInfo dir = new DirectoryInfo(value);
                if (!dir.Exists) throw new ArgumentException("Directory does not exist");
                SetDirectory(dir);
            }
        }
 
        [Browsable(false), Description("The selected files and directories"), Category("Behavior"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public List<FileSystemInfo> SelectedFiles
        {
            get
            {
                List<FileSystemInfo> list = new List<FileSystemInfo>();
                foreach (ListViewItem item in listView1.SelectedItems)
                {
                    FileSystemInfo fsi = item.Tag as FileSystemInfo;
                    if (fsi != null) list.Add(fsi);
                }
                return list;
            }
        }
 
        /// <summary>
        /// Sets the current directory. You can set with a string in <see cref="CurrentDirectory"/>
        /// </summary>
        public void SetDirectory(DirectoryInfo directory)
        {
            _currentDirectory = directory;
 
            listView1.BeginUpdate();
            listView1.Items.Clear();
 
            if (directory == null)
            {
                listView1.EndUpdate();
                return;
            }
 
            if (directory.GetFileSystemInfos().Length > 100)
            {
 
                if (bw.IsBusy)
                {
                    timer.Stop();
                    bw.CancelAsync();
                    while (bw.IsBusy)//spin until stopped
                    {
                        Application.DoEvents(); //important: keep UI messages pumping
                    }
                }
                bw.RunWorkerAsync(directory);
                timer.Start();
            }
            else
            {
                listView1.Items.AddRange(BuildFileList(directory, null).ToArray());
            }
            listView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
            listView1.EndUpdate();
        }
 
        #region BuildFileList (& BackgroundWorker events)
        private List<ListViewItem> BuildFileList(DirectoryInfo directory, BackgroundWorker bgw)
        {
            List<ListViewItem> list = new List<ListViewItem>();
            ListViewItem item;
            foreach (var dir in directory.GetDirectories())
            {
                if ((dir.Attributes & FileAttributes.Hidden) != 0) continue;
                item = new ListViewItem(dir.Name, 0);
                item.ForeColor = Color.DimGray;
                item.Tag = dir;
                item.SubItems.Add(
                    new ListViewItem.ListViewSubItem(item, directoryType));
                item.SubItems.Add(
                    new ListViewItem.ListViewSubItem(item, dir.LastWriteTime.ToShortDateString()));
                list.Add(item);
                if (bgw != null && bgw.CancellationPending) return list;
            }
            foreach (FileInfo file in directory.GetFiles())
            {
                if ((file.Attributes & FileAttributes.Hidden) != 0) continue;
                if ((file.Attributes & FileAttributes.System) != 0) continue;
                item = new ListViewItem(file.Name, 4);
                item.Tag = file;
                item.SubItems.Add(
                    new ListViewItem.ListViewSubItem(item, file.Extension + " File"));
                item.SubItems.Add(
                    new ListViewItem.ListViewSubItem(item, file.LastWriteTime.ToShortDateString()));
                item.SubItems.Add(
                    new ListViewItem.ListViewSubItem(item,
                        string.Format(_sizeFormatter, "{0:FS1}", file.Length)));
                list.Add(item);
                if (bgw != null && bgw.CancellationPending) return list;
            }
            return list;
        }
 
        void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            listView1.BeginUpdate();
            listView1.Items.Clear();
            List<ListViewItem> list = new List<ListViewItem>();
            //should check e.Cancelled and e.Error but we'll take what we have
            list = e.Result as List<ListViewItem>;
            listView1.Items.AddRange(list.ToArray());
 
            listView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
            listView1.EndUpdate();
        }
 
        void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bgw = sender as BackgroundWorker;
            DirectoryInfo directory = e.Argument as DirectoryInfo;
            e.Result = BuildFileList(directory, bgw);
            //if (bgw.CancellationPending) e.Cancel = true; //no exception thanks
        }
 
        void timer_Tick(object sender, EventArgs e)
        {
            if (bw.IsBusy)
                bw.CancelAsync();
            timer.Stop(); //only tick once
        }
        #endregion
 
        #region Internal events
        private void listView_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            if (e.Column == _columnSorter.SortColumn)
            {
                if (_columnSorter.Order == SortOrder.Ascending)
                    _columnSorter.Order = SortOrder.Descending;
                else
                    _columnSorter.Order = SortOrder.Ascending;
            }
            else
            {
                _columnSorter.SortColumn = e.Column;
                _columnSorter.Order = SortOrder.Ascending;
            }
            listView1.Sort();
        }
 
        private void listView_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Control && e.KeyCode == Keys.A) //control A= select All
            {
                foreach (ListViewItem item in listView1.Items)
                {
                    item.Selected = true;
                }
            }
        }
 
        private void listView_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            ListView lv = sender as ListView;
            ListViewItem item = lv.GetItemAt(e.X, e.Y);
            if (!(item.Tag is DirectoryInfo)) return;
            string newpath = Path.Combine(_currentDirectory.FullName, item.Text);
            DirectoryInfo dir = new DirectoryInfo(newpath);
            while (!dir.Exists) //could have been deleted, so we move up
            {
                if (dir == dir.Parent) break; //disc root
                dir = dir.Parent;
            }
            SetDirectory(dir);
            //raise event
            EventHandler<DirectoryClickedEventArgs> handler = DirectoryChanged;
            if (handler != null)
                handler(this, new DirectoryClickedEventArgs(dir));
        }
 
        private void listView_SelectedIndexChanged(object sender, EventArgs e)
        {
            //raise event
            EventHandler<FileSelectedEventArgs> handler = Selected;
            if (handler != null)
                handler(this, new FileSelectedEventArgs(SelectedFiles));
        }
        #endregion
 
        #region Designer Support
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
 
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
 
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.listView1 = new System.Windows.Forms.ListView();
            this.SuspendLayout();
            //
            // listView1
            //
            this.listView1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.listView1.Location = new System.Drawing.Point(0, 0);
            this.listView1.Name = "listView1";
            this.listView1.Size = new System.Drawing.Size(120, 120);
            this.listView1.TabIndex = 0;
            this.listView1.UseCompatibleStateImageBehavior = false;
            listView1.Columns.Add("Name", -1);
            listView1.Columns.Add("Type", -1);
            listView1.Columns.Add("Last Modified", -1);
            listView1.Columns.Add("Size", -1);
            listView1.View = View.Details;
            //
            // DirListView
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Controls.Add(this.listView1);
            this.Name = "DirListView";
            this.ResumeLayout(false);
        }
        #endregion
 
        public class FileSelectedEventArgs : EventArgs
        {
            private List<FileSystemInfo> _data;
            public FileSelectedEventArgs(List<FileSystemInfo> data)
            {
                _data = data;
            }
 
            public List<FileSystemInfo> SelectedFiles
            {
                get { return _data; }
                set { _data = value; }
            }
        }
    }
}