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!
Vladimir Enchev is Director of Engineering, Native Mobile UI & Frameworks