TreeView.ItemTemplate HierarchicalDataTemplate 中的数据在切换 Selected TreeItem 时被删除

Data in TreeView.ItemTemplate HierarchicalDataTemplate gets erased when switching the Selected TreeItem

我有以下代码:

Template.XAML

<Style TargetType="{x:Type HeaderedContentControl}">
    <Setter Property="Header">
        <Setter.Value>
            <ContentControl  Foreground="Red" 
                       FontFamily="Segoe UI" 
                       Margin="0,0,0,20" 
                        Content="{Binding Tag, RelativeSource={RelativeSource AncestorType=HeaderedContentControl}}" 
                       HorizontalAlignment="Center" 
                       VerticalAlignment="Center" />
        </Setter.Value>
    </Setter>
</Style>

<DataTemplate DataType="{x:Type local:ViewModel}">
    <HeaderedContentControl  xmlns:sys="clr-namespace:System;assembly=mscorlib"
                   Tag="{Binding Header}"
                   Background="SteelBlue"
                   BorderBrush="DarkSlateBlue">
    </HeaderedContentControl>
</DataTemplate>

<DataTemplate DataType="{x:Type local:ViewModel2}">
    <HeaderedContentControl  xmlns:sys="clr-namespace:System;assembly=mscorlib"
                   Tag="{Binding Header}"
                   Background="SteelBlue"
                   BorderBrush="DarkSlateBlue">
    </HeaderedContentControl>
</DataTemplate>

Windows.XAML:

<Window.DataContext>
    <local:WindowsVM x:Name="viewModel"/>
</Window.DataContext>

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Templates.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>

<StackPanel Orientation="Horizontal">
    <TreeView SelectedItemChanged="TreeView_SelectedItemChanged" ItemsSource="{Binding AllContents}">
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate>
                <Label Content="{Binding Header}" />
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
    <StackPanel Orientation="Vertical">
        <ContentControl Content="{Binding SelectedItem}" />
    </StackPanel>
</StackPanel>

Windows.XAML.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        viewModel.SelectedItem = (ViewModel)e.NewValue;
    }
}

ViewModel.cs

public class WindowsVM : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public WindowsVM()
    {
        AllContents = new ObservableCollection<ViewModel>();
        AllContents.Add(new ViewModel("Item 1"));
        AllContents.Add(new ViewModel2("Item 2")); //using ViewModel("Item 2") will show the Header as it should
        SelectedItem = AllContents.First();
    }

    private ViewModel _selectedItem;
    public ViewModel SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
        }
    }

    private ObservableCollection<ViewModel> _allContents;
    public ObservableCollection<ViewModel> AllContents
    {
        get { return _allContents; }
        set
        {
            _allContents = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AllContents)));
        }
    }
}

public  class ViewModel:INotifyPropertyChanged
{
    private string _header;

    public event PropertyChangedEventHandler PropertyChanged;

    public string Header
    {
        get { return _header; }
        set
        {
            _header = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Header)));
        }
    }

    public ViewModel(string header)
    {
        Header = header;
    }
}


public class ViewModel2 : ViewModel
{
    public ViewModel2(string header) : base(header)
    {
    }
}

如果您 运行 应用程序,"Item 1" 将显示为所选项目的 header,这是应该的。

如果您单击第二个树节点,我希望 "Item 2" 显示为所选项目的 header,但事实并非如此。

有趣的部分来了,如果对于"Item 2",它是ViewModel类型而不是ViewModel2,那么"Item 2" header将被显示。为什么会这样?这是 WPF treeview 错误吗?

本着 MVVM 模型的精神,我也欢迎解决方法。

您应该将 HeaderedContentControlHeader 属性 绑定到您的 Header 来源 属性,然后在HeaderedContentControl 风格。

如果您像这样实现 Templates.xaml,您的示例将按预期运行:

<Style TargetType="{x:Type HeaderedContentControl}">
    <Setter Property="HeaderTemplate">
        <Setter.Value>
            <DataTemplate>
                <Label Foreground="Red" 
                       FontFamily="Segoe UI" 
                       Margin="0,0,0,20" 
                       Content="{Binding}" 
                       HorizontalAlignment="Center" 
                       VerticalAlignment="Center" />
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

<DataTemplate DataType="{x:Type local:ViewModel}">
    <HeaderedContentControl Header="{Binding Header}"
                   Background="SteelBlue"
                   BorderBrush="DarkSlateBlue">
    </HeaderedContentControl>
</DataTemplate>

<DataTemplate DataType="{x:Type local:ViewModel2}">
    <HeaderedContentControl Header="{Binding Header}"
                   Background="SteelBlue"
                   BorderBrush="DarkSlateBlue">
    </HeaderedContentControl>
</DataTemplate>