Sunday, November 14, 2010

WPF - Supporting Multiple DataSources for same View

I have been asked so many times about supporting multiple datasources for a single view. I have decided to write a separate post about it so that I could refer for the community. The issue is that WPF supports just one DataContext. Many a times we need to show data from different sources to the view.

Some purists might want to say that there should be one view models to the view. All these different datasources should be handled by the View models. The view should just know about its DataContext and bind its control with this DataContext. I agree to this view mostly but there are, sometimes, requirements in which we have to support multiple view models. So how should we bind these different View models to our views.

Since I have made a point now about the severity of the issue, I think that I can jump on the example of doing that. We are creating an example of a simple view in which we have to show CourseName and StudentName in a single TextBox. CourseNmae and StudentName would be from CourseVM and StudentVM respectively. As I discussed above that there can only be one DataContext, so we should be assigning one to the DataContext (CourseVM). We are instantiating the other as a resource to the View (StudentVM)

The definition of two view models are as follows:

CourseVM:
class CourseVM : DependencyObject
{
public static DependencyProperty CourseNameProperty =
DependencyProperty.Register("CourseName", typeof(string), typeof(CourseVM));
public string CourseName
{
get { return (string)GetValue(CourseNameProperty); }
set { SetValue(CourseNameProperty, value); }
}
}

StudentVM:
class StudentVM : DependencyObject
{
public static DependencyProperty StudentNameProperty =
DependencyProperty.Register("StudentName", typeof(string), typeof(CourseVM));
public string StudentName
{
get { return (string)GetValue(StudentNameProperty); }
set { SetValue(StudentNameProperty, value); }
}
}

We also define a Converter for combining StudentName and CourseName. This would be used for MultiBinding so we are defining it as MultiValueConverter. It has to implement IMultiValueConverter from System.Windows.Data namespace.

class CourseStudentConverter : IMultiValueConverter
{

#region IMultiValueConverter Members

public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
string CourseName = (string)values[0];
string StudentName = (string)values[1];

return string.Format("{0} : {1}", CourseName, StudentName);
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}

#endregion
}

In the following view, we are binding using the Converter defined above. We are also binding using StringFormat.
<Window x:Class="MultiBinding_DifferentVMs.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:MultiBinding_DifferentVMs"
Title="Window1" Height="300" Width="300">
<Window.DataContext>
<local:CourseVM CourseName="Neural Networks" />
</Window.DataContext>
<Window.Resources>
<local:StudentVM x:Key="Student" StudentName="Muhammad" />
<local:CourseStudentConverter x:Key="CSConverter" />
</Window.Resources>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Using StringFormat " />
<TextBox>
<TextBox.Text>
<MultiBinding StringFormat="{}{0} : {1}" >
<Binding Path="CourseName" />
<Binding Source="{StaticResource Student}" Path="StudentName" />
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
<Border Height="24" />
<StackPanel Orientation="Horizontal">
<Label Content="Using Converter " />
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource CSConverter}" >
<Binding Path="CourseName" />
<Binding Source="{StaticResource Student}" Path="StudentName" />
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
</StackPanel>
</Window>

When we run the application, the windows is loaded as follows:

No comments: