如何为 TreeView 项目重复使用 XAML 模板?

How can I reuse XAML templates for TreeView items?

在下面的示例代码中,我有一个显示两种类型的 WPF 树视图。它很好用。然而,为每个类型定义模板的 XAML 除了使用的图像文件之外非常相似。我怎样才能摆脱重复?我假设有一种方法可以单独定义模板并使用某种触发器或其他东西来驱动要显示的图像。

<Window x:Class="Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Test"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TreeView Name="treeView" HorizontalAlignment="Left" Height="400" Margin="10,10,0,0" VerticalAlignment="Top" 
                  Width="233">
            <TreeView.Resources>
                <HierarchicalDataTemplate DataType="{x:Type local:TreeFolder}" ItemsSource="{Binding Nodes}">
                    <StackPanel Orientation="Horizontal" Margin="1,2,2,2">
                        <Image Source="images/folder.png" Width="13" Height="13" Margin="0,0,4,0"/>
                        <TextBlock Name="nameTextBlock" Text="{Binding Name}" />
                    </StackPanel>
                </HierarchicalDataTemplate>
                <DataTemplate DataType="{x:Type local:TreeNode}">
                    <StackPanel Orientation="Horizontal" Margin="1,2,2,2">
                        <Image Source="images/subroutine.png" Width="13" Height="13" Margin="0,0,4,0"/>
                        <TextBlock Name="nameTextBlock" Text="{Binding Name}" />
                    </StackPanel>
                </DataTemplate>
            </TreeView.Resources>
        </TreeView>
    </Grid>
</Window>
using System.Collections.ObjectModel;
using System.Windows;

namespace Test
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly ObservableCollection<TreeNode> treeList  = new ObservableCollection<TreeNode>();

        public MainWindow()
        {
            InitializeComponent();

            // set up test data
            treeList.Add(new TreeNode { Name = "Node 1" });
            treeList.Add(new TreeNode { Name = "Node 2" });
            var folder = new TreeFolder { Name = "Folder Node 1" };
            folder.Nodes.Add(new TreeNode { Name = "Node 3" });
            folder.Nodes.Add(new TreeNode { Name = "Node 4" });
            treeList.Add(folder);
            folder = new TreeFolder { Name = "Folder Node 2" };
            folder.Nodes.Add(new TreeNode { Name = "Node 3" });
            treeList.Add(folder);

            treeView.ItemsSource = treeList;
        }
    }
}
namespace Test
{
    public class TreeNode
    {
        public string Name { get; set; }
    }
}
using System.Collections.ObjectModel;

namespace Test
{
    class TreeFolder : TreeNode
    {
        public ObservableCollection<TreeNode> Nodes { get; } = new ObservableCollection<TreeNode>();
    }
}

如果为 Image.Source 创建一个转换器,您可以为 TreeNode 和 TreeFolder 重用模板:

public class NodeIconConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is TreeFolder)
        {
            return new BitmapImage(new Uri("/images/folder.png", UriKind.Relative));
        }

        if (value is TreeNode)
        {
            return new BitmapImage(new Uri("/images/subroutine.png", UriKind.Relative));
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

在视图资源中添加转换器:

<Window.Resources>
    <local:NodeIconConverter x:Key="IconConverter"/>
</Window.Resources>

和 mpdify 模板:

<TreeView.ItemTemplate>
    <HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
        <StackPanel Orientation="Horizontal" Margin="1,2,2,2">
            <Image Source="{Binding Converter={StaticResource IconConverter}}" 
                   Width="13" Height="13" Margin="0,0,4,0"/>
            <TextBlock Name="nameTextBlock" Text="{Binding Name}" />
        </StackPanel>
    </HierarchicalDataTemplate>
</TreeView.ItemTemplate>

虽然可以使用转换器解决方案,但我宁愿更改数据结构。我认为 TreeFolder 类型不是必需的

public enum NodeTypes
{
    Subroutine,
    Folder
}

public class TreeNode
{
    public string Name { get; set; }

    public NodeTypes NodeType { get; set; }

    public ObservableCollection<TreeNode> Nodes { get; } = new ObservableCollection<TreeNode>();
}

然后模板可以更改为:

<HierarchicalDataTemplate DataType="{x:Type local:TreeNode}" ItemsSource="{Binding Nodes}">
    <StackPanel Orientation="Horizontal" Margin="1,2,2,2">

        <Image Width="13" Height="13" Margin="0,0,4,0">
            <Image.Style>
                <Style TargetType="Image">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Path=NodeType}" Value="Folder">
                            <Setter Property="Source" Value="images/folder.png"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding Path=NodeType}" Value="Subroutine">
                            <Setter Property="Source" Value="images/subroutine.png"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Image.Style>
        </Image>

        <TextBlock Name="nameTextBlock" Text="{Binding Name}" />
    </StackPanel>
</HierarchicalDataTemplate>