How To: Synchronize your UI selected items with your data context using MVVM and Blend behaviors for Silverlight and WPF

Monday, May 31, 2010 by Vladimir Enchev | Comments 23

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!

 

23 Comments

  • Sörnt 31 May
    Can you please do me favor?

    a) Please format the the source code so that it is readable, half of the code is truncated. It will be nice if the page layout for these posts much wieder. Most of us developers have 22"+ displays, there is no need to limit the page wide to something like 400px.

    b) You didn't explain why a behavior is magically able to change a readonly collection. That may be because of a).

    Kind regards,
    Sörnt
  • Vlad 01 Jun
    Sörnt,

    You can check the attached applications however I'm afraid that you will not find any magic - just two additional methods to copy items from one collection to another. The real magic is already posted:
    <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>
    I'll talk to our web team to see what we can do with our blogs layout.

    Kind regards,
    Vlad
  • Remco Ros 04 Jun
    Hi,

    I have this behavior in an external (common) assembly.

    I just upgraded to the latest Silverlight controls (SP1) now MSBuild fails with:

    Task "ValidateXaml" (TaskId:164)
    E:\Projects\PLTC2\exactexport\src\Pltc.ExactExport.UI\Modules\SearchInvoices\Vi
    ews\SearchResults.xaml(86,34,86,34): error : The tag 'SelectedItemsBehavior' do
    es not exist in XML namespace 'http://www.mobielbereikbaar.nl'. [E:\Projects\PL
    TC2\exactexport\src\Pltc.ExactExport.UI\Pltc.ExactExport.UI.csproj]

    It always used to work !?

    When I include the Behavior in the Silverlight project (where the xaml lives) it compiles (and works) fine..

    Remco
  • Varsha 30 Jun
    Hi Vlad,

    I wish to implement the above behavior for Grid which is inside a RadDropDown which is inside the edit template of GridView Column as below

     

     

     

    <telerik:GridViewDataColumn UniqueName="IncentiveModel" Header="Incentive Model">

     

     

     

     

    <telerik:GridViewDataColumn.CellTemplate>

     

     

     

     

    <DataTemplate>

     

     

     

     

    <TextBlock Name="lblIncentiveDesc" Text="{Binding IncentiveModel}" VerticalAlignment="Center"></TextBlock>

     

     

     

     

    </DataTemplate>

     

     

     

     

    </telerik:GridViewDataColumn.CellTemplate>

     

     

     

     

    <telerik:GridViewDataColumn.CellEditTemplate>

     

     

     

     

    <DataTemplate>

     

     

     

     

    <telerik:RadDropDownButton x:Name="cmbIncentiveDescEdit" Height="25" Content="{Binding IncentiveModel}" Padding="5" Width="187" VerticalAlignment="Center">

     

     

     

     

    <telerik:RadDropDownButton.DropDownContent>

     

     

     

     

    <telerik:RadGridView SelectionChanged="dgIncentiveDescEdit_SelectionChanged" x:Name="dgIncentiveDescEdit" ItemsSource="{Binding AvailableIncentiveDescriptions}"

     

     

     

    AutoGenerateColumns="False" IsFilteringAllowed="False" ShowGroupPanel="False" IsSynchronizedWithCurrentItem="False"

     

     

     

    CanUserResizeColumns="False" CanUserDeleteRows="False" CanUserInsertRows="False"

     

     

     

    ShowInsertRow="False" SelectionMode="Extended" RowIndicatorVisibility="Collapsed"

     

     

     

    CanUserReorderColumns="False" CanUserSortColumns="True" MinWidth="187" MinHeight="250" ShowColumnHeaders="False">

     

     

     

     

    <telerik:RadGridView.Columns>

     

     

     

     

    <telerik:GridViewSelectColumn></telerik:GridViewSelectColumn>

     

     

     

     

    <telerik:GridViewDataColumn IsReadOnly="True" Width="165" DataMemberBinding="{Binding IncentiveDescription}" ></telerik:GridViewDataColumn>

     

     

     

     

    </telerik:RadGridView.Columns>

     

     

     

     

    <i:Interaction.Behaviors>

     

     

     

     

    <my:MultiSelectBehavior SelectedItems="{Binding IncentivesDetails}" />

     

     

     

     

    </i:Interaction.Behaviors>

     

     

     

     

    </telerik:RadGridView>

     

     

     

     

    </telerik:RadDropDownButton.DropDownContent>

     

     

     

     

    </telerik:RadDropDownButton>

     

     

     

     

    </DataTemplate>

     

     

     

     

    </telerik:GridViewDataColumn.CellEditTemplate>

     

     

     

     

    </telerik:GridViewDataColumn>

     




    I have applied the behaviour still the selected items are not shown when i edit the cell.
    Please advice
  • Henrique 08 Jul

    Vladimir, thank you!

    Your code is working fine. If I select one or more records, the SelectedItems are being updated.
    But I want to load the SelectedItems from ViewModel. 
    When the form opens, all items needed to be selected.
    Here is my code:

    public ObservableCollection<MyObject> SelectedItems  
    {  
        get 
        {  
            if (_SelectedItems == null)  
                _SelectedItems = new ObservableCollection<MyObject>(Data);  
            return _SelectedItems;  
        }  

    Best regards,
    Henrique

  • Yvan 17 Sep
    Hi,
    I have tried your example. It worked fine but... the code won't work when I initialise the DataContext in MainPage code behind. (My changed is in bold)...

    public MainPage()
    {
       InitializeComponent();
       LayoutRoot.DataContext = new MyDataContext();
    }

    And the MainPage.xaml code:
    <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 x:Name="LayoutRoot" Background="White">
            <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}}" />-->
                    <local:MyMultiSelectBehavior SelectedItems="{Binding DataContext.SelectedItems, ElementName=LayoutRoot}" />
                </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 DataContext.SelectedItems}" DisplayMemberPath="ID" />
            </StackPanel>
        </Grid>
    </UserControl>

    Thanks for reply!




  • Yvan 17 Sep
    Hi,
    I have tried your example. It worked fine but... the code won't work when I initialise the DataContext in MainPage code behind. (My changed is in bold)...

    public MainPage()
    {
       InitializeComponent();
       LayoutRoot.DataContext = new MyDataContext();
    }

    And the MainPage.xaml code:
    <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 x:Name="LayoutRoot" Background="White">
            <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}}" />-->
                    <local:MyMultiSelectBehavior SelectedItems="{Binding DataContext.SelectedItems, ElementName=LayoutRoot}" />
                </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 DataContext.SelectedItems}" DisplayMemberPath="ID" />
            </StackPanel>
        </Grid>
    </UserControl>

    Thanks for reply!




  • Ben 20 Oct
    Hi Vlad...

    I'm having the same issue as Yvan : unable to bind to the SelectedItems property of the DataContext when it is injected (as your ViewModel when you do Dependency Injection)...

    Would you be kind enough to make it work when the context is not a resource...

    Thanks in advance!
  • Vlad 22 Oct
    Hi Ben

    Here is an example how to do the same with code:

    var selectBehavior = new MyMultiSelectBehavior(); 
     
    BindingOperations.SetBinding(selectBehavior, MyMultiSelectBehavior.SelectedItemsProperty, 
     new Binding("SelectedItems") { Source = this.LayoutRoot.DataContext }); 
     
    Interaction.GetBehaviors(this.myGrid).Add(selectBehavior); 
     
  • Duncan Bayne 04 Nov
    Vlad,

    Thanks for the informative post.  I'm curious though: why is the SelectedItems property read-only?  

    The canonical approach to this problem is, as you say, by creating a two-way binding.  Why has Telerik decided to make this standard approach impossible, thereby forcing developers to write dozens of lines of boilerplate code?

    Yours,
    Duncan Bayne
  • Vlad 05 Nov
    Hi Duncan,

    Actually this is the standard approach. You can check for reference other standard Silverlight controls with multiple selection. 

    Vlad
  • Rayne 08 Dec
    Vlad, I'm having the same issue as Henrique. How do I load the selectedItems from the ViewModel so that at startup all selected items are selected?

    I couldn't even get that to work with your sample project. Thanks.
  • Vlad 08 Dec
    Here is an example how to do the same from code:

    var selectBehavior = new MyMultiSelectBehavior(); 
    BindingOperations.SetBinding(selectBehavior, MyMultiSelectBehavior.SelectedItemsProperty, 
     new Binding("SelectedItems") { Source = this.LayoutRoot.DataContext }); 
    Interaction.GetBehaviors(this.myGrid).Add(selectBehavior); 
  • Ridiculous 23 Mar
    seems the web developers cant widen the width more than 400 pixel of this blog?
  • Sukdeb 06 May
    Hi,

    I was trying to impliment the above behavior with Silverlight 4 . It is working fine functionally. But when run the application in Google Chrome it is showing the following error in Javascript console.

    Uncaught Error: NOT_FOUND_ERR: DOM Exception 8

    After loading data in the grid, when scrolling the data using Up and Down key, the error getting replicated.


    Thanks
    Sukdeb
  • Jerry T. 11 Aug
    Thank you for this!!
  • SBP 18 Aug
    I am uisng the above aproach to achieve the selectedItems binding but I seeem to be facing a problem. I need to action some buttons (set them to enable/disable) stuff when the SelectedItems property changes in the viewmodel. So I have the view model proerty like
    ObservableCollection<MyObject> _SelectedItems;        public ObservableCollection<MyObject> SelectedItems        {            get            {                if (_SelectedItems == null)                {                    _SelectedItems = new ObservableCollection<MyObject>();                }                return _SelectedItems;            }
                set            {                _SelectedItems = value;
                    //code to enable - disble buttons
                    this
    .setButtonsEnabled();             }        }
    But with the behaviour added the selected items gets correctly reflected on to my VM property SelectedItems without invoking the SET property and hence the code to enable and disable the buttons is unreachable. Is there a better way to achive it or anything that can help me call the SET property?
  • SBP 18 Aug
    Posting the above query with some formatting
    I am uisng the above aproach to achieve the selectedItems binding but I seeem to be facing a problem. I need to action some buttons (set them to enable/disable) stuff when the SelectedItems property changes in the
    viewmodel. So I have the view model property like
    ObservableCollection<MyObject> _SelectedItems;
    public ObservableCollection<MyObject> SelectedItems
    {
    get
    {
    if (_SelectedItems == null){_SelectedItems = new ObservableCollection<MyObject>();}
    return _SelectedItems;
    }
    set
    {
    _SelectedItems = value;
    //code to enable - disble buttons
    this.setButtonsEnabled();
    }
    }
    But with the behaviour added the selected items gets correctly reflected on to my VM property SelectedItems without invoking the SET property and hence the code to enable and disable the buttons is unreachable. Is there a better way to achive it or anything that can help me call the SET property?
  • SBP 18 Aug
    Posting the above query with some formatting
    I am uisng the above aproach to achieve the selectedItems binding but I seeem to be facing a problem. I need to action some buttons (set them to enable/disable) stuff when the SelectedItems property changes in the
    viewmodel. So I have the view model property like
    ObservableCollection<MyObject> _SelectedItems;
    public ObservableCollection<MyObject> SelectedItems
    {
    get
    {
    if (_SelectedItems == null){_SelectedItems = new ObservableCollection<MyObject>();}
    return _SelectedItems;
    }
    set
    {
    _SelectedItems = value;
    //code to enable - disble buttons
    this.setButtonsEnabled();
    }
    }
    But with the behaviour added the selected items gets correctly reflected on to my VM property SelectedItems without invoking the SET property and hence the code to enable and disable the buttons is unreachable. Is there a better way to achive it or anything that can help me call the SET property?
  • SBP 18 Aug
    Posting the above query with some formatting
    I am uisng the above aproach to achieve the selectedItems binding but I seeem to be facing a problem. I need to action some buttons (set them to enable/disable) stuff when the SelectedItems property changes in the
    viewmodel. So I have the view model property like
    ObservableCollection<MyObject> _SelectedItems;
    public ObservableCollection<MyObject> SelectedItems
    {
    get
    {
    if (_SelectedItems == null){_SelectedItems = new ObservableCollection<MyObject>();}
    return _SelectedItems;
    }
    set
    {
    _SelectedItems = value;
    //code to enable - disble buttons
    this.setButtonsEnabled();
    }
    }
    But with the behaviour added the selected items gets correctly reflected on to my VM property SelectedItems without invoking the SET property and hence the code to enable and disable the buttons is unreachable. Is there a better way to achive it or anything that can help me call the SET property?
  • SBP 18 Aug
    Posting the above query with some formatting
    I am uisng the above aproach to achieve the selectedItems binding but I seeem to be facing a problem. I need to action some buttons (set them to enable/disable) stuff when the SelectedItems property changes in the
    viewmodel. So I have the view model property like
    ObservableCollection<MyObject> _SelectedItems;
    public ObservableCollection<MyObject> SelectedItems
    {
    get
    {
    if (_SelectedItems == null){_SelectedItems = new ObservableCollection<MyObject>();}
    return _SelectedItems;
    }
    set
    {
    _SelectedItems = value;
    //code to enable - disble buttons
    this.setButtonsEnabled();
    }
    }
    But with the behaviour added the selected items gets correctly reflected on to my VM property SelectedItems without invoking the SET property and hence the code to enable and disable the buttons is unreachable. Is there a better way to achive it or anything that can help me call the SET property?
  • Sanjay Kumar 24 Aug
    How can I fire the Set{} code of the SelectedItems, I need to publish an event to the EventAggregator that I use in a PRISM environment that we have created.
  • franco 15 Nov
    capooo

Add comment

  1. Formatting options
       
     
     
     
     
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)