Jaký je problém v následujícím příkladu XAML kódu Silverlight aplikace?
<UserControl x:Class="SilverlightApplication.MainPage"
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"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:local="clr-namespace:SilverlightApplication"
xmlns:viewModels="clr-namespace:SilverlightApplication.ViewModels"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<viewModels:MainViewModel x:Key="ViewModel" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource ViewModel}}">
<sdk:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding List}">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Binding="{Binding StringValue}" Header="{Binding HeaderText, Source={StaticResource ViewModel}}" Width="110" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>
</UserControl>
Hlavní okno této Silverlight aplikace obsahuje control DataGrid s jedním sloupcem. Pro nastavení textu nadpisu tohoto sloupce (vlastnost Header) je použit data binding, (jeho výraz se odkazuje na vlastnost HeaderText objektu MainViewModel). V Silverlight ale nejsou (na rozdíl od WPF) vlastnosti ve třídě DataGridColumn implementovány jako DependencyProperty, a tak nelze použít data binding pro nastavení jejich hodnot.
Asi nejlepší řešení toho problému, je vytvořit třídu, kde budou attached vlastnosti, které budou nastavovat původní vlastnosti sloupce datagridu. Třídu nazveme DataGridColumnBindingHelper a naimplementujeme do ní vlastnosti Header, Visibility a IsReadOnly (předpokládám, že ostatní vlastnosti původního DataGridColumn nebudou potřeba nastavovat pomoci binding). Kód třídy je následující:
/// <summary>
/// Helper class to set DataGridColumn properties via binding.
/// </summary>
public class DataGridColumnBindingHelper
{
/// <summary>
/// Header DependencyProperty
/// </summary>
public static readonly DependencyProperty HeaderProperty = DependencyProperty.RegisterAttached("Header", typeof(object), typeof(DataGridColumnBindingHelper), new PropertyMetadata(null, OnHeaderPropertyChanged));
/// <summary>
/// Header Property getter
/// </summary>
public static object GetHeader(DependencyObject source)
{
return (object)source.GetValue(DataGridColumnBindingHelper.HeaderProperty);
}
/// <summary>
/// Header Property setter
/// </summary>
public static void SetHeader(DependencyObject target, object value)
{
target.SetValue(DataGridColumnBindingHelper.HeaderProperty, value);
}
private static void OnHeaderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var column = d as DataGridColumn;
if (column == null)
{
return;
}
column.Header = e.NewValue;
}
/// <summary>
/// Visibility DependencyProperty
/// </summary>
public static readonly DependencyProperty VisibilityProperty = DependencyProperty.RegisterAttached("Visibility", typeof(Visibility), typeof(DataGridColumnBindingHelper), new PropertyMetadata(Visibility.Visible, OnVisibilityPropertyChanged));
/// <summary>
/// Visibility Property getter
/// </summary>
public static Visibility GetVisibility(DependencyObject source)
{
return (Visibility)source.GetValue(DataGridColumnBindingHelper.VisibilityProperty);
}
/// <summary>
/// Visibility Property setter
/// </summary>
public static void SetVisibility(DependencyObject target, Visibility value)
{
target.SetValue(DataGridColumnBindingHelper.VisibilityProperty, value);
}
private static void OnVisibilityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var column = d as DataGridColumn;
if (column == null)
{
return;
}
column.Visibility = (Visibility)e.NewValue;
}
/// <summary>
/// IsReadOnly DependencyProperty
/// </summary>
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.RegisterAttached("IsReadOnly", typeof(bool), typeof(DataGridColumnBindingHelper), new PropertyMetadata(false, OnIsReadOnlyPropertyChanged));
/// <summary>
/// IsReadOnly Property getter
/// </summary>
public static bool GetIsReadOnly(DependencyObject source)
{
return (bool)source.GetValue(DataGridColumnBindingHelper.IsReadOnlyProperty);
}
/// <summary>
/// IsReadOnly Property setter
/// </summary>
public static void SetIsReadOnly(DependencyObject target, Visibility value)
{
target.SetValue(DataGridColumnBindingHelper.IsReadOnlyProperty, value);
}
private static void OnIsReadOnlyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var column = d as DataGridColumn;
if (column == null)
{
return;
}
column.IsReadOnly = Convert.ToBoolean(e.NewValue);
}
}
Celou třídu je také možné stáhnout zde. Její použití bude následující:
<sdk:DataGridTextColumn Binding="{Binding StringValue}" local:DataGridColumnBindingHelper.Header="{Binding HeaderText, Source={StaticResource ViewModel}}" Width="110" />
A nyní je už správně text sloupce načten z našeho ViewModelu.
Pozor ale při tvorbě binding výrazů pro nastavení těchto námi definovaných vlastností sloupců DataGridu. Protože sloupce DataGridu nepřebírají DataContext od DataGridu (a také nejsou součástí visual tree), nelze na nich použít RelativeSource-binding a ElementName-binding (*) (toto platí pro Silverlight i WPF).
Pokud potřebujeme text hlavičky sloupce nastavit z nějakého Resource pro lokalizaci, tak nám tento problém nevadí, musíme s tím ale počítat pokud se potřebujeme odkázat například při použití MVVM na vlastnost ViewModelu (nejčastěji pro nastavení textu nebo Visibility sloupce). V uvedeném příkladu se na ViewModel proto odkazuje pomoci definovaného StaticResource.
Více jak řešit binding na sloupcích datagridu je možné najít zde, zde nebo zde.
(*) ElementName-binding vrací chybu “Cannot find governing FrameworkElement or FrameworkContentElement for target element”.