TreeView数据绑定
源代码下载
2.0 下载页面
1.0 下载页面
注意:链接是微软SkyDrive页面,下载时请用浏览器直接下载,用某些下载工具可能无法下载
源代码环境:Microsoft Visual C# 2008 Express
程序ViewModel名称是FileSystemObjectViewModel,它把文件和文件夹统一视为一类,并用FileSystemObjectType枚举来区分类型。ViewModel的逻辑很简单,这里就不再多说了,下面主要介绍一些其他需要注意的地方。
建议下载源代码作参考。
在2.0后:
程序使用.NET 4.0中Directory类型所支持的EnumerateDirectories和EnumerateFiles方法。
即时数据加载
文件浏览器最基本的性能考虑就应该是数据的即时加载,也就是说展开的文件夹视图都是在用户进行TreeViewItem的展开操作之后才被加载的,当然文件夹视图收缩后已经加载的数据是不会被扔掉的,下次展开会直接打开已经加载的数据。
那么每当一个ViewModel被建立后,初始化函数会进行这样一种操作,如果它是文件夹的话,快速检查当前文件夹是否有子成员(子文件夹或子文件),如果有的话,把一个特殊的没有任何值的特殊ViewModel加在当前ViewModel的Children属性中。这样WPF数据绑定会在当前生成的TreeViewItem的Items加入这个由特殊ViewModel所生成的另一个TreeViewItem,当前TreeViewItem左侧会显示“加号”,代表着当前文件夹有子成员,而事实上,子成员的具体内容只有当该文件夹被用户展开后才会被加载。
MVVM中View中是没有事件代码的,只有XAML,因此我们首先要利用TreeViewItem的IsExpanded依赖属性,同时ViewModel中也有IsExpanded属性,用双向的数据绑定使这两个属性可以互相设置(在本例中其实只存在View设置ViewModel的情况)。
参考FileSystemObjectViewModel中的OnExpanded方法:
/// <summary>
/// 节点被展开后的操作
/// </summary>
protected virtual void OnExpanded()
{
//是否有特殊节点
if (HasSpecialChild)
{
//将要展开的节点拥有没有列举的子成员(第一次打开)
//我们需要移除特殊节点,并将子文件夹加入到Children中
RemoveSpecialChild();
foreach (var dir in GetSubDirs())
_Children.Add(new FileSystemObjectViewModel(dir, FileSystemObjectType.Folder));
foreach (var file in GetSubFiles())
_Children.Add(new FileSystemObjectViewModel(file, FileSystemObjectType.File));
}
}
打开TreeView的虚拟化
在.NET 3.5后,微软增加了VirtualizingStackPanel对TreeView的支持,虚拟化可以使TreeView得到更客观的性能,但是需要手动声明VirtualizingStackPanel的附加属性来使虚拟化起作用。
<TreeView VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"></TreeView>
VirtualizingStackPanel.VirtualizationMode默认是Standard:创建并丢弃项容器,应该尽可能的使用Recycling:重用项容器。
从文件或文件夹中提取图标
Windows Forms中有Icon.ExtractAssociatedIcon(MSDN:http://msdn.microsoft.com/en-us/library/system.drawing.icon.extractassociatedicon.aspx),但其缺点是无法从提取文件夹(包括磁盘根目录)的图标,并且在WPF程序中使用需要额外引用System.Drawing命名空间DLL。
因此使用SHGetFileInfo本地Windows API(MSDN:http://msdn.microsoft.com/en-us/library/bb762179.aspx),并将非托管图标资源转换成WPF的BitmapSource。
感谢互联网,就直接用这个链接所提供的代码来做SHGetFileInfo的执行:(http://stackoverflow.com/questions/6008600/get-program-icons),我把它包在一个叫IconExtractor的类中。
View
主View:FileSystemView使用ObjectDataProvider来调用ViewModel(FileSystemObjectViewModel)的静态方法来索取当前系统的所有磁盘根目录并将结果绑定在TreeView的DataContext上。资源中还包括一个自定义的数据转换器(IconConverter)用来将FileSystemObjectViewModel转换成相应的文件或文件夹图标,并通过Image显示在TreeView的HierarchicalDataTemplate中。
FileSystemView:
<UserControl.Resources>
<ObjectDataProvider x:Key="dataProvider"
ObjectType="loc:FileSystemObjectViewModel"
MethodName="GetSystemDrives"/>
<loc:IconConverter x:Key="iconConverter" />
</UserControl.Resources>
<TreeView VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
DataContext="{Binding Source={StaticResource dataProvider}}"
ItemsSource="{Binding Children}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="loc:FileSystemObjectViewModel"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Converter={StaticResource iconConverter}}" />
<TextBlock Text="{Binding DisplayName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>