WPF/Silverlight MVVM Design Data - create once and share with Unit Tests

Getting data appearing in design view in Blend is key to being able to manipulate the your layout and styles without needing to run up your app (to quote Marcin, ‘nudge, nudge, nudge instead of nudge-F5-waaaait, nudge-F5-waaaaait’)

As soon as a colleague pointed out to me VS2010 now supports Blend’s d:DataContext I brought forward getting things ‘working in in design view’, as I realised here was a way to quickly and easily wire up a test rig of data in C# that I could share between my unit tests, a mock session of my WPF/Silverlight app and design view in both Blend and VS2010 (rather than xaml test data for UI, C# or for tests/runtime test UI). Down to it …

Working backwards, here’s my end result (Bob and Alice are a List<Person> bound to a ListView). Doesn’t look like much, but it works with all xaml controls including DataGrids/TreeViews etc, so you should quickly start to see the power in simplicity.

image

Key XAML:

<UserControl ... xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:md="clr-namespace:MvvmDemo" mc:Ignorable="d"
        d:DesignHeight="600" d:DesignWidth="800"
        d:DataContext="{x:Static md:DesignData.PersonListViewModel}"
        x:Name="view" DataContext="{Binding ElementName=view, Path=ViewModel}">
... <TextBox Text="{Binding Filter.PersonName}"
... <DatePicker SelectedDate="{Binding Filter.BirthDate}"
... <ListBox ItemsSource="{Binding FilteredPeople}">

By the way, this is very cool: the VS2010 Properties window now has navigation of my ViewModel object hierarchy, as long as I’m bound to my real DataContext in xaml. Still prefer the speed of resharper smart auto-completion though (ctrl+alt+space).

image

Anyway, back to the xaml … note the d:DataContext is bound to a static DesignData class & property. Here you could either inject a fake/mock repository and have the ViewModel do its thing, or stop it from loading and populate it in the DesignData class. Both are good techniques in unit testing and design data, and can be used interchangeably. Here I demo both (the viewmodel loads existing people from the repository, hence in design the fake, and I populate the form filter fields).

public static class DesignData
{
    static DesignData()
    {
        Container.Register<IPersonRepository, FakePersonRepository>();
    }

    public static PersonListViewModel PersonListViewModel
    {
        get
        {
            return new PersonListViewModel
                       {
                           Filter = new PersonFilter
                                        {
                                            PersonName = "Ca",
                                            BirthDate = DateTime.Now.AddYears(-20)
                                        }
                       };
        }
    }
}

I inject & reuse the FakeRepository in my TestUI exe (a separate exe so as not to pollute my release code with accidental fake data). 

And just for good measure, here’s a kind of contrived test – the fake data doesn’t really effect the test but you get the idea: write once, use everywhere - same as the rest of your code.

[TestMethod]
public void search_on_name_should_filter()
{
   // ARRANGE 
   _sut = new PersonListViewModel();
   var people = new FakePersonRepository().GetPeople();
   _sut.People.AddRange(people);
   people = new List<Person> { new Person {Name = "Carol"},
                        new Person {Name = "Carlos"},
                        new Person {Name = "Charlie"} };
   _sut.People.AddRange(people);
    string search = "Ca";
    _sut.PersonName = search;

   // ACT 
   _sut.Search();

   // ASSERT
   Assert.IsFalse(_sut.FilteredPeople.Any(p => !p.Name.StartsWith(search)));
   Assert.IsTrue(_sut.FilteredPeople.Any(p => p.Name.StartsWith(search)));
}

Full code here:

image

Technorati Tags: ,,
April 1 2010

WPF Wait Indicator (aka Spinner)

UPDATE: New version of the spinner is available here:

Yet another leftover form Nym’s fancy downloader project is the “spinning doughnut of wait”. The downloader uses BackgroundWorker to get the initial list of videos available for download as doing so on the UI thread could cause the application to freeze for way too long. When we first put the BackgroundWorker in place, there was an “uneasy” period of time when the user would be staring at an empty screen with no visual clues as to what was going on. So I came up with a concept of a “spinner” show below (not very original, I know):

image 

The idea here is to display a spinning “doughnut” which gives the user a clue that something is indeed going on. As this is quite a common issue with background processing, I though that  wrapping the spinner in a reusable user control may be useful. The logic behind the control is simple: as soon as it becomes visible it starts spinning and stops when it’s being hidden. Parent control may then control visibility of the spinner (through data trigger etc) and that’s all that is required to use it. This behaviour is implemented through a bit of code behind as changes in visibility are advertised through IsVIsibleChanged event which is a standard non-routed .NET event. This sadly means that there is no way to react to it from the XAML event triggers and a piece of code was required. It is important to note here that it is necessary to stop the animation when it is no longer required. If you leave it running, it will keep spinning in the background, despite of the fact that it is not visible, consuming processor resources in the process. Get a couple of those running off screen and you will soon notice the difference in your computer’s performance.

Code Snippet
  1. public partial class Spinner : UserControl
  2. {
  3.     private Storyboard _storyboard;
  4.  
  5.     public Spinner()
  6.     {
  7.         InitializeComponent();
  8.  
  9.         this.IsVisibleChanged += OnVisibleChanged;
  10.     }
  11.  
  12.     private void OnVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
  13.     {
  14.         if ( IsVisible )
  15.         {
  16.             StartAnimation();
  17.         }
  18.         else
  19.         {
  20.             StopAnimation();
  21.         }
  22.     }
  23.  
  24.     private void StartAnimation()
  25.     {
  26.         _storyboard = (Storyboard)FindResource("canvasAnimation");
  27.         _storyboard.Begin(canvas, true);
  28.     }
  29.  
  30.     private void StopAnimation()
  31.     {
  32.         _storyboard.Remove(canvas);
  33.     }
  34. }

 

I have tested a couple of approaches to animate the spinner but settled on a version which simply rotates the canvas on which the rectangles are drawn. This means that I have just one thing to animate and when compared with phase-shift-animating opacities of sixteen individual rectangles it substantially easier :) It is also substantially cheaper (processor wise) when compared to animating opacities. The XAML of the spinner is shown below and I hope it is self explanatory. In order to achieve on/off effect for each rectangle I settled on key frame animation which is “jerky” by design, but it means that rectangles maintain their position but become lighter/darker as the doughnut spins thus creating the impression of them being momentarily turned on and then dimming slowly down. Think cheap 80’s disco effect :)

Code Snippet
  1. <UserControl x:Class="FancyPDCDownload2009.Modules.Main.Views.Spinner"
  2.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  4.   <UserControl.Resources>
  5.         <SolidColorBrush x:Key="SpinnerRectangleBrush" Color="Blue"/>
  6.         <Style TargetType="Rectangle">
  7.             <Setter Property="RadiusX" Value="5"/>
  8.             <Setter Property="RadiusY" Value="5"/>
  9.             <Setter Property="Width" Value="50"/>
  10.             <Setter Property="Height" Value="20"/>
  11.             <Setter Property="Fill" Value="{StaticResource SpinnerRectangleBrush}"/>
  12.             <Setter Property="Canvas.Left" Value="220"/>
  13.             <Setter Property="Canvas.Top" Value="140"/>
  14.             <Setter Property="Opacity" Value="0.1"/>
  15.         </Style>
  16.  
  17.         <Storyboard x:Key="canvasAnimation">
  18.             <DoubleAnimationUsingKeyFrames
  19.                RepeatBehavior="Forever"
  20.                SpeedRatio="16"
  21.                Storyboard.TargetProperty="RenderTransform.(RotateTransform.Angle)">
  22.                 <DiscreteDoubleKeyFrame KeyTime="0:0:1" Value="22.5"/>
  23.                 <DiscreteDoubleKeyFrame KeyTime="0:0:2" Value="45"/>
  24.                 <DiscreteDoubleKeyFrame KeyTime="0:0:3" Value="67.5"/>
  25.                 <DiscreteDoubleKeyFrame KeyTime="0:0:4" Value="90"/>
  26.                 <DiscreteDoubleKeyFrame KeyTime="0:0:5" Value="112.5"/>
  27.                 <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="135"/>
  28.                 <DiscreteDoubleKeyFrame KeyTime="0:0:7" Value="157.5"/>
  29.                 <DiscreteDoubleKeyFrame KeyTime="0:0:8" Value="180"/>
  30.                 <DiscreteDoubleKeyFrame KeyTime="0:0:9" Value="202.5"/>
  31.                 <DiscreteDoubleKeyFrame KeyTime="0:0:10" Value="225"/>
  32.                 <DiscreteDoubleKeyFrame KeyTime="0:0:11" Value="247.5"/>
  33.                 <DiscreteDoubleKeyFrame KeyTime="0:0:12" Value="270"/>
  34.                 <DiscreteDoubleKeyFrame KeyTime="0:0:13" Value="292.5"/>
  35.                 <DiscreteDoubleKeyFrame KeyTime="0:0:14" Value="315"/>
  36.                 <DiscreteDoubleKeyFrame KeyTime="0:0:15" Value="337.5"/>
  37.                 <DiscreteDoubleKeyFrame KeyTime="0:0:16" Value="360"/>
  38.             </DoubleAnimationUsingKeyFrames>
  39.         </Storyboard>
  40.     </UserControl.Resources>
  41.     <Grid>
  42.       <TextBlock
  43.          HorizontalAlignment="Center"
  44.          VerticalAlignment="Center"
  45.          Text="Loading..."/>
  46.         <Canvas Height="300" Width="300"
  47.                 Background="Transparent"
  48.                 Name="canvas">
  49.         <!-- First quadrant -->
  50.           <Rectangle Opacity="1"/>
  51.             <Rectangle>
  52.           <Rectangle.RenderTransform>
  53.             <RotateTransform Angle="22.5" CenterX="-70" CenterY="10"/>
  54.           </Rectangle.RenderTransform>
  55.         </Rectangle>
  56.         <Rectangle >
  57.           <Rectangle.RenderTransform>
  58.             <RotateTransform Angle="45" CenterX="-70" CenterY="10"/>
  59.           </Rectangle.RenderTransform>
  60.         </Rectangle>
  61.         <Rectangle >
  62.           <Rectangle.RenderTransform>
  63.             <RotateTransform Angle="67.5" CenterX="-70" CenterY="10"/>
  64.           </Rectangle.RenderTransform>
  65.         </Rectangle>
  66.        
  67.         <!-- Second quadrant -->
  68.         <Rectangle >
  69.           <Rectangle.RenderTransform>
  70.             <RotateTransform Angle="90" CenterX="-70" CenterY="10"/>
  71.           </Rectangle.RenderTransform>
  72.         </Rectangle>
  73.         <Rectangle >
  74.           <Rectangle.RenderTransform>
  75.             <RotateTransform Angle="112.5" CenterX="-70" CenterY="10"/>
  76.           </Rectangle.RenderTransform>
  77.         </Rectangle>
  78.         <Rectangle Name="r7">
  79.           <Rectangle.RenderTransform>
  80.             <RotateTransform Angle="135" CenterX="-70" CenterY="10"/>
  81.           </Rectangle.RenderTransform>
  82.         </Rectangle>
  83.         <Rectangle >
  84.           <Rectangle.RenderTransform>
  85.             <RotateTransform Angle="157.5" CenterX="-70" CenterY="10"/>
  86.           </Rectangle.RenderTransform>
  87.         </Rectangle>
  88.        
  89.         <!-- Third quadrant -->
  90.         <Rectangle Opacity="0.2">
  91.           <Rectangle.RenderTransform>
  92.             <RotateTransform Angle="180" CenterX="-70" CenterY="10"/>
  93.           </Rectangle.RenderTransform>
  94.         </Rectangle>
  95.             <Rectangle Opacity="0.3">
  96.           <Rectangle.RenderTransform>
  97.             <RotateTransform Angle="202.5" CenterX="-70" CenterY="10"/>
  98.           </Rectangle.RenderTransform>
  99.         </Rectangle>
  100.             <Rectangle Opacity="0.4" >
  101.           <Rectangle.RenderTransform>
  102.             <RotateTransform Angle="225" CenterX="-70" CenterY="10"/>
  103.           </Rectangle.RenderTransform>
  104.         </Rectangle>
  105.             <Rectangle Opacity="0.5">
  106.           <Rectangle.RenderTransform>
  107.             <RotateTransform Angle="247.5" CenterX="-70" CenterY="10"/>
  108.           </Rectangle.RenderTransform>
  109.         </Rectangle>
  110.        
  111.         <!-- Fourth quadrant -->
  112.             <Rectangle Opacity="0.6">
  113.           <Rectangle.RenderTransform>
  114.             <RotateTransform Angle="270" CenterX="-70" CenterY="10"/>
  115.           </Rectangle.RenderTransform>
  116.         </Rectangle>
  117.             <Rectangle Opacity="0.7">
  118.           <Rectangle.RenderTransform>
  119.             <RotateTransform Angle="292.5" CenterX="-70" CenterY="10"/>
  120.           </Rectangle.RenderTransform>
  121.         </Rectangle>
  122.             <Rectangle Opacity="0.8">
  123.           <Rectangle.RenderTransform>
  124.             <RotateTransform Angle="315" CenterX="-70" CenterY="10"/>
  125.           </Rectangle.RenderTransform>
  126.         </Rectangle>
  127.             <Rectangle Opacity="0.9">
  128.           <Rectangle.RenderTransform>
  129.             <RotateTransform Angle="337.5" CenterX="-70" CenterY="10"/>
  130.           </Rectangle.RenderTransform>
  131.         </Rectangle>
  132.         <Canvas.RenderTransform>
  133.             <RotateTransform Angle="0" CenterX="150" CenterY="150"/>
  134.         </Canvas.RenderTransform>
  135.                     </Canvas>
  136.     </Grid>
  137. </UserControl>
       

The source code for the downloader including the spinner can be downloaded from here.

March 30 2010

Session downloader for MIX 2010

I’ve rehashed the session downloader to be able to download the Mix 2010 sessions just gone by.

Thanks to Marcin Kaluza for helping out and giving the application some much needed design love.

image

As usual you can pick up the source at bitbucket: http://bitbucket.org/naeem.khedarun/fancypcddownloader2009/

And the download is now available via click-once: http://sharpfellows.com/SessionDownloader/publish.htm

Please let me know if you have any trouble with the application!

Memory Leaks and Dependency Properties

As we all know (or perhaps not) WPF introduced something called the “Dependency Property”.  This is a way of externalizing state from controls.  So for example, the text value of a TextBox is actually not stored within a private field – instead the TextBox has requested the WPF framework to store the value.

This approach is clearly reducing the encapsulation of the controls, however there are some very notable benefits from doing this:

  1. WPF is aware when values change, and so can update UI nicely (i.e. databinding)
  2. WPF can update values safely (e.g. due to animation) and can choose to pause updates onto an object
  3. WPF can optimise storage of these values (e.g. by only storing values which differ to default)
  4. Values can cascade along the visual control tree, thus allowing styling, inherited values and so on

Overall they are a big win for WPF.  Here is a typical pattern for implementing a dependency property:

   1: public static readonly DependencyProperty FooProperty = 
   2:     DependencyProperty.Register("Foo", typeof(string), typeof(MyUserControl));
   3:  
   4: public string Foo
   5: { 
   6:     get { return (string)GetValue(FooProperty); } 
   7:     set { SetValue(FooProperty, value); } 
   8: } 


So how can we intercept property updates?

Obviously we can put code into the setter of the property … but given point 2 above (i.e. WPF will sometimes update the values directly without calling our object code) this will not cover all eventualities. And so the WPF team came up with a way of notifying our code when a value changes – the DependencyPropertyDescriptor.  You’ll often see samples around the web like this:

   1: DependencyPropertyDescriptor dpd = 
   2:     DependencyPropertyDescriptor.FromProperty(MyUserControl.FooProperty, 
   3:                                               typeof(MyUserControl)));
   4: if (dpd != null)
   5: {
   6:     dpd.AddValueChanged(ControlInstance, delegate
   7:     {
   8:         // Add property change logic.
   9:     });
  10: }


and this will now leak memory.

And where is the memory leak?

Whenever you attach an object onto an event of another object you introduce the potential for a memory leak.  This WPF is fundamentally no different.  The use of the AddValueChanged method has introduced a memory leak, since the object containing our code is now referenced by the dependency property and so will never get garbage collected.

So we need to remove the event handler when …

Exactly … when should we remove the event handler?  Event handlers are typically removed as part of the Dispose pattern, and then everyone tries really hard to make sure views and other objects get disposed properly.  WPF gives us another, much cleaner and more reliable option:

   1: private static readonly DependencyProperty FooPropertyDescriptor = 
   2:     DependencyProperty.RegisterAttached("FooDescriptor", 
   3:                                         typeof (DependencyPropertyDescriptor), 
   4:                                         typeof (MyUserControl));
   5:  
   6: private void AttachEventHandlers() 
   7: { 
   8:     DependencyPropertyDescriptor dpd = 
   9:        DependencyPropertyDescriptor.FromProperty(GridViewColumn.WidthProperty, 
  10:                                                  typeof (GridViewColumn)); 
  11:     if(dpd != null) 
  12:     { 
  13:         dpd.AddValueChanged(source, OnFooChanged); 
  14:         source.SetValue(FooPropertyDescriptor, dpd); 
  15:     } 
  16: }
  17:  
  18: private void OnFooChanged(object sender, EventArgs arg) 
  19: { 
  20:     var source = (DependencyObject)sender; 
  21:     if (!BindingOperations.IsDataBound(source, FooProperty)) 
  22:     { 
  23:         // We are no longer data-bound, so we need to remove the ValueChanged 
  24:         //    event listener to avoid a memory leak 
  25:         var dpd = (DependencyPropertyDescriptor)source.GetValue(FooPropertyDescriptor); 
  26:         if(dpd != null) 
  27:         { 
  28:             dpd.RemoveValueChanged(source, OnFooChanged); 
  29:             source.SetValue(FooPropertyDescriptor, null); 
  30:         } 
  31:         return; 
  32:     }
  33:  
  34:     // Add property change logic 
  35: }
  36:  


Explanation:

  1. We register a private DependencyProperty of type DependencyPropertyDescriptor
  2. When we register the value changed callback, we set the value of this private dependency property
  3. Whenever the value changed callback is called, then we check BindingOperations.IsDataBound.  If this returns false then we use the cached DependencyPropertyDescriptor to deregister the value changed callback.

Result:

  1. No more memory leaks

Credits and references:

A big part of this technique comes from:

http://wpfglue.wordpress.com/2009/12/03/forwarding-the-result-of-wpf-validation-in-mvvm/

For a more detailed primer on dependency properties check out:

http://compilewith.net/2007/08/wpf-dependency-properties.html

March 19 2010
Newer Posts Older Posts