如何为WPF自定义控件内的ContentPresenter指定专属ResourceDictionary
解决方案
你遇到的报错原因是ResourceDictionary本身没有继承DependencyObject,它的Source属性是普通CLR属性,不支持绑定赋值,所以你之前的写法无法生效。有两种成熟的实现方案可以满足你的需求:
方案1:通过依赖属性回调手动注入资源(适合ContentPresenter可直接访问的场景)
- 在自定义控件的后台代码中定义两个
ResourceDictionary类型的依赖属性,并指定属性变更回调:
public static readonly DependencyProperty HeaderResourcesProperty = DependencyProperty.Register( nameof(HeaderResources), typeof(ResourceDictionary), typeof(DynamicDataGridControl), new PropertyMetadata(null, OnHeaderResourcesChanged)); public ResourceDictionary HeaderResources { get => (ResourceDictionary)GetValue(HeaderResourcesProperty); set => SetValue(HeaderResourcesProperty, value); } public static readonly DependencyProperty CellResourcesProperty = DependencyProperty.Register( nameof(CellResources), typeof(ResourceDictionary), typeof(DynamicDataGridControl), new PropertyMetadata(null, OnCellResourcesChanged)); public ResourceDictionary CellResources { get => (ResourceDictionary)GetValue(CellResourcesProperty); set => SetValue(CellResourcesProperty, value); } private static void OnHeaderResourcesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control = (DynamicDataGridControl)d; if (e.NewValue is ResourceDictionary res) { control.HeaderPresenter.Resources.MergedDictionaries.Clear(); control.HeaderPresenter.Resources.MergedDictionaries.Add(res); } } private static void OnCellResourcesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control = (DynamicDataGridControl)d; if (e.NewValue is ResourceDictionary res) { control.CellPresenter.Resources.MergedDictionaries.Clear(); control.CellPresenter.Resources.MergedDictionaries.Add(res); } }
- 使用者调用时直接传入资源字典即可:
<UserControl.Resources> <ResourceDictionary x:Key="HeaderRes"> <DataTemplate DataType="{x:Type local:MyData}"> <!-- 头部模板 --> </DataTemplate> </ResourceDictionary> <ResourceDictionary x:Key="CellRes"> <DataTemplate DataType="{x:Type local:MyData}"> <!-- 单元格模板 --> </DataTemplate> </ResourceDictionary> </UserControl.Resources> <CustomControl HeaderResources="{StaticResource HeaderRes}" CellResources="{StaticResource CellRes}"/>
方案2:使用附加属性实现(适合ContentPresenter在DataTemplate内部、无法直接按名称访问的场景)
针对你提到的两个ContentPresenter属于DataTemplate一部分的情况,用附加属性可以更灵活地完成资源注入:
- 定义附加属性类:
public static class ContentPresenterResources { public static readonly DependencyProperty CustomResourcesProperty = DependencyProperty.RegisterAttached( "CustomResources", typeof(ResourceDictionary), typeof(ContentPresenterResources), new PropertyMetadata(null, OnCustomResourcesChanged)); public static void SetCustomResources(DependencyObject element, ResourceDictionary value) { element.SetValue(CustomResourcesProperty, value); } public static ResourceDictionary GetCustomResources(DependencyObject element) { return (ResourceDictionary)element.GetValue(CustomResourcesProperty); } private static void OnCustomResourcesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not ContentPresenter presenter || e.NewValue is not ResourceDictionary res) return; presenter.Resources.MergedDictionaries.Clear(); presenter.Resources.MergedDictionaries.Add(res); } }
- 在自定义控件的XAML中绑定附加属性:
<ContentPresenter x:Name="HeaderPresenter" local:ContentPresenterResources.CustomResources="{Binding ElementName=self, Path=HeaderResources}"/> <ContentPresenter x:Name="CellPresenter" local:ContentPresenterResources.CustomResources="{Binding ElementName=self, Path=CellResources}"/>
使用方式和方案1完全一致,不需要修改使用者的调用代码。
两种方案都可以实现相同数据类型在两个ContentPresenter中匹配不同的隐式DataTemplate的需求,不需要额外转换器,性能也没有明显损失。
内容的提问来源于stack exchange,提问作者mike
相关产品推荐
相关产品推荐

