65.9K
CodeProject 正在变化。 阅读更多。
Home

简单的 WPF 资源管理器树

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (101投票s)

2007年11月9日

6分钟阅读

viewsIcon

499083

downloadIcon

17884

简单的 WPF 资源管理器树

目录

引言

我仍在掌握 WPF,昨晚,作为我仍在撰写的一篇更长的文章的一部分,我想创建一个简单的(基本版本)资源管理器树,它显示驱动器和文件夹。我想在 TreeViewItem 是驱动器时显示驱动器图像,否则显示文件夹图像。听起来很容易吧。错了,事实证明这相当棘手,至少对我来说是这样。所以我认为,由于这篇使用了该技术的大文章仍在撰写中,我将把树视图实现拆分成一篇较小的文章(这篇)。我想根据某些条件为当前的 TreeViewItem 显示不同的图像可能是一个相当常见的需求。所以这就是本文的全部内容。

解决问题

成品看起来像这样

真的很简单,不是吗。

那么我是如何让 WPF TreeView 做到这一点的呢

第一步是让它显示正确的树,这实际上归结为以下两个方法。

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    foreach (string s in Directory.GetLogicalDrives())
    {
        TreeViewItem item = new TreeViewItem();
        item.Header = s;
        item.Tag = s;
        item.FontWeight = FontWeights.Normal;
        item.Items.Add(dummyNode);
        item.Expanded += new RoutedEventHandler(folder_Expanded);
        foldersItem.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 s in Directory.GetDirectories(item.Tag.ToString()))
            {
                TreeViewItem subitem = new TreeViewItem();
                subitem.Header = s.Substring(s.LastIndexOf("\\") + 1);
                subitem.Tag = s;
                subitem.FontWeight = FontWeights.Normal;
                subitem.Items.Add(dummyNode);
                subitem.Expanded += new RoutedEventHandler(folder_Expanded);
                item.Items.Add(subitem);
            }
        }
    catch (Exception) { }
    }
}

这些足以让我们获得 TreeView 的驱动器/文件夹层次结构。下一步,我想要为单个 TreeViewItem 显示图像。

默认情况下,WPF TreeView 控件不显示图像,例如下图显示了开箱即用的 WPF 控件的样子(请注意我使用的是 Vista,因此在 XP 上可能看起来略有不同)

这不是我想要的。所以我开始四处寻找 TreeViewItem 上是否有 Image 属性或类似的东西,你猜怎么着,没有。但当然 WPF 允许我们使用样式/模板来改变控件的外观和感觉。所以这是一个很好的起点,也许可以开发一个样式/模板。注意:我建议不要使用 Expression Blend 来完成此任务,因为一旦你决定使用 Expression Blend 开始编辑 WPF TreeView 控件,它会立即创建大约 200 行 XAML,而且这还没有进行任何更改。所以它可能会更多。不要误解我的意思。Expression Blend 很方便,但对于某些事情,比如样式/模板编辑,VS2005/VS2008 和手工编写代码才是正确的方法,你可以用更少的代码完成工作。

好了,牢骚发完了,所以我们需要为 WPF TreeView 控件创建某种样式,所以我沿着这条路走下去,最终得到了以下内容

<TreeView x:Name="foldersItem"
          SelectedItemChanged="foldersItem_SelectedItemChanged"
          Width="Auto" Background="#FFFFFFFF"
          BorderBrush="#FFFFFFFF"
          Foreground="#FFFFFFFF">
    <TreeView.Resources>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="HeaderTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Name="img"
                                   Width="20"
                                   Height="20"
                                   Stretch="Fill"
                                   Source="Images/diskdrive.png"/>
                            <TextBlock Text="{Binding}" Margin="5,0" />
                        </StackPanel>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </TreeView.Resources>
</TreeView>

这种样式最终会得到以下结果,我们现在在 TreeViewItem 旁边有了一些图像,这很酷。我们正在接近目标。但所有的图像都相同。但这是因为这种样式对所有 Image Source 属性都使用了固定的路径。所以它注定不会起作用。唉。也许可以在样式中做更多的事情。事实证明,这正是所做的。让我们看看。

我只包含与上面所示不同的样式部分。

<Image Name="img"  Width="20" Height="20" Stretch="Fill"
    Source="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
    AncestorType={x:Type TreeViewItem}},
    Path=Header,
    Converter={x:Static local:HeaderToImageConverter.Instance}}"
/>

现在我不得不说,这可能是我在 XAML 中编写过的最复杂的绑定代码。那么它做了什么呢?

基本上,它将 Image Source 属性绑定到 TreeViewItemHeader 属性(Header 属性是保存渲染的 TreeView 控件上显示的文本的属性,因此它会保存诸如 c:\\Program FilesWindows 等字符串)。

但这有什么用呢,这些 c:\\Program FilesWindows 字符串值不是 Image SourceUri,对吧?它们甚至不接近,一个 Image SourceUri 应该像 C:\Windows\Web\Azul.jpg 这样的东西,不是吗?

嗯,它们确实应该这样。但是 WPF 数据绑定还有最后一招,那就是值转换器。值转换器允许我们创建一个类,它将使用原始数据绑定值并返回一个不同的对象,该对象将用作最终绑定值。

这就是我用来使图像源指向正确位置的技巧。基本上,在上面显示的 Image Source 绑定中,我还指定了一个名为 HeaderToImageConverter 的转换器,我用它来检查实际的 TreeViewItemHeader 属性是否包含 \ 字符。如果包含,我将该 TreeViewItem 视为磁盘驱动器,因此我返回一个磁盘驱动器 Image Source Uri,否则我返回一个文件夹 Image Source Uri。一旦您看到实际的转换器,这可能会更清楚。

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media.Imaging;

namespace WPF_Explorer_Tree
{
    #region HeaderToImageConverter

    [ValueConversion(typeof(string), typeof(bool))]
    public class HeaderToImageConverter : IValueConverter
    {
        public static HeaderToImageConverter Instance =
            new HeaderToImageConverter();

        public object Convert(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            if ((value as string).Contains(@"\"))
            {
                Uri uri = new Uri
                ("pack://application:,,,/Images/diskdrive.png");
                BitmapImage source = new BitmapImage(uri);
                return source;
            }
            else
            {
                Uri uri = new Uri("pack://application:,,,/Images/folder.png");
                BitmapImage source = new BitmapImage(uri);
                return source;
            }
        }

        public object ConvertBack(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            throw new NotSupportedException("Cannot convert back");
        }
    }

    #endregion // HeaderToImageConverter

}

因此可以看出,HeaderToImageConverter 接受一个字符串并返回一个对象。基本上,传入的 value 参数是 TreeViewItemHeader 属性,我们在原始绑定中指定了它。我们不关心其他参数,但 ConvertConvertBack 方法签名是由 IValueConverter 接口决定的,所以我们必须有正确的方法签名,尽管我们实际最终使用哪些参数。

无论如何,所以 value 参数 = TreeViewItemHeader 属性,这是我们现在唯一关心的。下一步是查看这个值(TreeViewItemHeader)是否包含 \ 字符,如果包含,则返回磁盘驱动器 Image Source Uri,否则我返回一个文件夹 Image Source Uri。所以现在这很容易,我们只需做一个小的字符串测试,然后创建适当的 Uri。这里唯一棘手的部分是,因为图像在 Visual Studio 中的构建操作实际上设置为“Resource”,我们需要从应用程序中获取图像路径。

“那糟糕的三重逗号语法从何而来?

“pack://application:,,,/Images/diskdrive.png”

这到底是什么意思。

pack URI 格式是 XML Paper Specification (XPS) 的一部分,可在 http://www.microsoft.com/whdc/xps/default.mspx 找到

指定的格式为 pack://packageURI/partPath

packageURI 实际上是 URI 中的一个 URI,所以它通过将其正斜杠转换为逗号来编码。这个 packageURI 可以指向一个 XPS 文档,例如 file:///C:/Document.xps 编码为 file:,,,c:,Documenr.xps,或者,在 WPF 程序中,它可以是平台特别处理的两个 URI 之一

  • siteOfOrigin:///(编码为 siteOfOrigin:,,,)
  • application:///(编码为 application:,,,)

因此,三重逗号实际上是编码的正斜杠位,是原始参数的占位符。(请注意,这些也可以用两个斜杠/逗号而不是三个来指定)。

所有不使用 siteOfOrigin 的资源引用都隐式使用 application:/// 包。换句话说,XAML 中指定的以下 URI

logo.jpg

实际上只是以下内容的简写形式

pack://application:,,,/logo.jpg

以及这个 URI

MyDll;Component/logo.jpg

是以下内容的简写形式

pack://application:,,,/MyDll;Component/logo.png

你可以在 XAML 中使用这些更长、更明确的 URI,但没有充分的理由这样做。”

Windows Presentation Foundation Unleashed。Adam Nathan,Sams。2007

就这些

我希望这能帮助任何想要在 WPF 中创建更好、功能齐全的资源管理器树的人。这个满足了我的要求。

您怎么看?

我想请问,如果你喜欢这篇文章,请投票并留下评论,这能让我知道文章是否达到了合适的水平,以及是否包含了人们需要知道的内容。

历史

  • v1.0 07/11/09: 初版
© . All rights reserved.