改进: 简单的WPF目录树

翻译 专栏收录该内容
5 篇文章 0 订阅

原文地址: Reaction to: A Simple WPF Explorer Tree

Reaction to: A Simple WPF Explorer Tree

Friday, November 9th, 2007 at 9:34 am

By Josh Smithon

Sacha Barber最近在CodeProject发表了一篇文章,标题为"A Simple WPF Explorer Tree"。这篇文章展示了怎么延迟加载目录结构的TreeView控件到电脑上。这是一篇非常好的WPF TreeView延迟加载的介绍文章,我强烈推荐阅读原文。

在读完Sacha的这篇好文章后,我总感觉有点不对劲。在他的Demo中,树的根节点有个驱动器的图标,所有的其他节点有文件夹的图标。他对于使用哪种图标的实现逻辑是通过一个值转换器。一个TreeViewItem用来显示图标的图形元素的Source属性绑定到了TreeViewItem的Header上。这个绑定有一个转换器,转换器检查Header的文本里面是否有一个反斜杠(\),如果找到的话,那说明这是一个根节点 (比如它表示一个驱动器,而不是一个目录)。例如值转换器接收到的Header文本是"C:\",它就返回驱动器图标。

值转换器包含了图标资源的名字,它通过那些硬编码的资源标识符去加载一个BitmapSource。这就是我觉得有问题的地方。我认为应该避免在值转换器和模板选择器中使用硬编码的资源标识符,因为这严重限制了这些累的重用性。

至少有两种其他的方法可以用来实现图标选择功能而不需要在值选择器里面固定死资源的键值。第一种是允许你通过值选择器的属性来设置图标文件名 (资源标识符)。这样你就能指定XAML使用哪个图标,而值转换器只需要知道怎么解析它接收到的数据。

我并不是很喜欢那个方法。事实上我想在这种情况使用值转换器并不是最好的方法。转换器的逻辑依赖于Header文本的某个字符虽然可以,但是我感觉和生硬。我认为TreeView结构和它的元素应该提供我们需要的所有信息,而不是元素数据里面的某个内容。

我的解决方案为根节点提供一个属性,然后使用DataTriggers绑定这个属性并决定使用哪个图标。下面是代码。首先我们要有一个包含绑定属性的类,注意默认值是false:

using System.Windows;

namespace WpfExplorerTreeNoConverter
{

	public static class TreeViewItemProps
	{
		public static bool GetIsRootLevel(DependencyObject obj)
		{
			return (bool)obj.GetValue(IsRootLevelProperty);
		}

		public static void SetIsRootLevel(
			DependencyObject obj, bool value)
		{
			obj.SetValue(IsRootLevelProperty, value);
		}
		
		public static readonly DependencyProperty IsRootLevelProperty =
			DependencyProperty.RegisterAttached(
			"IsRootLevel", 
			typeof(bool), 
			typeof(TreeViewItemProps), 
			new UIPropertyMetadata(false));
	}

}

当我们初始化TreeViewItems的时候,它们代表电脑上的驱动器,我们把每个元素绑定的IsRootLevel属性设置为true:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.IO;

// NOTE: The original concept of this demo was created by Sacha Barber, in
// this article: http://www.codeproject.com/useritems/WPF_Explorer_Tree.asp

namespace WpfExplorerTreeNoConverter
{
	/// <summary>
	/// Interaction logic for Window1.xaml
	/// </summary>

	public partial class Window1 : System.Windows.Window
	{
		private readonly object _dummyNode = null;

		public Window1()
		{
			InitializeComponent();
			this.Loaded += new RoutedEventHandler(Window1_Loaded);
		}

		void Window1_Loaded(object sender, RoutedEventArgs e)
		{
			foreach (string drive in Directory.GetLogicalDrives())
			{
				TreeViewItem item = new TreeViewItem();
				item.Header = drive;
				item.Tag = drive;
				item.Items.Add(_dummyNode);
				item.Expanded += folder_Expanded;

				// Apply the attached property so that 
				// the triggers know that this is root item.
				TreeViewItemProps.SetIsRootLevel(item, true);

				foldersTree.Items.Add(item);
			}
		}

		void folder_Expanded(object sender, RoutedEventArgs e)
		{
			TreeViewItem item = (TreeViewItem)sender;
			if (item.Items.Count == 1 && item.Items[0] == _dummyNode)
			{
				item.Items.Clear();
				try
				{					
					foreach (string dir in Directory.GetDirectories(item.Tag as string))
					{
						TreeViewItem subitem = new TreeViewItem();
						subitem.Header = new DirectoryInfo(dir).Name;
						subitem.Tag = dir;
						subitem.Items.Add(_dummyNode);
						subitem.Expanded += folder_Expanded;
						item.Items.Add(subitem);
					}
				}
				catch (Exception) { }
			}
		}
	}
}

最后,我们的模板指明每一个TreeViewItem的Header如何呈现。注意由于Header是通过ContentPresenter来展现的,我们需要绑定DataTriggers到一个可以找到相应的TreeViewItem相对资源:

<Window x:Class="WpfExplorerTreeNoConverter.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfExplorerTreeNoConverter" 
    Title="WpfExplorerTreeNoConverter" Height="300" Width="300"
    >
  <Grid>    
    <TreeView x:Name="foldersTree">
      <TreeView.Resources>        
        <Style TargetType="{x:Type TreeViewItem}">
          <Setter Property="HeaderTemplate">
            <Setter.Value>              
              <DataTemplate DataType="ContentPresenter">
                <StackPanel Orientation="Horizontal">
                  <Image 
                    Name="img" 
                    Width="20" Height="20" 
                    Stretch="Fill" 
                    />
                  <TextBlock Text="{Binding}" Margin="5,0" />
                </StackPanel>

                <DataTemplate.Triggers>
                  <DataTrigger Binding="{Binding 
                    RelativeSource={RelativeSource 
                      Mode=FindAncestor, 
                      AncestorType={x:Type TreeViewItem}}, 
                    Path=(local:TreeViewItemProps.IsRootLevel)}" 
                    Value="True"
                    >
                    <Setter 
                      TargetName="img" 
                      Property="Source" 
                      Value="Images/diskdrive.png" 
                      />
                  </DataTrigger>

                  <DataTrigger Binding="{Binding 
                    RelativeSource={RelativeSource 
                      Mode=FindAncestor, 
                      AncestorType={x:Type TreeViewItem}}, 
                    Path=(local:TreeViewItemProps.IsRootLevel)}" 
                    Value="False"
                    >
                    <Setter 
                      TargetName="img" 
                      Property="Source" 
                      Value="Images/folder.png" 
                      />
                  </DataTrigger>
                </DataTemplate.Triggers>
              </DataTemplate>              
            </Setter.Value>
          </Setter>
        </Style>        
      </TreeView.Resources>
    </TreeView>  
  </Grid>
</Window>

在这里下载演示项目: ExplorerTree (demo project) 请把文件后缀从.DOC改成.ZIP然后解压。

译者按: 因为WordPress被屏蔽,所以把资源上传到了CSDN,可以在这里下载演示代码 (0资源分)

  • 1
    点赞
  • 1
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值