Monday, 26 February 2007
Transient test data for persistent types in Blend Part 2: Binding test data in Blend

In my previous post I was discussing about how to mock test data for persistent objects mapped with Genome. Now I want to discuss how to provide this sample data in Blend so the designer building a front end with WPF actually sees how the UI would look like with data.

Providing the sample data as Collection

Although we can instantiate our sample contact objects, in the application we will need a collection of contacts that we can bind to the list. In the current solution I created a helper class to collect the sample instances in a collection.

public class Root
{
  private ObservableCollection contacts = new
    ObservableCollection();

  public ObservableCollection Contacts
  {
    get { return contacts; }
    set { contacts =  value; }
  }

  public Root()
  {}
}

I am using ObservableCollection (provided by WPF) since this collection class worked best for our usecase (using List<T> does not work when serializing to XAML, as you will see later, and with IEnumerable<T> Blend had problems with reflecting the element type and providing an element sample – but more on this in another blog post).

The contact instances can be easily added to the Contacts collection of the root object as follows:

Root r = new Root();
r.Contacts.Add(c);
r.Contacts.Add(c2);

Sample data still in X(A)ML

When I was writing the construction code of the sample data in C# I realized that XAML itself supports constructing such complex structures exactly:

<Root xmlns="clr-namespace:Contacts.TestData;assembly=Contacts.TestData" xmlns:cb="clr-namespace:Contacts.Business;assembly=Contacts.TestData" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Root.Contacts>
  <cb:mock_Contact ContactId="7152608e-a1d4-4f93-b393-f246f20e1970" FirstName="Jack" LastName="Foo" EmailAddress1="hisemail@dummy.at" >
    <cb:mock_Contact.Account>
      <cb:mock_Account ParentAccount="{x:Null}" AccountId="cd40f630-5a7f-4bf6-a219-d058902e6f1e" Name="TechTalk Ltd"/>
    </cb:mock_Contact.Account>
  </cb:mock_Contact>
</Root.Contacts>
</Root>

Note that wrapping the Contacts collection into Root also provides the necessary root element for the XAML representation of the data.

Describing the sample data in XAML brings back the benefits of the XML representation (which we initially abandoned because of the different data binding syntax), namely that the designer can easily understand and modify data without understanding C#. As I had my data construction code in C# already, I didn’t want to write the XAML for the test data from scratch, so I decided to serialize my constructed root object into XAML. This is easier than you would think:

string xaml = XamlWriter.Save(rs)

The line of code above produces the XAML representation of the test data shown before.

Deserializing an object-graph from XAML is not much harder either; you can call the

XamlReader.Load()
method, passing an XML reader or a Stream.

I’d like to note here that, although XAML serialization is more flexible than the standard XML serialization, there can be still some issues with it, e.g. it does not support generic types, as described here.

Despite of this, my Root class using ObservableCollection<Contact> can be still serialized, but only if both the public property and the underlying representation are ObservableCollection<T>. This seems to be a hack in XAML, since if you change the collection to some other generic type like List<>, you get an exception telling you that generic types cannot be serialized. The same happens if you use ObservableCollection<> internally, but on the public property you specify a less specific interface like IList<> or IEnumerable<>. Strange…

Back to the designer’s box

Now we have the infrastructure to provide sample data as a CLR object graph and if we are lucky we can even construct it in XAML. The only question is how to integrate this into the application.

Playing around with Blend, it turned out that if you want Blend to reflect the properties of your CLR object and to help in the design and binding, you have to add a “CLR object” to the “Data” section. After selecting the type of the object Blend generates an ObjectDataProvider that creates an instance of that type for binding.

<ObjectDataProvider x:Key="RootDS" d:IsDataSource="True" ObjectType="{x:Type Contacts_TestData:Root}"/>

Now, you can either write the constructor of your class to perform what you want to do, or you can change the default ObjectDataProvider to call e.g. a static method that creates the sample data instead of creating a blank instance. In the static method you can execute either your C# sample data generation code or load the sample data from XAML.

<ObjectDataProvider x:Key="RootDS" d:IsDataSource="True" ObjectType="{x:Type Contacts_TestData:Root}" MethodName="ParseXamlFromFile"> 
    <ObjectDataProvider.MethodParameters>
        <system:String>C:\Temp\Contacts_TestData\testdata.xaml</system:String>
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

The runtime data source can be set in the load event of the window with the real data populated from the database. The event handler code runs during the real execution only:

void ContactsWindow_Loaded(object sender, RoutedEventArgs e)
{
  contacts = filter.GetMatchingObjects(Helper.DB, ContextFactory.WinClientContext).ToArray();
}

The shadow-copy problem with Blend

There is a minor but still very inconvenient issue I have with Blend, namely that it executes your assembly in design time from a shadow-copy folder. More interestingly, neither the codebase nor the location of the executed assembly point to the original location. Hence, it is not possible to access any files of your solution with a relative path and you cannot even find out the original location. As a consequence you can load the sample data file only if you either hardwire the full path of it or you make this information somehow available in the designer.

In the above example I wired it in the XAML file where the ObjectDataProvider is declared, passing the path as parameter to the C# code (this way I didn’t wire it in C# at least). Since you can call any static method here, you can easily extend the example in a way that it does not read up the sample data during runtime. You can decide if you are in design mode or not by calling the

System.ComponentModel.DesignerProperties.GetIsInDesignMode(obj)
method.

Unfortunately the configuration seems to suffer from the same problem (the config file is not copied along with your assembly), hence all your config sections return null when running in Blend designer, making it impossible to provide the path as configuration. I am still looking for better solutions here, but if I don’t find any better way I will probably use an environment variable (brr.) so that I can remove the absolute path from the solution.

Still, the ObjectDataProvider solution makes Blend aware of the type of the object you will bind, and it uses this information in several places to help your work.

My other approach to solve the data file problem was to put the XAML sample data into a resource dictionary (or just in any resource) and have it compiled into the solution. In this case I can bind the ItemsSource of my list to the resource by using the StaticResource binding:

<ListBox x:Name="contactsControl" ItemsSource="{Binding Path=Contacts, Mode=Default, Source={StaticResource ContactsSampleData}}"/>

Basically this solves the absolute path problem, as I don’t have to find a file lying around in the solution anymore. But unfortunately Blend does not seem to handle this solution equally well. I was not able to click this binding together, I had to write this binding manually into XAML. Hence, the wizard that used to pop up when binding the ItemsSource with the ObjectDataProvider does not appear in this case either.

Also I don’t see an easy way to use the GetIsInDesignMode in this case to avoid the unnecessary binding of the sample data during runtime. Of course the user never sees this data, as we overwrite it in the Load event, but still it is an unnecessary overhead in the application/window initialization. Hence I decided to stick to the first approach for now.

The results so far

We now have different solutions to the problem, and any of these solutions provide a much better design time experience for the Designer as compared to working without test data.

WPF client in Blend bound to test data

Still, we are seeking for a better solution without the above described flaws to have a more maintainable solution.

Genome | WPF