简单的 WPF 资源管理器树






4.70/5 (101投票s)
2007年11月9日
6分钟阅读

499083

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
属性绑定到 TreeViewItem
的 Header
属性(Header
属性是保存渲染的 TreeView
控件上显示的文本的属性,因此它会保存诸如 c:\\
、Program Files
、Windows
等字符串)。
但这有什么用呢,这些 c:\\
、Program Files
、Windows
字符串值不是 Image Source
的 Uri
,对吧?它们甚至不接近,一个 Image Source
的 Uri
应该像 C:\Windows\Web\Azul.jpg 这样的东西,不是吗?
嗯,它们确实应该这样。但是 WPF 数据绑定还有最后一招,那就是值转换器。值转换器允许我们创建一个类,它将使用原始数据绑定值并返回一个不同的对象,该对象将用作最终绑定值。
这就是我用来使图像源指向正确位置的技巧。基本上,在上面显示的 Image Source
绑定中,我还指定了一个名为 HeaderToImageConverter
的转换器,我用它来检查实际的 TreeViewItem
的 Header
属性是否包含 \
字符。如果包含,我将该 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 参数是 TreeViewItem
的 Header
属性,我们在原始绑定中指定了它。我们不关心其他参数,但 Convert
和 ConvertBack
方法签名是由 IValueConverter
接口决定的,所以我们必须有正确的方法签名,尽管我们实际最终使用哪些参数。
无论如何,所以 value 参数 = TreeViewItem
的 Header
属性,这是我们现在唯一关心的。下一步是查看这个值(TreeViewItem
的 Header
)是否包含 \
字符,如果包含,则返回磁盘驱动器 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: 初版