在WPF应用程序中,经常需要让用户选择文件或目录。本文将介绍如何创建一个简单的目录选择器控件,它可以被嵌入到对话框窗口中。为了理解本教程,需要具备基本的WPF知识(例如,了解UserControl是什么,DialogWindow是什么等),C#知识(例如,集合、IO和事件),以及WPF中的数据绑定技术。
该控件设计了一个位于中间的列表框,一个位于控件上部的文本框,以及一个按钮用于向上导航目录树。以下是页面的代码:
        <UserControl x:Class="DirectoryPicker.DPick"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            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>
    
以下是后台代码:
        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)
            {
            }
        }
    
在代码中,首先定义了一个ObservableCollection<string>,它定义了当前目录的内容。然后定义了一个名为CPath的依赖属性,它代表控件当前的路径。可以将CPath设置为一个初始目录,但它也是控件当前所在的目录(即用户到目前为止已选择的目录)。
接下来,UserControl_Loaded方法被调用。如果CPath属性已被设置并且目录存在,则将其用作控件的初始目录,否则列表将被填充为LogicalDrives的列表。
然后,调用PopulateList方法。这个方法清除ObservableCollection(以及ListBox),然后开始将当前目录的内容(仅目录名)作为字符串添加到其中。如果发生错误,或者目录不再存在(用户可能在控件已经更新后删除了它),或者没有访问该目录的权限,那么将字符串"Access Denied"添加到列表中。
ListBox_MouseDoubleClick事件在代码中定义。当用户双击列表框或其子项时,将触发此事件。这是因为事件冒泡。事件冒泡表示事件可以沿着控件树向上传播并到达主容器。例如,如果双击列表框中的一个项,列表框和UserControl都会被双击。在这种情况下,获取选定的索引,并使用静态类Path.Combine的帮助将其添加到CPath字符串中。
为了"向上导航",可以使用静态类Path,该类有一个方法GetDirectoryName,它返回另一个元素所在的目录的路径。在这种情况下,这个元素是一个目录,所以可以使用这段代码来向上导航。例如,从C:\Dir1\Dir2\Dir3,Path.GetDirectoryName(string)将返回C:\Dir1\Dir2。
最后一点:如果在文本框中输入一个路径,并且用户按下Enter键,控件将自动导航到指定的目录(如果它存在)。