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; }
}
}
}
}