WPF ContentPresenter模板重复重建:如何避免属性变更触发重建?
问题:WPF ContentPresenter 频繁重建 DataTemplate 的解决办法
在WPF用户控件中,通过ContentPresenter结合DataTemplate,基于ViewModel的Unit.IsColor.Value属性切换子控件。初始切换正常,但当应用正确模板后,ViewModel中其他仅绑定到模板内部控件的属性发生变更时,ContentPresenter会重新创建整个DataTemplate,而非仅更新模板内的控件。调用栈显示MeasureCore触发了ApplyTemplate等重建流程。
原始XAML代码
<ContentPresenter Grid.Row="2" Content="{Binding}"> <ContentPresenter.Resources> <DataTemplate x:Key="Gray" d:DataType="{x:Type vm:ResultViewModel}"> <util:RestrictDesiredSize KeepHeight="True"> <Border Padding="5,5"> <controls:GrayAnalysis/> </Border> </util:RestrictDesiredSize> </DataTemplate> <DataTemplate x:Key="Color" d:DataType="{x:Type vm:ResultViewModel}"> <util:RestrictDesiredSize KeepHeight="True"> <Border Padding="5,5"> <controls:ColorAnalysis/> </Border> </util:RestrictDesiredSize> </DataTemplate> </ContentPresenter.Resources> <ContentPresenter.Style> <Style TargetType="{x:Type ContentPresenter}"> <Setter Property="ContentTemplate" Value="{StaticResource Gray}" /> <Style.Triggers> <DataTrigger Value="True" Binding="{Binding Unit.IsColor.Value}"> <Setter Property="ContentTemplate" Value="{StaticResource Color}" /> </DataTrigger> <DataTrigger Value="False" Binding="{Binding Unit.IsColor.Value}"> <Setter Property="ContentTemplate" Value="{StaticResource Gray}" /> </DataTrigger> </Style.Triggers> </Style> </ContentPresenter.Style> </ContentPresenter>
触发重建的调用栈
mscorlib.dll!System.Activator.CreateInstance(System.Type type, bool nonPublic) Unknown mscorlib.dll!System.RuntimeType.CreateInstanceImpl(System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, object[] args, System.Globalization.CultureInfo culture, object[] activationAttributes, ref System.Threading.StackCrawlMark stackMark) Unknown mscorlib.dll!System.Activator.CreateInstance(System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, object[] args, System.Globalization.CultureInfo culture, object[] activationAttributes) Unknown mscorlib.dll!System.Activator.CreateInstance(System.Type type, object[] args) Unknown System.Xaml.dll!System.Xaml.Schema.SafeReflectionInvoker.CreateInstanceCritical(System.Type type, object[] arguments) Unknown System.Xaml.dll!System.Xaml.Schema.XamlTypeInvoker.CreateInstance(object[] arguments) Unknown System.Xaml.dll!MS.Internal.Xaml.Runtime.ClrObjectRuntime.CreateInstance(System.Xaml.XamlType xamlType, object[] args) Unknown System.Xaml.dll!System.Xaml.XamlObjectWriter.Logic_CreateAndAssignToParentStart(MS.Internal.Xaml.Context.ObjectWriterContext ctx) Unknown System.Xaml.dll!System.Xaml.XamlObjectWriter.WriteEndObject() Unknown PresentationFramework.dll!System.Windows.FrameworkTemplate.LoadTemplateXaml(System.Xaml.XamlReader templateReader, System.Xaml.XamlObjectWriter currentWriter) Unknown PresentationFramework.dll!System.Windows.FrameworkTemplate.LoadTemplateXaml(System.Xaml.XamlObjectWriter objectWriter) Unknown PresentationFramework.dll!System.Windows.FrameworkTemplate.LoadOptimizedTemplateContent(System.Windows.DependencyObject container, System.Windows.Markup.IComponentConnector componentConnector, System.Windows.Markup.IStyleConnector styleConnector, System.Collections.Generic.List<System.Windows.DependencyObject> affectedChildren, System.Windows.UncommonField<System.Collections.Hashtable> templatedNonFeChildrenField) Unknown PresentationFramework.dll!System.Windows.FrameworkTemplate.LoadContent(System.Windows.DependencyObject container, System.Collections.Generic.List<System.Windows.DependencyObject> affectedChildren) Unknown PresentationFramework.dll!System.Windows.StyleHelper.ApplyTemplateContent(System.Windows.UncommonField<System.Collections.Specialized.HybridDictionary[]> dataField, System.Windows.DependencyObject container, System.Windows.FrameworkElementFactory templateRoot, int lastChildIndex, System.Collections.Specialized.HybridDictionary childIndexFromChildID, System.Windows.FrameworkTemplate frameworkTemplate) Unknown PresentationFramework.dll!System.Windows.FrameworkTemplate.ApplyTemplateContent(System.Windows.UncommonField<System.Collections.Specialized.HybridDictionary[]> templateDataField, System.Windows.FrameworkElement container) Unknown PresentationFramework.dll!System.Windows.FrameworkElement.ApplyTemplate() Unknown PresentationFramework.dll!System.Windows.FrameworkElement.MeasureCore(System.Windows.Size availableSize) Unknown PresentationCore.dll!System.Windows.UIElement.Measure(System.Windows.Size availableSize) Unknown PresentationCore.dll!System.Windows.ContextLayoutManager.UpdateLayout() Unknown PresentationCore.dll!System.Windows.ContextLayoutManager.UpdateLayoutCallback(object arg) Unknown PresentationCore.dll!System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks() Unknown
解决办法
1. 移除重复的DataTrigger(最简单的修复)
原Style中已经通过Setter设置了默认的Gray模板,当Unit.IsColor.Value变为False时,Style会自动回退到默认值,不需要额外添加False值的DataTrigger。重复设置相同的模板会触发不必要的模板重新应用:
<ContentPresenter.Style> <Style TargetType="{x:Type ContentPresenter}"> <Setter Property="ContentTemplate" Value="{StaticResource Gray}" /> <Style.Triggers> <DataTrigger Value="True" Binding="{Binding Unit.IsColor.Value}"> <Setter Property="ContentTemplate" Value="{StaticResource Color}" /> </DataTrigger> <!-- 移除下面这个重复的DataTrigger --> <!-- <DataTrigger Value="False" Binding="{Binding Unit.IsColor.Value}"> <Setter Property="ContentTemplate" Value="{StaticResource Gray}" /> </DataTrigger> --> </Style.Triggers> </Style> </ContentPresenter.Style>
2. 使用ContentTemplateSelector替代Style触发器
自定义模板选择器,仅在Unit.IsColor.Value变化时切换模板,避免Style触发器可能带来的布局触发问题:
C# 自定义Selector类
public class AnalysisTemplateSelector : DataTemplateSelector { public DataTemplate GrayTemplate { get; set; } public DataTemplate ColorTemplate { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (item is ResultViewModel vm && vm.Unit?.IsColor != null) { return vm.Unit.IsColor.Value ? ColorTemplate : GrayTemplate; } return GrayTemplate; } }
XAML 中使用Selector
<ContentPresenter Grid.Row="2" Content="{Binding}"> <ContentPresenter.Resources> <DataTemplate x:Key="Gray" d:DataType="{x:Type vm:ResultViewModel}"> <util:RestrictDesiredSize KeepHeight="True"> <Border Padding="5,5"> <controls:GrayAnalysis/> </Border> </util:RestrictDesiredSize> </DataTemplate> <DataTemplate x:Key="Color" d:DataType="{x:Type vm:ResultViewModel}"> <util:RestrictDesiredSize KeepHeight="True"> <Border Padding="5,5"> <controls:ColorAnalysis/> </Border> </util:RestrictDesiredSize> </DataTemplate> <local:AnalysisTemplateSelector x:Key="AnalysisSelector" GrayTemplate="{StaticResource Gray}" ColorTemplate="{StaticResource Color}"/> </ContentPresenter.Resources> <ContentPresenter.ContentTemplateSelector> <StaticResource ResourceKey="AnalysisSelector"/> </ContentPresenter.ContentTemplateSelector> </ContentPresenter>
3. 排查自定义控件RestrictDesiredSize的布局逻辑
调用栈显示MeasureCore触发了模板重建,可能是RestrictDesiredSize控件在属性变更时引发了不必要的布局重新测量。可以暂时移除该控件测试,如果问题消失,说明该控件的MeasureOverride或ArrangeOverride逻辑存在问题,需要调整以避免频繁触发布局更新。
内容的提问来源于stack exchange,提问作者Franz Gsell
相关产品推荐
相关产品推荐

