WeakEvents and MVVM

I’ve been doing research into solving the WeakEvent problem as part of building an MVVM framework that suites my needs.  Although I came across a number of articles, none presented as elegant a solution as Thomas Levesque’s post A simple implementation of the WeakEvent pattern.  Pretty much, the only way in which this is lacking for me is that it is not thread-safe, and doesn’t deal with cross thread dispatching.  But, that’s a whole different problem.

Categories: WPF Tags: ,

WPF Themes – Markup Extensions

This is the third post in a series about my experience developing a WPF Theme system.  In my last post, I showed how I solved the problem of Theme Management and extensibility.  

In this post, I’m going to talk about some Markup Extensions that I created in order to assist in making the final Theme brushes.  

When I look at the brushes used in other people’s Themes, it seems clear that the people making them are at least moderately skilled graphic designers.  The obviously have artistic talent.  I, on the other hand, have none.  The best I can do is say “I want this color/brush to be a little darker or lighter than that color/brush.”  

I am also lazy (I have long held that a good engineer is a lazy engineer).  While it would be possible to take the ARGB of a color and manually do math to figure out what another related color should be, there was no way I was going to spend that much time doing something so tedious.  

Instead, I wrote a number of Markup Extensions to simplify this process.  

The first is the ColoringExtension, which takes a base color, an opacity, and an optional background color, and calculates a new color.  

public class ColoringExtension : MarkupExtension
{
    private bool BackgroundColorSet { get; set; }
    private Color _backgroundColor;
    public Color BackgroundColor
    {
        get { return _backgroundColor; }
        set { _backgroundColor = value; BackgroundColorSet = true; }
    }
    public Color BaseColor { get; set; }
    public double Opacity { get; set; }

    public ColoringExtension()
    {
        Opacity = 1.0;
    }

    public override object ProvideValue(IServiceProvider sp)
    {
        if (BackgroundColorSet == false)
        {
            return Color.FromArgb((byte)(Opacity * 255),
                              BaseColor.R,
                              BaseColor.G,
                              BaseColor.B);
        }
        else
        {
            return Color.FromArgb((byte)255,
                              (byte)((Opacity * BaseColor.R) +
                                    ((1 - Opacity) * BackgroundColor.R)),
                              (byte)((Opacity * BaseColor.G) +
                                    ((1 - Opacity) * BackgroundColor.G)),
                              (byte)((Opacity * BaseColor.B) +
                                    ((1 - Opacity) * BackgroundColor.B)));
        }
    }
}

With this, I can take a single base color and create a collection of related colors.  

<SolidColorBrush x:Key="BackgroundBrush">
 <SolidColorBrush.Color>
  <st:Coloring BaseColor="{StaticResource BackgroundColor}"
         BackgroundColor="White"
         Opacity="{StaticResource WindowOpacity}" />
 </SolidColorBrush.Color>
</SolidColorBrush>
<SolidColorBrush x:Key="FadedBackgroundBrush">
 <SolidColorBrush.Color>
  <st:Coloring BaseColor="{StaticResource BackgroundColor}"
         BackgroundColor="White"
         Opacity="0.4" />
 </SolidColorBrush.Color>
</SolidColorBrush>
<SolidColorBrush x:Key="ControlBackgroundBrush">
 <SolidColorBrush.Color>
  <st:Coloring BaseColor="{StaticResource BackgroundColor}"
         BackgroundColor="White"
         Opacity="0.3" />
 </SolidColorBrush.Color>
</SolidColorBrush>

What this really enables me to do is have a single list of brushes used by all Themes, with a simple list of Theme-specific colors – but I’ll get into that more in my next post.  

The other extension is the TintingExtension, which takes a brush, a tint (percent lighter or darker) and an opacity, and modifies the brush that it is attached to.  

public class TintingExtension : MarkupExtension
{
 public Brush Brush { get; set; }
 public double Tint { get; set; }
 public double Opacity { get; set; }

 public TintingExtension()
 {
  Brush = null;
  Tint = 1.0;
  Opacity = 1.0;
 }

 public override object ProvideValue(IServiceProvider sp)
 {
  return new TintInfo(Brush, Tint, Opacity);
 }
}

public class TintInfo
{
 public Brush Brush { get; set; }
 public double Tint { get; set; }
 public double Opacity { get; set; }

 public TintInfo()
 {
 }

 public TintInfo(Brush brush, double tint, double opacity)
 {
  Brush = brush;
  Tint = tint;
  Opacity = opacity;
 }
}

public class Tint
{
 public static TintInfo GetInfo(DependencyObject obj)
 {
  return (TintInfo)obj.GetValue(InfoProperty);
 }

 public static void SetInfo(DependencyObject obj, TintInfo value)
 {
  obj.SetValue(InfoProperty, value);
 }

 public static readonly DependencyProperty InfoProperty = null;

 static Tint()
 {
  FrameworkPropertyMetadata metaData = new FrameworkPropertyMetadata(null,
      new PropertyChangedCallback(OnInfoAttachedPropertyChanged));
  InfoProperty = DependencyProperty.RegisterAttached("Info", typeof(TintInfo),
      typeof(Tint), metaData);
 }

 public static void OnInfoAttachedPropertyChanged(DependencyObject d,
       DependencyPropertyChangedEventArgs e)
 {
  Brush brush = d as Brush;
  TintInfo info = e.NewValue as TintInfo;
  if (brush != null && info != null)
  {
   BrushHelper.CopyAndTintBrush(info.Brush, brush, info.Tint, info.Opacity);
  }
  brush.Freeze();
 }
}

I mainly use this for creating a collection of related gradient brushes from a single base brush  

<LinearGradientBrush x:Key="ActiveItemsGradientBrush" EndPoint="0.5,0" StartPoint="0.5,1">
 <GradientStop Offset="0">
  <GradientStop.Color>
   <st:Coloring BaseColor="{StaticResource SelectedHighlightColor}"
             Opacity="1.0" />
  </GradientStop.Color>
 </GradientStop>
 <GradientStop Offset="0.01">
  <GradientStop.Color>
   <st:Coloring BaseColor="{StaticResource SelectedHighlightColor}"
            Opacity="0.5" />
  </GradientStop.Color>
 </GradientStop>
 <GradientStop Offset="0.45">
  <GradientStop.Color>
   <st:Coloring BaseColor="{StaticResource SelectedHighlightColor}"
            Opacity="0.0" />
  </GradientStop.Color>
 </GradientStop>
 <GradientStop Offset="0.55">
  <GradientStop.Color>
   <st:Coloring BaseColor="{StaticResource SelectedHighlightColor}"
            Opacity="0.0" />
  </GradientStop.Color>
 </GradientStop>
 <GradientStop Offset="0.99">
  <GradientStop.Color>
   <st:Coloring BaseColor="{StaticResource SelectedHighlightColor}"
            Opacity="0.5" />
  </GradientStop.Color>
 </GradientStop>
 <GradientStop Offset="1.0">
  <GradientStop.Color>
   <st:Coloring BaseColor="{StaticResource SelectedHighlightColor}"
            Opacity="1.0" />
  </GradientStop.Color>
 </GradientStop>
</LinearGradientBrush>

<LinearGradientBrush x:Key="SelectedItemsBackgroundBrush"
 st:Tint.Info="{st:Tinting Brush={StaticResource ActiveItemsGradientBrush},
   Opacity={StaticResource ActiveOpacity}}" />
<LinearGradientBrush x:Key="MouseOverItemsBackgroundBrush"
 st:Tint.Info="{st:Tinting Brush={StaticResource SelectedItemsBackgroundBrush},
   Opacity=0.5}" />

The meat of the work is done in the BrushHelper class.  

public static class BrushHelper
{
 public static void CopyAndTintBrush(Brush sourceBrush, Brush targetBrush,
     double tint, double opacity)
 {
  if (sourceBrush.GetType() != targetBrush.GetType())
  {
   throw new ArgumentException("Invlid targetBrush");
  }

  if (sourceBrush is SolidColorBrush)
  {
   SolidColorBrush solidSourceBrush = sourceBrush as SolidColorBrush;
   SolidColorBrush solidTargetBrush = targetBrush as SolidColorBrush;

   solidTargetBrush.Color = TintColor(solidSourceBrush.Color, tint, opacity);
  }
  else if (sourceBrush is LinearGradientBrush)
  {
   LinearGradientBrush gradientSourceBrush = sourceBrush as LinearGradientBrush;
   LinearGradientBrush gradientTargetBrush = targetBrush as LinearGradientBrush;

   // Empty the target brush's stops, gut in case
   gradientTargetBrush.GradientStops.Clear();

   gradientTargetBrush.StartPoint = gradientSourceBrush.StartPoint;
   gradientTargetBrush.EndPoint = gradientSourceBrush.EndPoint;
   foreach (GradientStop stop in gradientSourceBrush.GradientStops)
   {
    gradientTargetBrush.GradientStops.Add(new
       GradientStop(TintColor(stop.Color, tint, opacity), stop.Offset));
   }
  }
 }

 public static Color TintColor(Color color, double tintShift, double opacity)
 {
  Color newColor = color;

  newColor.A = (byte)Math.Max(Math.Min((double)color.A * opacity, 255.0), 0.0);
  newColor.R = (byte)TintShade((double)color.R, tintShift);
  newColor.G = (byte)TintShade((double)color.G, tintShift);
  newColor.B = (byte)TintShade((double)color.B, tintShift);

  return newColor;
 }

 private static double TintShade(double shade, double tint)
 {
  if (tint == 0)
  {
   return 0;
  }
  else
  {
   double remainder = 255 - shade;
   double newRemainder = remainder / tint;
   return Math.Max(Math.Min(255 - newRemainder, 255), 0);
  }
 }
}

In the next (and final) installment, I’ll tie it all together and show how I built the actual Themes.

Categories: WPF Tags: , , ,

WPF Themes – Theme Management

This is the second post in a series about my experience developing a WPF Theme system.  In the first post, I talked about my frustration in locating an fully-functional system, and my decision to build my own.

I’m going to start by addressing the problem of Theme Management – how to change the Theme dynamically at runtime without disrupting the rest of the application space.  In so doing, I’m also going to solve the issue of extensibility.

As I said before, the way that everyone else seemed to handle Theme Management was to assume that Application.Current.Resources.MergedDictionaries did not contain anything of interest, and simply replaced the contents of this with the Theme Resource Dictionary.  Clearly, in an application of any level of complexity, this just wouldn’t do.

I concluded, however, that the essence of the idea – that having the first Resource Dictionary in this collection be the Theme was a pretty good idea.  There just needed to be a better approach to getting it there.

A little experimentation determined that the first Resource Dictionary in the MergedDictionaries of the first Resource Dictionary in Application.Current.Resources.MergedDictionaries (that is, an extra level down) was still effectively the first Resource Dictionary in the stack.  So, if I could get a Resource Dictionary that I owned to be the first in A.C.R.MD (I’m tired of typing it out), then I could do whatever I wanted to the contents of it.

I created a ThemeManager class that will be used to manage the list of possible Themes, and to set the current Theme.  The first time a Theme is set, this class will examine the contents of A.C.R.MD and make a list of the Uris for any Resource Dictionaries in that collection.  It would then empty that collection, add a special Resource Dictionary to contain the Themes, and then add all of the previous Resource Dictionaries back into the collection.

private static void AddThemesContainer()
{
    lock (_useLock)
    {
        if (Instance.ThemesContainer == null)
        {
            List<Uri> preserveList = new List<Uri>();

            // Make a list of the URIs of things we want to keep
            foreach (ResourceDictionary resource in
                     Application.Current.Resources.MergedDictionaries)
            {
                preserveList.Add(resource.Source);
            }

            // Clear the list
            Application.Current.Resources.MergedDictionaries.Clear();

            // Add the Global Includes dictionary
            ResourceDictionary globalIncludeDictionary = new ResourceDictionary();
            globalIncludeDictionary.Source =
                    new Uri("/SavoyTek.Windows.Themes;component/Themes/GlobalIncludes.xaml",
                    UriKind.RelativeOrAbsolute);
            Application.Current.Resources.MergedDictionaries.Add(globalIncludeDictionary);
            DiscoverThemeRegistrars(globalIncludeDictionary);

            // Add everything else back in
            foreach (Uri uri in preserveList)
            {
                if (uri != null)
                {
                    ResourceDictionary resource = new ResourceDictionary();
                    resource.Source = uri;
                    Application.Current.Resources.MergedDictionaries.Add(resource);
                }
            }

            Instance.ThemesContainer = Application.Current.Resources
                      .MergedDictionaries[0]
                      .MergedDictionaries[0];
        }
    }
}

The GlobalIncludes Resource Dictionary looks like this:

<ResourceDictionary>

    <s:String x:Key="GlobalIncludes">GlobalIncludes</s:String>

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary
                  Source="\SavoyTek.Windows.Themes;component\Themes\ThemesContainer.xaml" />
        <ResourceDictionary
                  Source="\SavoyTek.Windows.Themes;component\Themes\GlobalBrushes.xaml" />
        <ResourceDictionary
                  Source="\SavoyTek.Windows.Themes;component\Themes\GlobalImages.xaml" />
        <ResourceDictionary
                  Source="\SavoyTek.Windows.Themes;component\Themes\CommonStyles.xaml" />
    </ResourceDictionary.MergedDictionaries>

</ResourceDictionary>

As you can see, the ThemeManager saves a reference to the ThemesContainer Resource Dictionary.  It is this Resource Dictionary that ends up containing the Resource Dictionary for the active Theme.  Now that I have a location completely under my control to house the active Theme, changing the Theme is done in essentially the same way as the other Theme Management systems I found.

public static void ChangeTheme(Theme theme)
{
    if (theme != GetCurrentTheme())
    {
        AddThemesContainer();

        lock (_useLock)
        {
            Instance.ThemesContainer.MergedDictionaries.Clear();
            Instance.ThemesContainer.MergedDictionaries.Add(theme.ResourceDictionary);
            Instance.CurrentTheme = theme;
        }

        // Signal that the theme changed.
        OnCurrentThemeChanged();
    }
}

In AddThemesContainer, you may have noticed a call to DiscoverThemeRegistrars.  This method uses reflection to find any classes adorned with a custom attribute.  It is expected that these classes will derive from ThemeResourceRegistrar.  An instance of this class will be created, and it will be given the opportunity to add things (global Styles, Converters, etc.) to the GlobalIncludes Resource Dictionary.

private static void DiscoverThemeRegistrars(ResourceDictionary dictionary)
{
    Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

    foreach (Assembly assembly in assemblies)
    {
        try
        {
            Type[] types = assembly.GetTypes();
            foreach (Type type in types)
            {
                object[] attribs = type.GetCustomAttributes(typeof(ThemeRegistrarAttribute),
                                 false);
                if (attribts.Length > 0)
                {
                    ThemeResourceRegistrar registrar =
                                 (ThemeResourceRegistrar)type.GetConstructor(new Type[] { })
                                 .Invoke(new object[] { });
                    registrar.AddResources(dictionary);
                }
            }
        }
        catch (Exception) { }
    }
}

The idea is that another Assembly in the application space will have a Registrar class, which will be responsible for adding resources local to that Assembly to the Theme space.  It would also be able to register additional Themes with the system.

In the next installment, I’ll talk about custom Markup Extensions that I used in my Themes.

Categories: WPF Tags: , ,

WPF Themes – The Problem

April 11, 2010 Leave a comment

I’m working on a WPF project, and I wanted to add theming capability to it.  What I was wanting wasn’t too sophisticated – just the ability to change the colors (although more would be good).  I had seen several CodePlex projects dealing with this, so I downloaded a few of them to check them out – specifically, WPF, WPF Themes, and the Silverlight Toolkit.

All of these looked good at first blush.  They all had a decent selection of options, all appeared to offer a complete set of control templates, and all had a method for changing the theme at run time.  I figured it was just a matter of picking the one that I liked the best (the WPF Themes one seemed like the most complete option) and move on.

I quickly found that all of the examples that I could find, while apparently fine, were all actually unusable in a real application – that is, any application that was more than a single assembly, with no global resources other than the Theme, and didn’t contain any custom controls.

First of all, the method for theme management was essentially the same for all of them – go to Application.Current.Resources.MergedDictionaries and empty that collection (assuming that the only thing in that collection is the Resource Dictionary for the “current” theme), and then add the Resource Dictionary for the “new” theme.  All well and good…but, what if my application has other things in that collection?  They are just lost.

Next, there was no consistency in the way that the themes defined colors and brushes.  For instance, I wanted to have a panel in a UserControl be the same color as the face of a button.  What I would like to do is reference the same brush as the Button template – that way, when the theme changed, the color of my panel would change.  However, each theme named the brush used for the button background color something completely different.  I originally hoped that it would just be a simple matter of normalizing the names, but I quickly discovered that the internal structure of the themes was so inconsistent that this just wasn’t practical.

And lastly, the scheme was not at all extensible.  Since the control templates were defined internal to the theme itself, if I added a new custom control, I needed to add a template to everything theme – even if the only difference was the way the control was colored.  Furthermore, if that control is defined in a different Assembly than the themes, there was no way for it to participate in the theme.

In the next couple of posts, I’m going to discuss how I resolved these issues and built my own theming system.

Categories: WPF Tags: , , ,

Making a Custom Binding Extension

March 29, 2010 Leave a comment

Recently, I was reading an article on Josh Smith’s blog about how to set the focus from inside the View Model of a MVVM pattern using a custom MarkupExtension class.  While this article was very interesting in and of itself (and I’ll definitely be employing this approach as I continue in my own MVVM adventures), the part that really caught my interest was the link to Philipp Sumi’s blog about how to write a custom a Binding extension class.

This was especially interesting to me because a couple of weeks ago, while working on a custom WPF Theme (see a future post about that), I tried to extend the DynamicResourceExtension class and encountered the same sort of problem that Philipp Sumi encountered – that the necessary parts of DynamicResourceExtension were either sealed or internal. 

Philipp’s solution was to extend MarkupExtension rather than Binding or BindingBase, and to internally create a Binding.  I believe that this approach can be used to wrap a DynamicResource markup in the same way.  I’ll delve into this in greater detail when I blog about authoring a WPF Theme in the next few weeks.

Categories: WPF Tags: , , ,

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.

Optimizing a WPF Screen – XmlDataProvider

This is the second post in a series about my experience optimizing a WPF screen.  Last time, I built a test environment, established that there was, in fact, a problem, and gathered some baseline metrics.

After a little spelunking in the code, I was able to determine that, at its heart, the screen was deceptively simple:

<ScrollViewer VerticalScrollBarVisibility="Auto">
  <ItemsControl ItemsSource="{Binding XPath=//DataRegions/DataRegion}"
                VirtualizingStackPanel.IsVirtualizing="True"
                ItemTemplate="{StaticResource DataRegionTemplate}" />
</ScrollViewer>

Now, I knew that the XML being rendered contained two sections – one contained the data, and the other contained information about how the data should be laid out.  This ItemsControl was binding to the top of the layout tree – a DataRegion contained rows, which contained columns, which contained groups, which themselves contained rows, which contained columns, which contained DataNodes.

An aside about this construct: the purpose here was to allow our customers to lay out a data-entry form tailored to their specific needs.  We explored having the screen builder generate XAML, and either compile it or parse it at runtime.  However, we learned (much to our surprise) that it was actually faster to have an ItemsControl (or similar container) with an appropriate (or even dynamic) DataTemplate than it was to have a larger piece of explicit XAML.

A quick search of the code told me that DataRegionTemplate was a DataTemplate that contained an ItemsControl bound to RegionRows, and that the ItemTemplate of this control pointed to another DataTemplate containing an ItemsControl, and so on down to the DataNode.  The layout that was being rendered was not overly complex, so I didn’t think there was anything particularly wrong with this scheme.  One thing that did draw my attention, however, was that all of the DataTemplates employed XPath binding.

Full discloser: I am not a believer in XML as a data-model.  I think that there are lots of places where XML is the perfect tool – persisting dynamic semi-structured data, as a medium for communication between distributed systems, that sort of thing.  But, unless you use an accompanying schema (which I pretty much never see), it is not typed and there is no way to discover what the structure could or should be, only what it is.  It just feels sloppy – like an old VB program where you’d let all variables be VARIANT.  You could do it, but it almost always led to problems.  So, I was suspicious of the fact that the data was accessed and manipulated entirely within an XML document.

With this bias in mind, my first thought was to render the XML into an object model, and bind to that object model.  Since the layout portion of the screen was relatively simple – only 7 different classes, each with just a few attributes and a list of child elements – I decided to start there.

An hour’s work later, and I had my 7 classes and their associated revised DataTemplates, and had read the layout data out of the XML and built an object tree.  I fired up my test application and gathered new metrics.  All of the numbers dropped by 20%.

I’ll admit, at this point, I was a little surprised.  Despite my bias against XML, I hadn’t really expected much from this change.  I thought I’d see some improvements, but I had assumed that the real optimization work would happen in the ControlTemplates and DataTemplates further down – like on the DataNodes themselves.

The obvious next step was to render the DataNode portion of the XML into an object model.  A brief investigation, however, revealed that this would be much more involved than I’d like – especially just to experiment with the idea.  While the data portion was just a simple list of keyed DataNodes, there were dozens of different types of DataNodes, each with its own DataTemplate in the XAML – some of which were rather complex.  I wasn’t going to revise all of these DataTemplates just to do my test.  Even if I were to limit my efforts to the DataTemplates that were actually used by my test data, it was too many.

So, instead, I went back to my source XML and revised it so that all of the DataNodes were of the same type.  With that, I reran my initial tests to get a new set of baseline metrics.  Now it was a relatively simple matter to read the XML into a list of my one type of DataNode, and revise the much smaller number of DataTemplates to bind to my object model rather than to the XML.

I reran my tests and collected some new metrics:

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

Which is a 70%(ish) reduction in the initial load time and memory consumption, and a 99% reduction in the data-updating steps.  With this one simple (although involved to fully implement) change, the application had moved from being “too horrible to use” to “eh, it’s a little slow” – and I hadn’t even started to look at the XAML itself.

Now that I knew that there was hope, I went back to try to understand why this change had made such a big difference.  I started by seeking an answer to the simplest question I could raise from the initial data: why did it take 30ms to set a value in the XML and let that propagate through UI?  All that I was doing was changing the InnerText of a single Xml element which resulted in the contents of a single TextBox being changed – why did that take any time?

So, I fired up Reflector (if I had to pick one tool that was most useful for investigating things in .Net, it would be Reflector) and poked around in XmlDataProvider, XmlDocument, and the XPath-based binding constructs.

It’s a little murky in there, and without the ability to step into the WPF code, its a little difficult to tell exactly what is happening.  But, what I concluded was this: ultimately, the binding was attaching to the various NodeChanging events exposed by the XmlDocument as a whole – the individual Xml nodes do not have any events.  This means that, when the contents of the Xml changes, all of the XPath binding objects would receive a notification, and they would look at the information in the notification to determine if this was about a node that they cared about.  Only then would the correct XPath binding object fire a change to the UI.

With all of the bindings that actually occurred the screen, there were literally hundreds of binding objects that had to get notified, even though only one of the cared.  Even though they were individually fast, this added up to enough time to notice.

When I moved to binding to an object model, I was able to use individual DependencyProperties, which fired much more precise change notifications.  So, when I changed a value in the object model, only the individual bindings that cared had to get notified.

Its possible that, at some point in the future, the .Net Framework will be changed so that individual XmlNode objects expose their own notifications, and that this change would enable the original binding scheme to work.  However, even if that were the case, I would still expect it to perform less well because I just can’t believe that evaluating the XPath is cheap – and there are certainly many events that would require it to have to evaluate the XPath again.  That’s just speculation, of course.

My take away from this is that, while there are circumstances where using the XmlDataProvider is appropriate, in larger use cases, you’re better off ditching it.

This brought to mind the MVVM pattern (which I am only now starting to really delve in to).  My (admittedly limited) understanding of the MVVM pattern tells me that what I just did in my application is introduce a ViewModel layer – another layer just below the UI that translates the DataModel into a form more appropriate for the UI layer’s consumption.  It also handles the UI-level logic, so that the UI itself can be as dumb as absolutely possible.  I think I’m going to have to do some deeper research into that pattern – now that I understand a little better the “why” of it (or, at least a “why” of it), I think it will make better sense to me.

Next time, I’ll crack open the XAML itself, and see if any further optimizations can be made at that level.

Follow

Get every new post delivered to your Inbox.