自定义TreeViewItem设置DefaultStyleKeyProperty后无法在TreeView显示求助
问题场景
我创建了带自定义图片属性的TreeViewTestItem,代码如下:
class TreeViewTestItem : TreeViewItem, INotifyPropertyChanged { public ImageSource _image; public ImageSource Image { get { return _image; } set { _image = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Image")); } } static TreeViewTestItem() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeViewTestItem), new FrameworkPropertyMetadata(typeof(TreeViewTestItem))); } public TreeViewTestItem() { PreviewMouseDown += TreeViewTestItem_PreviewMouseDown; _image = (ImageSource)new ImageSourceConverter().ConvertFrom(new Uri(@"pack://application:,,,/DEBUG;component/Resources/Icons/square.png")); } }
在Themes/Generic.xaml中定义了对应样式:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-DEBUG.Views.Controls"> <Style TargetType="{x:Type local:TreeViewTestItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}"> <Setter Property="HeaderTemplate"> <Setter.Value> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="0,0,0,3"> <Image Source="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:TreeViewTestItem}}, Path=Image, Mode=TwoWay}" VerticalAlignment="Center" Width="30" Height="30" /> <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=Header}" VerticalAlignment="Center" Margin="5,0,0,0" FontSize="18" /> </StackPanel> </DataTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
TestView中TreeView绑定ViewModel的集合:
<Window x:Class="DEBUG.Views.TestView" 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:src="clr-namespace:DEBUG.Views.Controls" xmlns:local="clr-namespace:DEBUG.Views" mc:Ignorable="d"> [...] <TreeView ItemsSource="{Binding TreeViewTests}"> </TreeView> </Window>
ViewModel填充集合的代码:
class TestViewModel : MVVMHelper { public ObservableCollection<TreeViewTestItem> TreeViewTests { get; set; } [...] public TestViewModel(List<Test> testList) { TreeViewTests = new ObservableCollection<TreeViewTestItem>(); } private void AddTestNode(TreeViewTestItem treeItem, List<Test> testList) { foreach (Test test in testList) { TreeViewTestItem tvi = new TreeViewTestItem(); tvi.Test = test; tvi.Header = test.LibelleEn; if (treeItem == null) { TreeViewTests.Add(tvi); } else { treeItem.Items.Add(tvi); } AddTestNode(tvi, test.ListeTest); } } }
核心问题:保留TreeViewTestItem静态构造函数中的DefaultStyleKeyProperty.OverrideMetadata代码时,TreeView为空;移除后能正常显示,但自定义样式无法生效。尝试过移动自定义项位置、直接在视图中设置样式均无效,无异常抛出。
解决方案
1. 显式合并Generic.xaml资源字典
WPF自动加载Themes/Generic.xaml可能因项目结构或命名空间问题失效,可在App.xaml中显式合并:
<Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/DEBUG;component/Themes/Generic.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources>
注意将DEBUG替换为你的实际程序集名称。
2. 修正HeaderTemplate的绑定逻辑
设置DefaultStyleKey后,RelativeSource查找可能失效,可直接绑定到当前TreeViewTestItem实例:
<Style TargetType="{x:Type local:TreeViewTestItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}"> <Setter Property="HeaderTemplate"> <Setter.Value> <DataTemplate DataType="{x:Type local:TreeViewTestItem}"> <StackPanel Orientation="Horizontal" Margin="0,0,0,3"> <Image Source="{Binding Image, Mode=TwoWay}" VerticalAlignment="Center" Width="30" Height="30" /> <TextBlock Text="{Binding Header}" VerticalAlignment="Center" Margin="5,0,0,0" FontSize="18" /> </StackPanel> </DataTemplate> </Setter.Value> </Setter> </Style>
通过指定DataType,让DataTemplate直接绑定到控件实例,避免查找上下文问题。
3. 检查命名空间与程序集匹配
确保Generic.xaml中的xmlns:local命名空间与TreeViewTestItem所在程序集完全一致。若控件命名空间为DEBUG.Views.Controls但程序集名称不同,需补充程序集信息:
xmlns:local="clr-namespace:DEBUG.Views.Controls;assembly=YourActualAssemblyName"
4. 确认样式继承完整逻辑
自定义样式必须通过BasedOn="{StaticResource {x:Type TreeViewItem}}"继承原生TreeViewItem的所有布局、交互逻辑,否则重写DefaultStyleKey后控件可能因缺失基础模板无法渲染。
内容的提问来源于stack exchange,提问作者Alex

