Introduction
This article deals with the creation of a control which allows the user to pick a directory. The control might be easily put into a DialogWindow.
Background
In order to understand the tutorial you need to have a basic knowledge of WPF (what's a UserControl, a DialogWindow, ecc.), C# (collections, IO and events) and Data Binding in WPF (dynamic list binding)
Using the code
The control is designed with a listbox in the middle, a textbox in the upper part of the control and, next to it, there is a button to go up the directory tree.
The code of the page is:
<UserControl x:Class="DirectoryPicker.DPick"
xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">http://schemas.microsoft.com/winfx/2006/xaml</a>"
xmlns:mc="<a href="http://schemas.openxmlformats.org/markup-compatibility/2006">http://schemas.openxmlformats.org/markup-compatibility/2006</a>"
xmlns:d="<a href="http://schemas.microsoft.com/expression/blend/2008">http://schemas.microsoft.com/expression/blend/2008</a>"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Loaded="UserControl_Loaded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="25" />
</Grid.ColumnDefinitions>
<TextBox x:Name="txtPath" Grid.Row="0" Margin="5,2" KeyUp="txtPath_KeyUp"/>
<Button Content="Up" Grid.Column="1" Margin="2" Click="Button_Click"/>
</Grid>
<ListBox x:Name="lstDirs" Margin="5" Grid.Row="1" MouseDoubleClick="lstDirs_MouseDoubleClick"/>
</Grid>
</UserControl>
and the code behind is:
public partial class DPick : UserControl
{
ObservableCollection<string> Dirs = new ObservableCollection<string>();
public string CPath
{
get { return (string)GetValue(CPathProperty); }
set { SetValue(CPathProperty, value); }
}
public static readonly DependencyProperty CPathProperty =
DependencyProperty.Register("CPath", typeof(string), typeof(DPick), new PropertyMetadata(""));
public DPick()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, EventArgs e)
{
this.lstDirs.ItemsSource = Dirs;
if (CPath == String.Empty || Directory.Exists(CPath) == false)
{
CPath = "";
foreach (string s in Directory.GetLogicalDrives())
{
Dirs.Add(s);
}
this.txtPath.Text = CPath;
}
else
{
PopulateList();
}
}
private void lstDirs_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
int SelIndex = this.lstDirs.SelectedIndex;
CPath = System.IO.Path.Combine(CPath, Dirs[SelIndex]);
PopulateList();
}
private void PopulateList()
{
try
{
Dirs.Clear();
foreach (var dir in Directory.GetDirectories(CPath))
{
Dirs.Add(GetDirName(dir));
}
this.txtPath.Text = CPath;
}
catch (Exception)
{
Dirs.Clear();
Dirs.Add("Access Denied");
}
}
private string GetDirName(string Path)
{
return Path.Substring(Path.LastIndexOf('\\') + 1);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
CPath = System.IO.Path.GetDirectoryName(CPath);
PopulateList();
}
private void txtPath_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
string old = CPath;
try
{
CPath = this.txtPath.Text;
PopulateList();
}
catch (Exception) { CPath = old; }
}
}
private void btnOK_Click(object sender, RoutedEventArgs e)
{
}
}
Comment
The first line defines an ObservableCollection<string> which define the content of the current directory. It is not a list because an ObservableCollection implements from INotifyPropertyChanged and it can notify whenever it is modified. Then we have defined a DependencyProperty which name's CPath which represents the current path of the control. You can set CPath to an initial directory, but it's also the directory the control is currently in (so the directory the user has selected till now).
Then the method UserControl_Loaded is called. If the CPath property has been set and the directory exists, it's used as the initial directory for the control, otherwise the list is populated with the list of LogicalDrives. I would like to focus the first line of the method
this.lstDirs.ItemsSource = Dirs;
this line indicates to the listbox that Dirs is its source of items. Thanks to the peculiar ability of the ObservableCollection class, the content of the Collection and the content of the ListBox will be always the same.
At this point, the method calls another method called PopulateList. This method cleares the ObservableCollection (and so the ListBox) and begins to add the content of the current directory (just directory names) to it as strings. If an error occurs, or the directory does not exists anymore (the user might have deleted it while the control has been already updated) or we haven't got the access to that directory, so we add to the list the string "Access Denied"
Then the event ListBox_MouseDoubleClick is defined in the code. This event occurs whenever the user double-clicks on the listbox or on one of its child. This happens because of the event bubbling. The event bubbling says that an event can travel up the control tree and reach the main container. I.E. if an item of the listbox is double-clicked, the listbox is double-clicked as well as the UserControl. In this event, we take the selected index and we add it to the CPath string with the help of the static class Path.Combine
In order to "go up", we can use the static class path with the method GetDirectoryName, which returns the path to the directory another element is in. In this case, this element is a directory, so we can use this code in order to go up. I.E. From C:\Dir1\Dir2\Dir3 Path.GetDirectoryName(string) will return C:\Dir1\Dir2
The last little thing: if we insert a path into the textbox and the users presses the Enter button, the control automatically navigates to the specified directory if it exists.
Points of Interest
You can write a DialogWindow with this control. Remember that's a UserControl. This window has to have a DependencyProperty called Path (or whatever you want) and this has to be passed to the CPath DependencyProperty of the control. When the user clicks Ok (so the window returns true) you can access the property Path into the Window and (if you 've written the right code in order to make your Path equal to the CPath of the control) use it for whatever you want!
History
1°Edition of the Article