Sunday, January 31, 2010

WPF : Using the designer with design time data

Back on the WPF track. Since it is really needed at my company, I re-started (again). But I finally have some good news :-). Before, I wrote all the WPF programs with 'NotePad'. Not really NotePad, I did use VS2008, but without the designer.
The designer does not work with the programs at work! and I thought : yup another glitch in WPF....

So I started wondering why this would be, and started from scratch.
° add a screen : designer works
° add a listbox : designer still works
° bind the listbox itemsource to values (via a dataprovider) : designer works
° retrieve the data from a database via wcf service via company framework: designer does NOT work ????

Meaning it had to be something with the WCF service or the company framework, but since the program does work in runtime, this would be hard to pinpoint(maybe due to the security - login). So I tried another approach : The basic idea is that I want to make a screen, and preferably with some dummy data so I can use the designer. This data does not have to be data from the database.

Conclusion : in design time the dataprovider may retrieve dummy data, in runtime it has to go via the wcf service to the database.
And there may not be any code changes required after the design is done. Otherwise there is a risk of showing dummy data in a production environment!

Solution : different dataproviders.
In the old way of working I had 1 dataprovider class, mostly called DataProvider
Now I split this up into 3 classes and 1 interface :
° DataProvider : the one the xaml file(s) bind to like before.
° DataProviderDesignTime : this one provides dummy data
° DataProviderRunTime : this one goes via the wcf service to the database
° IDataProvider : holds all the methods of the various dataproviders

Way of working :
° Dataprovider has a private variable of type IDataprovider, initialised to DataProviderDesignTime.
° At runtime, one of my first commands is to change this IDataprovider in the Dataprovider class from design-time to run-time, via a shared function called SetRuntimeDataprovider

Now this saves me a lot of time : I can finally use the designer !!
And even the effect of convertes can be shown now, cool.

The code

<Window x:Class="WpfApplication8.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lc="clr-namespace:WpfApplication8"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider x:Key="ListAllPersons"
ObjectType="{x:Type lc:DataProvider}"
MethodName="ListAllPersons"
/>

<DataTemplate x:Key="PersonTemplate" >
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}" />
<Label Content="{Binding Age}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Source={StaticResource ListAllPersons}}"
ItemTemplate="{StaticResource PersonTemplate}" >
</ListBox>
</StackPanel>
</Window>



using System;
using System.Collections.Generic;

namespace WpfApplication8
{
public class DataProvider
{
private static IDataprovider dp = new DataProviderDesignTime();

public static List<Person> ListAllPersons()
{
return dp.ListAllPersons();
}

public static void SetRuntimeDataprovider()
{
dp = new DataProviderRunTime();
}
}
}

using System;
using System.Collections.Generic;

namespace WpfApplication8
{
interface IDataprovider
{
List<Person> ListAllPersons();
}
}

using System;
using System.Collections.Generic;

namespace WpfApplication8
{
class DataProviderDesignTime : IDataprovider
{
public List<Person> ListAllPersons()
{
List<Person> result = new List<Person>();
for (int i = 1; i < 10; i++)
{
result.Add(new Person { Name = "Clone " + i.ToString(), Age = i });
}
return result;
}
}
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WpfApplication8
{
class DataProviderRunTime : IDataprovider
{
public List<Person> ListAllPersons()
{
// get the data
}
}
}