Wednesday, 11 February 2009

Improvements to WPF data binding

I am working on a WPF application to make loss estimates of live catastrophic events. Part of this application is a wizard that guides the user through the steps of the process. Last year I made a WPF application based on an article I wrote in code project http://www.codeproject.com/KB/WPF/nTierWithLinqAndWPF.aspx. I made a code review of how this works in the context of wizard control and we found some simplifications that mean that we don’t need to use a Singleton and reduce the use of dependency variables.

The first simplification is instead of letting the WPF instantiate the dataprovider on it’s own that this is done as shown below:

public partial class WinLiveCatWizard : Window
{
private WinLiveCatWizardDataProvider DataProvider;

….

public WinLiveCatWizard(String CatFocusSQLServer, int USER_ID)
{
DataProvider = new WinLiveCatWizardDataProvider(CatFocusSQLServer, USER_ID);

InitializeComponent();

}


}

This eliminates the need of the singleton and controls the instantiation of the dataprovider thus avoiding 2 instances being created when a userccontrol is bound to the same dataprovider.

The next simplification is not to use dependency properties everywhere. In this way the data provider for a parent child combo box will look like:

WinLiveCatWizardDataProvider.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;

using PartnerRe.DOM.CatFocusClientFacade;
using PartnerRe.UTILS.LinqValueObjects.CatFocusDB;
using PartnerRe.UTILS.ValueObjects.CatFocusDB;

namespace PartnerRe.WPF.LiveCat
{

public class WinLiveCatWizardDataProvider
{
private PERILItem mySelectedPerilID;
private String _CatFocusSQLServer;
private int _USER_ID;

public bool IsInDesignMode
{
get
{
return DesignerProperties.GetIsInDesignMode(new DependencyObject());
}
}
public bool IsInRuntimeMode
{
get { return !IsInDesignMode; }
}
public bool IsDebug
{
get
{
#if DEBUG
return true;
#else
return false;
#endif
}
}
#if DEBUG
private bool _isInTestMode = false;
public bool IsInTestMode
{
get
{
return _isInTestMode;
}
set
{
if (_isInTestMode == value)
{
return;
}
_isInTestMode = value;
MakeData();
}
}
#endif
#region INOTIFYPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, args);
}
}

#endregion

public PERILItem MySelectedPerilID
{
get
{
return mySelectedPerilID;
}

set
{
if (value != mySelectedPerilID && value != null)
{
if (value.PERIL_ID == 1)
{
MyREGIONCollection.Add(new REGIONItem(1, "Region1"));
MyREGIONCollection.Add(new REGIONItem(2, "Region2"));
}
else if (value.PERIL_ID == 2)
{
MyREGIONCollection.Add(new REGIONItem(3, "Region3"));
MyREGIONCollection.Add(new REGIONItem(4, "Region4"));
}
}
mySelectedPerilID = value;
}
}

private ObservableCollection _myPERILCollection = new ObservableCollection();
public ObservableCollection MyPERILCollection
{
get
{
return _myPERILCollection;
}
}

public REGIONItem MySelectedREGIONID { get; set; }

private ObservableCollection _myREGIONCollection = new ObservableCollection();
public ObservableCollection MyREGIONCollection
{
get
{
return _myREGIONCollection;
}
}
private void MakeData()
{

if (_myPERILCollection.Count > 0)
{
_myPERILCollection.Clear();
}

#if DEBUG
if (IsInDesignMode IsInTestMode)
{
// TEST DATA FOR UI DESIGN
int PERIL_ID = 1;
String PERIL = "PERIL1";
PERILItem z = new PERILItem(PERIL_ID, PERIL);

_myPERILCollection.Add(new PERILItem(PERIL_ID, PERIL));
PERIL_ID = 2;
PERIL = "PERIL2";

_myPERILCollection.Add(new PERILItem(PERIL_ID, PERIL));
}
else
#endif
{
// LIVE DATA

int PERIL_ID = 1;
String PERIL = "PERIL1";
PERILItem z = new PERILItem(PERIL_ID, PERIL);

_myPERILCollection.Add(new PERILItem(PERIL_ID, PERIL));
PERIL_ID = 2;
PERIL = "PERIL2";
_myPERILCollection.Add(new PERILItem(PERIL_ID, PERIL));
}
}

public WinLiveCatWizardDataProvider(String CatFocusSQLServer, int USER_ID)
{
_USER_ID = USER_ID;
_CatFocusSQLServer = CatFocusSQLServer;
MakeData();
}
}
}

This dataprovider is referenced in the xaml (local:WinLiveCatWizardDataProvider x:Key="MyProvider") is no longer required.

The user control no longer has a dependency property to the dataprovider. It looks like:

local:CntLiveCatWizardStep1ChooseModel
x:Name="CntLiveCatWizardStep1ChooseModel1"
BtnBackClick="CntLiveCatWizardStep1ChooseModel1_BtnBackClick"

The data context of the control is set as follows:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = DataProvider;
CntLiveCatWizardStep1ChooseModel1.DataContext = DataProvider;
}

Then in the control we can bind directly to the data provider in the xaml

ComboBox Name="cmbPeril"
ItemsSource="{Binding MyPERILCollection}"
DisplayMemberPath = "PERIL"
SelectedValue="{Binding MySelectedPerilID}"
Height="23" Width="120"

In this way the only dependany properties I need are those handling the button click events eg

public static readonly RoutedEvent BtnNextClickEvent = EventManager.RegisterRoutedEvent("BtnNextClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CntLiveCatWizardStep1ChooseModel));
public event RoutedEventHandler BtnNextClick
{
add { this.AddHandler(BtnNextClickEvent, value); }
remove { this.RemoveHandler(BtnNextClickEvent, value); }
}

private void BtnNext_Click(object sender, RoutedEventArgs e)
{

RoutedEventArgs args = new RoutedEventArgs(BtnNextClickEvent);
RaiseEvent(args);
}