Monday, June 7, 2010

Bind a Silverlight DataGrid Column to the User Control DataContext

Silverlight Data and Controls Series

Background

I recently had to come up with a solution for a problem that was bothering me for some time. The issue I was how to access the user control’s DataContext from a Silverlight column. I was able to find the exact solution I was looking for by using the code in Dan Wahlin’s genius blog posting (http://weblogs.asp.net/dwahlin/archive/2009/08/20/creating-a-silverlight-datacontext-proxy-to-simplify-data-binding-in-nested-controls.aspx).

The specific issue was I needed to enable and disable specific columns in a grid based on the form’s state; and not the state of object bound to the.

Take a look at the code exerts from a xaml control below:

  • You will see there is a DataGrid bound to a list of Employees that is on my ViewModel.
  • I then added autocomplete column to the grid. I bounded the Text property to the Employee’ s EmployeeCode property.
  • I then tried to bind to the ViewModel’s IsEmployeesIsEnabled property to the IsEnabled property of autocomplete column.

That solution will never work. You will never get a compile or runtime error however because EmployeesIsEnabled is not a property of the Employee object in the row, the binding will never work.

<!--User control-->
<UserControl x:Class="OCS.Controls.Canvass.DetailsInfo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:local="clr-namespace:OCS.Converters" xmlns:vm="clr-namespace:OCS.ViewModels" d:DesignHeight="450" d:DesignWidth="1024" x:Name="DetailsInfoControl">

<!--Data Grid-->
<sdk:DataGrid AutoGenerateColumns="False" Height="240" Name="dgDetailsEmployees" Width="Auto" BorderThickness="0"
ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee, Mode=TwoWay}">

<!--Data Grid Column-->
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<sdk:AutoCompleteBox Name="acbEmployeeCode"
MinimumPrefixLength="0" MinimumPopulateDelay="0" IsTextCompletionEnabled="True" ItemsSource="{Binding EmployeeCodes, Source={StaticResource oLookups}}" ValueMemberBinding="{Binding Abbreviation}" Text="{Binding EmployeeCode, Mode=TwoWay, ValidatesOnDataErrors=True,NotifyOnValidationError=True}" IsEnabled="{Binding EmployeesIsEnabled}">

Solution

To solve this use the code in this blog (http://weblogs.asp.net/dwahlin/archive/2009/08/20/creating-a-silverlight-datacontext-proxy-to-simplify-data-binding-in-nested-controls.aspx). Here is the code so you do not have to go over there but thank you very much Nick!!!

using System.Windows.Controls;
using System.Windows.Data;

namespace JobPlan.Controls
{
public class DataContextProxy : FrameworkElement
{
public DataContextProxy()
{
this.Loaded += new RoutedEventHandler(DataContextProxy_Loaded);
}

void DataContextProxy_Loaded(object sender, RoutedEventArgs e)
{
Binding binding = new Binding();
if (!String.IsNullOrEmpty(BindingPropertyName))
{
binding.Path = new PropertyPath(BindingPropertyName);
}
binding.Source = this.DataContext;
binding.Mode = BindingMode;
this.SetBinding(DataContextProxy.DataSourceProperty, binding);
}

public Object DataSource
{
get { return (Object)GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}

public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(Object), typeof(DataContextProxy), null);


public string BindingPropertyName { get; set; }

public BindingMode BindingMode { get; set; }

}
}

Solution

Then make the following modifications:

  • I added a new resource to the xaml that creates the DataContextProxy.
  • Then I modified the IsEnabled property of the autocomplete column to be bound to EmployeesIsEnabled property of the ViewModel set to the user control’s DataContext.

Voila – it works – the column will be enabled and disabled based on the form state and not the grid row.

<UserControl x:Class="OCS.Controls.Canvass.DetailsInfo" xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml xmlns:d=http://schemas.microsoft.com/expression/blend/2008 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" xmlns:sdk=http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk xmlns:local="clr-namespace:OCS.Converters" xmlns:vm="clr-namespace:OCS.ViewModels" d:DesignHeight="450" d:DesignWidth="1024">

<UserControl.Resources><vm:DataContextProxy x:Key="dataContextProxy" /></UserControl.Resources>

<!--Data Grid-->
<sdk:DataGrid AutoGenerateColumns="False" Height="240" Name="dgDetailsEmployees" Width="Auto" BorderThickness="0"
ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee, Mode=TwoWay}">

<!--Data Grid Column-->
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<sdk:AutoCompleteBox Name="acbEmployeeCode"
MinimumPrefixLength="0" MinimumPopulateDelay="0" IsTextCompletionEnabled="True" ItemsSource="{Binding EmployeeCodes, Source={StaticResource oLookups}}" ValueMemberBinding="{Binding Abbreviation}" Text="{Binding EmployeeCode, Mode=TwoWay, ValidatesOnDataErrors=True,NotifyOnValidationError=True}" IsEnabled="{Binding Source={StaticResource dataContextProxy},Path=DataSource.EmployeesIsEnabled}">

No comments: