学习WPF: 创建数据绑定目录树
如果使用了WPF而不使用数据绑定(手工在界面和数据间进行同步),总会感觉不值.但是大部分讨论WPF数据绑定的文章,主题大多集中在ListBox这样平坦的数据集合上,讲如何绑定层次结构数据的比较少,这里我就通过一个简单的显示磁盘目录树的例子来展示如何完成这样的任务.
第一步,当然是定义要绑定的数据类型了.
在目录树这个例子中,每个TreeViewItem要显示的数据可以用System.IO.DirectoryInfo来表示,但是这样做有一个麻烦:DirectoryInfo只能通过GetDirectories()方法来获取子目录,但是WPF里的数据绑定则更倾向于使用属性在数据间导航,所以为了更方便地使用数据绑定,我们最好还是自定义一个类来完成这样的工作:
这个类所作的工作很简单,就是正规化目录路径,获取目录名称,以及延迟加载子目录(以提升性能)的列表,我们的界面也只要求它具有这些功能就行了.
第二步,创建一个数据提供类(DataProvider)
我们可以在Window的代码里设置界面的DataContext,ItemsSource等属性来让界面显示指定的数据,也可以构造一个专门提供数据的类,完全在界面(XAML)里指定,这里使用的是第二种方法:
这个类就更简单了,仅仅是在创建的时候加载所有的磁盘的根目录.
第三步,设计用户界面
只需要在Window中添加一个TreeView,然后修改几行代码,就能轻松地显示我们的数据了:
我们还定义了一个针对BindDirectory类型的层次型数据模板itemsTemplate,指定了要获取此类型的数据的子数据需要通过Directories属性,并且告诉WPF用一个TextBlock来显示它的名称.
最后,我们设置一下TreeView的ItemsSource和ItemTemplate就完成工作了.
运行截图:
第一步,当然是定义要绑定的数据类型了.
在目录树这个例子中,每个TreeViewItem要显示的数据可以用System.IO.DirectoryInfo来表示,但是这样做有一个麻烦:DirectoryInfo只能通过GetDirectories()方法来获取子目录,但是WPF里的数据绑定则更倾向于使用属性在数据间导航,所以为了更方便地使用数据绑定,我们最好还是自定义一个类来完成这样的工作:
using System.Collections.Generic;
using System.IO;
namespace WpfApplication1
{
class BindDirectory
{
public BindDirectory(string directoryPath)
{
//正规化目录路径,确保Path以'\\'结尾
directoryPath = directoryPath.TrimEnd('\\');
Path = directoryPath + '\\';
//计算出目录名称(不包含路径)
int indexLastSlash = directoryPath.LastIndexOf('\\');
if (indexLastSlash >= 0)
{
Name = directoryPath.Substring(indexLastSlash + 1);
}
else
{
Name = directoryPath;
}
}
public string Name
{
get;
private set;
}
public string Path
{
get;
private set;
}
public IEnumerable<BindDirectory> Directories
{
get
{
//延迟加载
if (directories == null)
{
directories = new List<BindDirectory>();
foreach (string d in Directory.GetDirectories(Path))
{
directories.Add(new BindDirectory(d));
}
}
return directories;
}
}
List<BindDirectory> directories;
}
}
using System.IO;
namespace WpfApplication1
{
class BindDirectory
{
public BindDirectory(string directoryPath)
{
//正规化目录路径,确保Path以'\\'结尾
directoryPath = directoryPath.TrimEnd('\\');
Path = directoryPath + '\\';
//计算出目录名称(不包含路径)
int indexLastSlash = directoryPath.LastIndexOf('\\');
if (indexLastSlash >= 0)
{
Name = directoryPath.Substring(indexLastSlash + 1);
}
else
{
Name = directoryPath;
}
}
public string Name
{
get;
private set;
}
public string Path
{
get;
private set;
}
public IEnumerable<BindDirectory> Directories
{
get
{
//延迟加载
if (directories == null)
{
directories = new List<BindDirectory>();
foreach (string d in Directory.GetDirectories(Path))
{
directories.Add(new BindDirectory(d));
}
}
return directories;
}
}
List<BindDirectory> directories;
}
}
这个类所作的工作很简单,就是正规化目录路径,获取目录名称,以及延迟加载子目录(以提升性能)的列表,我们的界面也只要求它具有这些功能就行了.
第二步,创建一个数据提供类(DataProvider)
我们可以在Window的代码里设置界面的DataContext,ItemsSource等属性来让界面显示指定的数据,也可以构造一个专门提供数据的类,完全在界面(XAML)里指定,这里使用的是第二种方法:
using System.Collections.Generic;
using System.IO;
namespace WpfApplication1
{
class BindDirectoryList : List<BindDirectory>
{
public BindDirectoryList()
{
foreach (var drive in DriveInfo.GetDrives())
{
Add(new BindDirectory(drive.RootDirectory.FullName));
}
}
}
}
using System.IO;
namespace WpfApplication1
{
class BindDirectoryList : List<BindDirectory>
{
public BindDirectoryList()
{
foreach (var drive in DriveInfo.GetDrives())
{
Add(new BindDirectory(drive.RootDirectory.FullName));
}
}
}
}
这个类就更简单了,仅仅是在创建的时候加载所有的磁盘的根目录.
第三步,设计用户界面
只需要在Window中添加一个TreeView,然后修改几行代码,就能轻松地显示我们的数据了:
<!--xml:sample这一行用来引入我们自己代码的命名空间-->
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sample="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<!--引入我们自己的数据提供对象-->
<ObjectDataProvider x:Key="drives" ObjectType="{x:Type sample:BindDirectoryList}" />
<!--设置如何显示数据,以及如何获取下一级数据的列表-->
<HierarchicalDataTemplate x:Key="itemTemplate" DataType="{x:Type sample:BindDirectory}" ItemsSource="{Binding Directories}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView ItemsSource="{Binding Source={StaticResource drives}}"
ItemTemplate="{StaticResource itemTemplate}" >
</TreeView>
</Window>
这里我们在XAML里定义了一个drives对象,它的类型为BindDirectoryList,创建时会自动加载磁盘的根目录;<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sample="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<!--引入我们自己的数据提供对象-->
<ObjectDataProvider x:Key="drives" ObjectType="{x:Type sample:BindDirectoryList}" />
<!--设置如何显示数据,以及如何获取下一级数据的列表-->
<HierarchicalDataTemplate x:Key="itemTemplate" DataType="{x:Type sample:BindDirectory}" ItemsSource="{Binding Directories}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView ItemsSource="{Binding Source={StaticResource drives}}"
ItemTemplate="{StaticResource itemTemplate}" >
</TreeView>
</Window>
我们还定义了一个针对BindDirectory类型的层次型数据模板itemsTemplate,指定了要获取此类型的数据的子数据需要通过Directories属性,并且告诉WPF用一个TextBlock来显示它的名称.
最后,我们设置一下TreeView的ItemsSource和ItemTemplate就完成工作了.
运行截图: