Archive

Posts Tagged ‘DependencyProperty’

Optimizing a WPF Screen – Template Triggers

March 21, 2010 Leave a comment

This is the third post in a series about my experience optimizing a WPF screen.  Last time, I determined that the data binding to XML via an XmlDataProvider was a huge contributor to the performance issues.

For the final pass at optimizing my screen, I dug into the actual XAML templates.  As I said before, my expectations going into this project was that the actual cause of the performance issues was going to be the XAML – nested Grids, extraneous markup, etc.  I was very surprised to find that so much of the problem was stemming from the use of XML Binding.  Still, I had hopes that optimizations the XAML itself could yield some benefit.

The first thing I found were constructs of the form:

<Grid>
 <Rectangle Style="{StaticResource DatapointBubbleStyle}" />
 <Grid Margin="5">
  <Grid.RowDefinitions>
   <RowDefinition Height="Auto"/>
   <RowDefinition MinHeight="25"/>
  </Grid.RowDefinitions>
  <TextBlock Text="{Binding XPath=@Label}" Grid.Row="0" />
  <Control Grid.Row="1" DataContext="{Binding XPath=Value}" />
 </Grid>
</Grid>

which I refactored to be like:

<Border Style="{StaticResource DatapointBubbleStyle}" />
 <Grid Margin="5">
  <Grid.RowDefinitions>
   <RowDefinition Height="Auto"/>
   <RowDefinition MinHeight="25"/>
  </Grid.RowDefinitions>
  <TextBlock Text="{Binding XPath=@Label}" Grid.Row="0" />
  <Control Grid.Row="1" DataContext="{Binding XPath=Value}" />
 </Grid>
</Border>

thus removing one nested Grid and making the layout a little more explicit (it took me a minute to realize that the Rectangle would be stretched to fill the entire containing Grid, thus simulating a border).  While this did yield a performance improvement, it was very small, only about 1% – certainly worth being aware of, but probably not even worth the time to hunt out and fix all such uses.

The second thing I found were constructs of the form:

<DataTemplate x:Key="DataNodeTemplate">
 <Grid>
  <TextBlock x:Name="TypeText" Text="{Binding XPath=@Type}"
    Visibility="Collapsed" />
  <Control x:Name="TemplatedDataPoint" Tag="{Binding XPath=@DisplayPath}"
    Loaded="Control_Loaded"/>
 </Grid>
 <DataTemplate.Triggers>
  <Trigger SourceName="TypeText" Property="Text" Value="field">
   <Setter TargetName="TemplatedDataPoint" Property="Template"
     Value="{StaticResource FieldTemplate}"/>
  </Trigger>
  <Trigger SourceName="TypeText" Property="Text" Value="link">
   <Setter TargetName="TemplatedDataPoint" Property="Template"
     Value="{StaticResource LinkTemplate}"/>
  </Trigger>
  <Trigger SourceName="TypeText" Property="Text" Value="text">
   <Setter TargetName="TemplatedDataPoint" Property="Template"
     Value="{StaticResource TextTemplate}"/>
  </Trigger>
 </DataTemplate.Triggers>               
</DataTemplate>

This was a little bit trickier to understand.  The TextBlock was being used to pull some piece of information out of the XML which could then be fed into triggers to set the Template.  The Control_Loaded event was used to bridge from the leaf of the Layout tree (the DataNode) to the actual data (the DataPoint).  Refactoring this XAML to use DataTriggers would let me drop the containing Grid and the TextBlock, so I changed it to be this:

<DataTemplate x:Key="DataNodeTemplate">
 <Control x:Name="TemplatedDataPoint" Tag="{Binding XPath=@DisplayPath}"
    Loaded="Control_Loaded"/>
 <DataTemplate.Triggers>
  <DataTrigger Binding="{Binding XPath=@Type}" Value="field">
   <Setter TargetName="TemplatedDataPoint" Property="Template"
     Value="{StaticResource FieldTemplate}"/>
  </DataTrigger>
  <DataTrigger Binding="{Binding XPath=@Type}" Value="link">
   <Setter TargetName="TemplatedDataPoint" Property="Template"
     Value="{StaticResource LinkTemplate}"/>
  </DataTrigger>
  <DataTrigger Binding="{Binding XPath=@Type}" Value="text">
   <Setter TargetName="TemplatedDataPoint" Property="Template"
     Value="{StaticResource TextTemplate}"/>
  </DataTrigger>
 </DataTemplate.Triggers>
</DataTemplate>

What I found when I tested this was, if anything, a degradation in performance.  This was perplexing to me – I had reduced the amount of XAML in the screen, and yet things got less performant.

My previous experience with XML Binding suggested the answer.  The original XAML had one binding into the XML, with three Triggers against the result of that binding.  My revised XAML had three bindings into the XML. 

It is always tempting for me to think of triggers in a template to be analogous to a switch statement.  In truth, they are actually analogous to a series of unrelated if statements.  Presumably, WPF has some smarts to figure out when it actually needs to reevaluate any given trigger, but when the underlying data changes, all of the affected triggers are evaluated.

My refactoring of the XAML has exchanged one XPath lookup for three, and the cost of that has more than offset any savings from reducing the complexity of the XAML. 

Since in this particular place, the trigger conditions would never change once the screen was loaded, I returned to my object model for the Layout.  I changed the DataNode class to derive from Control, loaded the three ControlTemplates one time on startup, and added this code to the constructor:

switch (Type)
{
 case "field":
  Template = FieldTemplate;
  break;
 case "type":
  Template = TypeTemplate;
  break;
 case "text":
  Template = TextTemplate;
  break;
}

Testing this change revealed a measurable performance boost.  Investigating further, I found that the the FieldTemplate (the only branch being utilized in my test) had 35 triggers – all of them MultiDataTriggers – to determine things like exactly which control to consume (DateEdit, MaskedText, etc.) and to set various run-time states (“required”, read-only, etc.). 

In some cases, the triggers were nominally redundant:  there were 4 triggers to determine if the field was “required” – necessary because of the different permutations of data internal to the DataPoint.  Many others were for conditions that would never change once the record was displayed (e.g., the actual control type to be used based on the data type of the field).  But, even assuming that WPF is very efficient in determining which triggers need to be evaluated on a change of data, there were always many extraneous triggers that would have to be evaluated.

As a quick test, I removed the 30 or so trigger conditions that would never pass for my test scenario.  This resulted in about a 20% performance increase over my previous results.  I returned to my data model and refactored the DataPoint class to calculate all of the trigger conditions intelligently in code and expose the results as DependencyProperties.  I refactored the FieldTemplate to bind to these results, and removed all of the triggers from my templates.

Using the object-model binding and refactored templates, I reran my tests and collected new metrics:

Load the screen 333ms
Set a value in a DataNode which triggers automation 5ms
Set a value in a DataNode which doesn’t trigger automation 1ms
Memory consumption (via GC.GetTotalMemory(true)) 15mb

Which is a 25% improvement over my last time of 449ms, and 75% improvement over my initial time of 1390ms to load the screen.  The apparently improvement in time to set a DataNode value is sufficiently small that it may be the result of random noise in the process (although the possibility of a 15% improvement there is not bad news).

My take away from all of this is that declarative logic in the XAML via triggers is something to be considered carefully – particularly for things that are relatively static. 

This makes me think that I should take another look at the Visual State Manager.  When I first encountered this, several years ago, I dismissed it as a hack for Silverlight that was back-ported into WPF to appease the “cross platform developer” crowd.  Since I was strictly a WPF developer, and everything you could do with VSM you could do with triggers, I didn’t give it any further thought.  Now, though, I’m not so sure.  Most of the triggers that I see in XAML are to transition between states – and if the logic for determining this is complex, this logic can be much more efficiently be performed in code.  Maybe there is some gold in there, after all.

Since last time, I’ve done some reading on MVVM, and I’m pretty much sold on it.  It just fits with the way that I think.  It takes the WPF concept of a “lookless control” and expands it to a “lookless data model”.  My previous exposure to MVVM was through some projects on CodePlex that sought to build a sophisticated framework for MVVM (like Cinch).  These had led me to believe that MVVM was complex and deterred me from serious consideration at the time.  I went back to the WPF page on CodePlex and found the MVVM toolkit on the WPF Futures page, which gave me a better appreciation for the simplicity and elegance.  I strongly recommend the pattern.

In addition to Cinch, I’m also going to be taking a deeper look at MVVM Foundation and MVVM Light Toolkit.  I’ll write about my findings in the future.

Follow

Get every new post delivered to your Inbox.