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 27

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!

 

27 Comments

  • Sörnt 31 May 2010
    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 2010
    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 2010
    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 2010
    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 2010

    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 2010
    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 2010
    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 2010
    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 2010
    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 2010
    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 2010
    Hi Duncan,

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

    Vlad
  • Rayne 08 Dec 2010
    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 2010
    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 2011
    seems the web developers cant widen the width more than 400 pixel of this blog?
  • Sukdeb 06 May 2011
    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 2011
    Thank you for this!!
  • SBP 18 Aug 2011
    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 2011
    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 2011
    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 2011
    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 2011
    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 2011
    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 2011
    capooo
  • veera 05 May 2012
    I am unable to understand the coding, can you post full coding with solution downloadable format.
  • Sakur 22 May 2012
    What should be in the event handlers? Can you please post the full code or at least explain what these event handlers should do?
  • Stu Farish 23 May 2012
    I've used this example to add the functionality to an app I'm writing. I have the binding working, I can step trhough it in the debugger and see Transfer() add each row to the target.
    What I haven't figured out how to do is access this data from my ViewModel class. For example, I populate a RadGridView with several files. I then ctrl-click to select some of them & click on a button that has a DelegateCommand to my ViewModel class. Inside that method I need to retrieve the selectedItems from the grid, but so far I haven't figured out how to do that. I thought I could do that in the MultiSelectBehavior class but so far no luck.
  • Stu Farish 23 May 2012
    I've used this example to add the functionality to an app I'm writing. I have the binding working, I can step trhough it in the debugger and see Transfer() add each row to the target.
    What I haven't figured out how to do is access this data from my ViewModel class. For example, I populate a RadGridView with several files. I then ctrl-click to select some of them & click on a button that has a DelegateCommand to my ViewModel class. Inside that method I need to retrieve the selectedItems from the grid, but so far I haven't figured out how to do that. I thought I could do that in the MultiSelectBehavior class but so far no luck.

Add comment

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