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!

 


About the Author

Vladimir Enchev

is Director of Engineering, Native Mobile UI & Frameworks

Comments