Very often you need “two-way” binding between some UI component with multiple selection and a data context property for selected items – let’s say RadGridView and the following model:
XAML
<telerik:RadGridView ItemsSource="{Binding Data}" SelectionMode="Extended" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
…
C#
public class MyDataContext : INotifyPropertyChanged
{
... ObservableCollection<MyObject> _SelectedItems;
public ObservableCollection<MyObject> SelectedItems
{
get
{
if (_SelectedItems == null)
{
_SelectedItems = new ObservableCollection<MyObject>();
}
return _SelectedItems;
}
}
MyObject _SelectedItem;
public MyObject SelectedItem
{
get
{
return _SelectedItem;
}
set
{
if (_SelectedItem != value)
{
_SelectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
...
}
You cannot however use traditional two-way binding for the grid SelectedItems property since this property is read-only. Now what to do?!?! …. Blend behaviors to the rescue!
Using System.Windows.Interactivity.Behavior<T> you can create very easily synchronization for both collections:
public class MyMultiSelectBehavior : Behavior<RadGridView>
{
private RadGridView Grid
{
get
{
return AssociatedObject as RadGridView;
}
}
public INotifyCollectionChanged SelectedItems
{
get { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(INotifyCollectionChanged), typeof(MyMultiSelectBehavior), new PropertyMetadata(OnSelectedItemsPropertyChanged));
private static void OnSelectedItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
var collection = args.NewValue as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged += ((MyMultiSelectBehavior)target).ContextSelectedItems_CollectionChanged;
}
}
protected override void OnAttached()
{
base.OnAttached();
Grid.SelectedItems.CollectionChanged += GridSelectedItems_CollectionChanged;
}
...
}
And here is how to bind all these in pure XAML:
WPF
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow">
<Window.Resources>
<local:MyDataContext x:Key="context"/>
</Window.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource context}">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<telerik:RadGridView ItemsSource="{Binding Data}" SelectionMode="Extended" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<i:Interaction.Behaviors>
<local:MyMultiSelectBehavior SelectedItems="{Binding SelectedItems, Source={StaticResource context}}" />
</i:Interaction.Behaviors>
</telerik:RadGridView>
<StackPanel Orientation="Vertical" Grid.Column="1">
<telerik:RadButton Content="Clear selected items" Click="Button_Click" />
<TextBlock Text="Selected items:"/>
<ListBox ItemsSource="{Binding SelectedItems}" DisplayMemberPath="ID" />
</StackPanel>
</Grid>
</Window>
Silverlight
<UserControl x:Class="SilverlightApplication1.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:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:SilverlightApplication1" mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<local:MyDataContext x:Key="context"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource context}">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<telerik:RadGridView ItemsSource="{Binding Data}" SelectionMode="Extended" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<i:Interaction.Behaviors>
<local:MyMultiSelectBehavior SelectedItems="{Binding SelectedItems, Source={StaticResource context}}" />
</i:Interaction.Behaviors>
</telerik:RadGridView>
<StackPanel Orientation="Vertical" Grid.Column="1">
<telerik:RadButton Content="Clear selected items" Click="Button_Click" />
<TextBlock Text="Selected items:"/>
<ListBox ItemsSource="{Binding SelectedItems}" DisplayMemberPath="ID" />
</StackPanel>
</Grid>
</UserControl>
Enjoy!