Bind to SelectedItems from DataGrid or ListBox in MVVM

63,210

Solution 1

This will work:

MultiSelectorBehaviours.vb

Imports System.Collections
Imports System.Windows
Imports System.Windows.Controls.Primitives
Imports System.Windows.Controls
Imports System

Public NotInheritable Class MultiSelectorBehaviours
    Private Sub New()
    End Sub

    Public Shared ReadOnly SynchronizedSelectedItems As DependencyProperty = _
        DependencyProperty.RegisterAttached("SynchronizedSelectedItems", GetType(IList), GetType(MultiSelectorBehaviours), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnSynchronizedSelectedItemsChanged)))

    Private Shared ReadOnly SynchronizationManagerProperty As DependencyProperty = DependencyProperty.RegisterAttached("SynchronizationManager", GetType(SynchronizationManager), GetType(MultiSelectorBehaviours), New PropertyMetadata(Nothing))

    ''' <summary>
    ''' Gets the synchronized selected items.
    ''' </summary>
    ''' <param name="dependencyObject">The dependency object.</param>
    ''' <returns>The list that is acting as the sync list.</returns>
    Public Shared Function GetSynchronizedSelectedItems(ByVal dependencyObject As DependencyObject) As IList
        Return DirectCast(dependencyObject.GetValue(SynchronizedSelectedItems), IList)
    End Function

    ''' <summary>
    ''' Sets the synchronized selected items.
    ''' </summary>
    ''' <param name="dependencyObject">The dependency object.</param>
    ''' <param name="value">The value to be set as synchronized items.</param>
    Public Shared Sub SetSynchronizedSelectedItems(ByVal dependencyObject As DependencyObject, ByVal value As IList)
        dependencyObject.SetValue(SynchronizedSelectedItems, value)
    End Sub

    Private Shared Function GetSynchronizationManager(ByVal dependencyObject As DependencyObject) As SynchronizationManager
        Return DirectCast(dependencyObject.GetValue(SynchronizationManagerProperty), SynchronizationManager)
    End Function

    Private Shared Sub SetSynchronizationManager(ByVal dependencyObject As DependencyObject, ByVal value As SynchronizationManager)
        dependencyObject.SetValue(SynchronizationManagerProperty, value)
    End Sub

    Private Shared Sub OnSynchronizedSelectedItemsChanged(ByVal dependencyObject As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        If e.OldValue IsNot Nothing Then
            Dim synchronizer As SynchronizationManager = GetSynchronizationManager(dependencyObject)
            synchronizer.StopSynchronizing()

            SetSynchronizationManager(dependencyObject, Nothing)
        End If

        Dim list As IList = TryCast(e.NewValue, IList)
        Dim selector As Selector = TryCast(dependencyObject, Selector)

        ' check that this property is an IList, and that it is being set on a ListBox
        If list IsNot Nothing AndAlso selector IsNot Nothing Then
            Dim synchronizer As SynchronizationManager = GetSynchronizationManager(dependencyObject)
            If synchronizer Is Nothing Then
                synchronizer = New SynchronizationManager(selector)
                SetSynchronizationManager(dependencyObject, synchronizer)
            End If

            synchronizer.StartSynchronizingList()
        End If
    End Sub

    ''' <summary>
    ''' A synchronization manager.
    ''' </summary>
    Private Class SynchronizationManager
        Private ReadOnly _multiSelector As Selector
        Private _synchronizer As TwoListSynchronizer

        ''' <summary>
        ''' Initializes a new instance of the <see cref="SynchronizationManager"/> class.
        ''' </summary>
        ''' <param name="selector">The selector.</param>
        Friend Sub New(ByVal selector As Selector)
            _multiSelector = selector
        End Sub

        ''' <summary>
        ''' Starts synchronizing the list.
        ''' </summary>
        Public Sub StartSynchronizingList()
            Dim list As IList = GetSynchronizedSelectedItems(_multiSelector)

            If list IsNot Nothing Then
                _synchronizer = New TwoListSynchronizer(GetSelectedItemsCollection(_multiSelector), list)
                _synchronizer.StartSynchronizing()
            End If
        End Sub

        ''' <summary>
        ''' Stops synchronizing the list.
        ''' </summary>
        Public Sub StopSynchronizing()
            _synchronizer.StopSynchronizing()
        End Sub

        Public Shared Function GetSelectedItemsCollection(ByVal selector As Selector) As IList
            If TypeOf selector Is MultiSelector Then
                Return TryCast(selector, MultiSelector).SelectedItems
            ElseIf TypeOf selector Is ListBox Then
                Return TryCast(selector, ListBox).SelectedItems
            Else
                Throw New InvalidOperationException("Target object has no SelectedItems property to bind.")
            End If
        End Function

    End Class
End Class

IListItemConverter.vb

''' <summary>
''' Converts items in the Master list to Items in the target list, and back again.
''' </summary>
Public Interface IListItemConverter
    ''' <summary>
    ''' Converts the specified master list item.
    ''' </summary>
    ''' <param name="masterListItem">The master list item.</param>
    ''' <returns>The result of the conversion.</returns>
    Function Convert(ByVal masterListItem As Object) As Object

    ''' <summary>
    ''' Converts the specified target list item.
    ''' </summary>
    ''' <param name="targetListItem">The target list item.</param>
    ''' <returns>The result of the conversion.</returns>
    Function ConvertBack(ByVal targetListItem As Object) As Object
End Interface

TwoListSynchronizer.vb

Imports System.Collections
Imports System.Collections.Specialized
Imports System.Linq
Imports System.Windows

''' <summary>
''' Keeps two lists synchronized. 
''' </summary>
Public Class TwoListSynchronizer
    Implements IWeakEventListener

    Private Shared ReadOnly DefaultConverter As IListItemConverter = New DoNothingListItemConverter()
    Private ReadOnly _masterList As IList
    Private ReadOnly _masterTargetConverter As IListItemConverter
    Private ReadOnly _targetList As IList


    ''' <summary>
    ''' Initializes a new instance of the <see cref="TwoListSynchronizer"/> class.
    ''' </summary>
    ''' <param name="masterList">The master list.</param>
    ''' <param name="targetList">The target list.</param>
    ''' <param name="masterTargetConverter">The master-target converter.</param>
    Public Sub New(ByVal masterList As IList, ByVal targetList As IList, ByVal masterTargetConverter As IListItemConverter)
        _masterList = masterList
        _targetList = targetList
        _masterTargetConverter = masterTargetConverter
    End Sub

    ''' <summary>
    ''' Initializes a new instance of the <see cref="TwoListSynchronizer"/> class.
    ''' </summary>
    ''' <param name="masterList">The master list.</param>
    ''' <param name="targetList">The target list.</param>
    Public Sub New(ByVal masterList As IList, ByVal targetList As IList)
        Me.New(masterList, targetList, DefaultConverter)
    End Sub

    Private Delegate Sub ChangeListAction(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))

    ''' <summary>
    ''' Starts synchronizing the lists.
    ''' </summary>
    Public Sub StartSynchronizing()
        ListenForChangeEvents(_masterList)
        ListenForChangeEvents(_targetList)

        ' Update the Target list from the Master list
        SetListValuesFromSource(_masterList, _targetList, AddressOf ConvertFromMasterToTarget)

        ' In some cases the target list might have its own view on which items should included:
        ' so update the master list from the target list
        ' (This is the case with a ListBox SelectedItems collection: only items from the ItemsSource can be included in SelectedItems)
        If Not TargetAndMasterCollectionsAreEqual() Then
            SetListValuesFromSource(_targetList, _masterList, AddressOf ConvertFromTargetToMaster)
        End If
    End Sub

    ''' <summary>
    ''' Stop synchronizing the lists.
    ''' </summary>
    Public Sub StopSynchronizing()
        StopListeningForChangeEvents(_masterList)
        StopListeningForChangeEvents(_targetList)
    End Sub

    ''' <summary>
    ''' Receives events from the centralized event manager.
    ''' </summary>
    ''' <param name="managerType">The type of the <see cref="T:System.Windows.WeakEventManager"/> calling this method.</param>
    ''' <param name="sender">Object that originated the event.</param>
    ''' <param name="e">Event data.</param>
    ''' <returns>
    ''' true if the listener handled the event. It is considered an error by the <see cref="T:System.Windows.WeakEventManager"/> handling in WPF to register a listener for an event that the listener does not handle. Regardless, the method should return false if it receives an event that it does not recognize or handle.
    ''' </returns>
    Public Function ReceiveWeakEvent(ByVal managerType As Type, ByVal sender As Object, ByVal e As EventArgs) As Boolean Implements System.Windows.IWeakEventListener.ReceiveWeakEvent
        HandleCollectionChanged(TryCast(sender, IList), TryCast(e, NotifyCollectionChangedEventArgs))

        Return True
    End Function

    ''' <summary>
    ''' Listens for change events on a list.
    ''' </summary>
    ''' <param name="list">The list to listen to.</param>
    Protected Sub ListenForChangeEvents(ByVal list As IList)
        If TypeOf list Is INotifyCollectionChanged Then
            CollectionChangedEventManager.AddListener(TryCast(list, INotifyCollectionChanged), Me)
        End If
    End Sub

    ''' <summary>
    ''' Stops listening for change events.
    ''' </summary>
    ''' <param name="list">The list to stop listening to.</param>
    Protected Sub StopListeningForChangeEvents(ByVal list As IList)
        If TypeOf list Is INotifyCollectionChanged Then
            CollectionChangedEventManager.RemoveListener(TryCast(list, INotifyCollectionChanged), Me)
        End If
    End Sub

    Private Sub AddItems(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
        Dim itemCount As Integer = e.NewItems.Count

        For i As Integer = 0 To itemCount - 1
            Dim insertionPoint As Integer = e.NewStartingIndex + i

            If insertionPoint > list.Count Then
                list.Add(converter(e.NewItems(i)))
            Else
                list.Insert(insertionPoint, converter(e.NewItems(i)))
            End If
        Next
    End Sub

    Private Function ConvertFromMasterToTarget(ByVal masterListItem As Object) As Object
        Return If(_masterTargetConverter Is Nothing, masterListItem, _masterTargetConverter.Convert(masterListItem))
    End Function

    Private Function ConvertFromTargetToMaster(ByVal targetListItem As Object) As Object
        Return If(_masterTargetConverter Is Nothing, targetListItem, _masterTargetConverter.ConvertBack(targetListItem))
    End Function

    Private Sub HandleCollectionChanged(ByVal sender As Object, ByVal e As NotifyCollectionChangedEventArgs)
        Dim sourceList As IList = TryCast(sender, IList)

        Select Case e.Action
            Case NotifyCollectionChangedAction.Add
                PerformActionOnAllLists(AddressOf AddItems, sourceList, e)
                Exit Select
            Case NotifyCollectionChangedAction.Move
                PerformActionOnAllLists(AddressOf MoveItems, sourceList, e)
                Exit Select
            Case NotifyCollectionChangedAction.Remove
                PerformActionOnAllLists(AddressOf RemoveItems, sourceList, e)
                Exit Select
            Case NotifyCollectionChangedAction.Replace
                PerformActionOnAllLists(AddressOf ReplaceItems, sourceList, e)
                Exit Select
            Case NotifyCollectionChangedAction.Reset
                UpdateListsFromSource(TryCast(sender, IList))
                Exit Select
            Case Else
                Exit Select
        End Select
    End Sub

    Private Sub MoveItems(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
        RemoveItems(list, e, converter)
        AddItems(list, e, converter)
    End Sub

    Private Sub PerformActionOnAllLists(ByVal action As ChangeListAction, ByVal sourceList As IList, ByVal collectionChangedArgs As NotifyCollectionChangedEventArgs)
        If sourceList Is _masterList Then
            PerformActionOnList(_targetList, action, collectionChangedArgs, AddressOf ConvertFromMasterToTarget)
        Else
            PerformActionOnList(_masterList, action, collectionChangedArgs, AddressOf ConvertFromTargetToMaster)
        End If
    End Sub

    Private Sub PerformActionOnList(ByVal list As IList, ByVal action As ChangeListAction, ByVal collectionChangedArgs As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
        StopListeningForChangeEvents(list)
        action(list, collectionChangedArgs, converter)
        ListenForChangeEvents(list)
    End Sub

    Private Sub RemoveItems(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
        Dim itemCount As Integer = e.OldItems.Count

        ' for the number of items being removed, remove the item from the Old Starting Index
        ' (this will cause following items to be shifted down to fill the hole).
        For i As Integer = 0 To itemCount - 1
            list.RemoveAt(e.OldStartingIndex)
        Next
    End Sub

    Private Sub ReplaceItems(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
        RemoveItems(list, e, converter)
        AddItems(list, e, converter)
    End Sub

    Private Sub SetListValuesFromSource(ByVal sourceList As IList, ByVal targetList As IList, ByVal converter As Converter(Of Object, Object))
        StopListeningForChangeEvents(targetList)

        targetList.Clear()

        For Each o As Object In sourceList
            targetList.Add(converter(o))
        Next

        ListenForChangeEvents(targetList)
    End Sub

    Private Function TargetAndMasterCollectionsAreEqual() As Boolean
        Return _masterList.Cast(Of Object)().SequenceEqual(_targetList.Cast(Of Object)().[Select](Function(item) ConvertFromTargetToMaster(item)))
    End Function

    ''' <summary>
    ''' Makes sure that all synchronized lists have the same values as the source list.
    ''' </summary>
    ''' <param name="sourceList">The source list.</param>
    Private Sub UpdateListsFromSource(ByVal sourceList As IList)
        If sourceList Is _masterList Then
            SetListValuesFromSource(_masterList, _targetList, AddressOf ConvertFromMasterToTarget)
        Else
            SetListValuesFromSource(_targetList, _masterList, AddressOf ConvertFromTargetToMaster)
        End If
    End Sub




    ''' <summary>
    ''' An implementation that does nothing in the conversions.
    ''' </summary>
    Friend Class DoNothingListItemConverter
        Implements IListItemConverter

        ''' <summary>
        ''' Converts the specified master list item.
        ''' </summary>
        ''' <param name="masterListItem">The master list item.</param>
        ''' <returns>The result of the conversion.</returns>
        Public Function Convert(ByVal masterListItem As Object) As Object Implements IListItemConverter.Convert
            Return masterListItem
        End Function

        ''' <summary>
        ''' Converts the specified target list item.
        ''' </summary>
        ''' <param name="targetListItem">The target list item.</param>
        ''' <returns>The result of the conversion.</returns>
        Public Function ConvertBack(ByVal targetListItem As Object) As Object Implements IListItemConverter.ConvertBack
            Return targetListItem
        End Function
    End Class

End Class

Then for the XAML:

<DataGrid ..... local:MultiSelectorBehaviours.SynchronizedSelectedItems="{Binding SelectedResults}" />

And finally the VM:

Public ReadOnly Property SelectedResults As ObservableCollection(Of StatisticsResultModel)
    Get
        Return _objSelectedResults
    End Get
End Property

Credit Goes to: http://blog.functionalfun.net/2009/02/how-to-databind-to-selecteditems.html

Solution 2

SelectedItems is bindable as a XAML CommandParameter.

After a lot of digging and googling, I have finally found a simple solution to this common issue.

To make it work you must follow ALL the following rules:

  1. Following Ed Ball's suggestion', on you XAML command databinding, define CommandParameter property BEFORE Command property. This a very time-consuming bug.

    enter image description here

  2. Make sure your ICommand's CanExecute and Execute methods have a parameter of object type. This way you can prevent silenced cast exceptions that occurs whenever databinding CommandParameter type does not match your command method's parameter type.

    private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)  
    {
        // Your code goes here
    }
    
    private bool OnDeleteSelectedItemsExecute(object SelectedItems)  
    {
        // Your code goes here
    }
    

For example, you can either send a listview/listbox's SelectedItems property to you ICommand methods or the listview/listbox it self. Great, isn't it?

Hope it prevents someone spending the huge amount of time I did to figure out how to receive SelectedItems as CanExecute parameter.

Solution 3

You cannot bind to SelectedItems because it is a read-only property. One fairly MVVM-friendly way to work around this is to bind to the IsSelected property of DataGridRow.

You can set up the binding like this:

<DataGrid ItemsSource="{Binding DocumentViewModels}"
          SelectionMode="Extended">
    <DataGrid.Resources>
        <Style TargetType="DataGridRow">
            <Setter Property="IsSelected"
                    Value="{Binding IsSelected}" />
        </Style>
    </DataGrid.Resources>
</DataGrid>

Then you need to create a DocumentViewModel that inherits from ViewModelBase (or whatever MVVM base class you are using) and has the properties of your Document you want to present in the DataGrid, as well as an IsSelected property.

Then, in your main view model, you create a List(Of DocumentViewModel) called DocumentViewModels to bind your DataGrid to. (Note: if you will be adding/removing items from the list, use an ObservableCollection(T) instead.)

Now, here's the tricky part. You need to hook into the PropertyChanged event of each DocumentViewModel in your list, like this:

For Each documentViewModel As DocumentViewModel In DocumentViewModels
    documentViewModel.PropertyChanged += DocumentViewModel_PropertyChanged
Next

This allows you to respond to changes in any DocumentViewModel.

Finally, in DocumentViewModel_PropertyChanged, you can loop through your list (or use a Linq query) to grab the info for each item where IsSelected = True.

Solution 4

With a bit of trickery you can extend the DataGrid to create a bindable version of the SelectedItems property. My solution requires the binding to have Mode=OneWayToSource since I only want to read from the property anyway, but it might be possible to extend my solution to allow the property to be read-write.

I assume a similar technique could be used for ListBox, but I haven't tried it.

public class BindableMultiSelectDataGrid : DataGrid
{
    public static readonly DependencyProperty SelectedItemsProperty =
        DependencyProperty.Register("SelectedItems", typeof(IList), typeof(BindableMultiSelectDataGrid), new PropertyMetadata(default(IList)));

    public new IList SelectedItems
    {
        get { return (IList)GetValue(SelectedItemsProperty); }
        set { throw new Exception("This property is read-only. To bind to it you must use 'Mode=OneWayToSource'."); }
    }

    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        base.OnSelectionChanged(e);
        SetValue(SelectedItemsProperty, base.SelectedItems);
    }
}

Solution 5

Here is one simple solution. This way you can pass/update any data to ViewModel

Designer.xaml

<DataGrid Grid.Row="1" Name="dgvMain" SelectionChanged="DataGrid_SelectionChanged" />

Designer.cs

ViewModel mModel = null;
public Designer()
{
    InitializeComponent();

    mModel = new ViewModel();
    this.DataContext = mModel;
}

private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    mModel.SelectedItems = dgvMain.SelectedItems;
}

ViewModel.cs

public class ViewModel
{
    public IList SelectedItems { get; set; }
}
Share:
63,210
Omar Mir
Author by

Omar Mir

Self taught programmer by hobby and sometimes trade. Happy to help when I can!

Updated on July 21, 2020

Comments

  • Omar Mir
    Omar Mir almost 4 years

    Just doing some light reading on WPF where I need to bind the selectedItems from a DataGrid but I am unable to come up with anything tangible. I just need the selected objects.

    DataGrid:

    <DataGrid Grid.Row="5" 
        Grid.Column="0" 
        Grid.ColumnSpan="4" 
        Name="ui_dtgAgreementDocuments"
        ItemsSource="{Binding Path=Documents, Mode=TwoWay}"
        SelectedItem="{Binding Path=DocumentSelection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
        HorizontalAlignment="Stretch" 
        VerticalAlignment="Stretch" 
        Background="White"
        SelectionMode="Extended" Margin="2,5" 
        IsReadOnly="True" 
        CanUserAddRows="False" 
        CanUserReorderColumns="False" 
        CanUserResizeRows="False"
        GridLinesVisibility="None" 
        HorizontalScrollBarVisibility="Hidden"
        columnHeaderStyle="{StaticResource GreenTea}" 
        HeadersVisibility="Column" 
        BorderThickness="2" 
        BorderBrush="LightGray" 
        CellStyle="{StaticResource NonSelectableDataGridCellStyle}"
        SelectionUnit="FullRow" 
        HorizontalContentAlignment="Stretch" AutoGenerateColumns="False">
    
  • Omar Mir
    Omar Mir about 12 years
    Actually just figured it out using the CommandParameter I can pass the files from the datagrid. But yes I just needed to know what they were.
  • devuxer
    devuxer about 12 years
    @OmarMir, I see you already found a solution. Please see my edit for an alternative.
  • Omar Mir
    Omar Mir about 12 years
    This method would involve keeping a property in my documents model called IsSelected wouldnt it?
  • devuxer
    devuxer about 12 years
    @OmarMir, yes, you would either need to add IsSelected to your Document or create a separate DocumentViewModel. If you're doing MVVM, I would recommend having a separate view model. That way, any data that is needed by your view can be added without modifying your model.
  • Omar Mir
    Omar Mir about 12 years
    @DanM I am using MVVM - see the documents are only one part of a large view model because too much of the data is interrelated and logically it is one business entity with many models/objects. So I bind the documents grid to a property that is an ObservableCollection not sure if I can/want to change the bindings to a different viewmodel.
  • Omar Mir
    Omar Mir about 12 years
    Don't get me wrong - this is much cleaner and MVVM friendly - I'm trying to see what is the best way to do it, because how some of the other objects use the documents collection I'm not sure I want to seperate the view model. And no they don't rely on each other but it makes the plumbing a lot easier if I can access the appropriate properties from the sam VM, though I suppose I could do that using two seperate VMs, you've given me something to think on Dan as seperating the VMs might make the code cleaner too....
  • devuxer
    devuxer about 12 years
    @OmarMir, There are always tradeoffs. If you over-do the patterns, you'll end up just doing a ton of plumbing for not much benefit. But if you notice that things start to feel kludgy, it might be time to separate your view models.
  • Omar Mir
    Omar Mir about 12 years
    @DanM time to re-examine the code and see which way to go, unlike ExitSolution I don't believe the command parameter method is bad in this single instance when you have the entire source code in front of you.
  • Omar Mir
    Omar Mir about 12 years
    Using the Style Tag you end up breaking the binding when there is a scrollbar in the datagrid, very strange behaviour starts to occur. I have come up with a maybe cleaner solution at the bottom.
  • blueshift
    blueshift about 10 years
    I set the binding to SelectedItems="{Binding SelectedSamples, Mode=OneWayToSource}". But in the setter for my ViewModel's property, SelectedSamples, value is always null, even though the SelectedItems property has one or more items. What am I doing wrong?
  • Wallace Kelly
    Wallace Kelly about 10 years
    Omar mentioned, "breaking the binding when there is a scrollbar". I think this is because of row virtualization. You could set EnableRowVirtualization="False" on the DataGrid. Otherwise, I agree that this approach does not work.
  • Vereos
    Vereos over 9 years
    @blueshift same issue here. Did you manage to get it to work?
  • blueshift
    blueshift over 9 years
    No, I was not able to get it to work. SelectedItems is always null.
  • JTennessen
    JTennessen over 9 years
    It worked for me, though on a ListView instead of a DataGrid.
  • user99999991
    user99999991 about 9 years
    I fixed it. So my issue was I was using IList<MyType> in my viewmodel and trying to bind it. This doesn't work. You need to use IList and don't call new List<T> or something in your view model unless you intentionally are trying to populate the ListBox yourself. Otherwise, SetValue() will spit out null indefinitely.
  • Tom
    Tom almost 7 years
    I have a DataGrid where if the user accesses one of its ContextMenu's MenuItem's Command's via an InputBinding's KeyBinding whose CommandParameter="{Binding ElementName=MyDataGrid, Path=SelectedItems}", it'll pass the SelectedItems to the Bound ICommand. However, null is passed if it's accessed via the ContextMenu. I've tried CommandParameter= "{Binding SelectedItems}", "{Binding ElementName=MyDataGrid, Path=SelectedItems}" and "{Binding RelativeSource={RelativeSource Self}, Path=SelectedItems}". Yes, I set the CommandParameter prior to the Command.
  • Tom
    Tom almost 7 years
    Also tried "{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=SelectedItems}". FYI, ContextMenu is defined inline via <DataGrid.ContextMenu> and then ContextMenu.
  • cjmurph
    cjmurph almost 7 years
    This works, but it's worth adding how to deal with the object you get. if (parameter != null) { System.Collections.IList items = (System.Collections.IList)parameter; var selection = items?.Cast<mydatatype>(); }
  • TheFaithfulLearner
    TheFaithfulLearner over 6 years
    I have a question about this. I've implemented it, but since it's a DependencyProperty, how do I get it into my View Model? In the provided code, it's just in the code behind. But I already have access to SelectedItems in the code behind.
  • Thomas Klammer
    Thomas Klammer about 6 years
    This is good, thanks. Maybe you should mention that you have to initialize the collection first.
  • hillstuk
    hillstuk almost 5 years
    A pragmatic answer. Thanks.
  • Robin Bennett
    Robin Bennett almost 5 years
    @Tom - context menu items aren't part of the visual tree, so you can't use ElementName. try CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource FindAncestor,AncestorType=ContextMenu}}"
  • Adam L. S.
    Adam L. S. over 4 years
    +1 for noting that CommandParameter must precede Command. I eventually gave up and had to resort to other methods. Tried it again with this in mind and it works!
  • Rob
    Rob almost 4 years
    Requires nuget package Microsoft.Xaml.Behaviors.Wpf and namespace xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
  • Kappacake
    Kappacake almost 4 years
    could you add an example of a command that handles this as you mentioned?
  • Dinoel Vokiniv
    Dinoel Vokiniv over 3 years
    Nice and clean way, and I think this ought to work even better for the MVVM separation by using Microsoft.Xaml.Behaviors.Wpf to turn the SelectionChanged event into a Command (and thus have it automatically call the view model).
  • Amadeus
    Amadeus about 3 years
    +1s to the answer and the @Rob comment. It works with a ListBox as well. You can add <i:CallMethodAction TargetObject="{Binding}" MethodName="MapListBox_SelectionChanged"/> in XAML then create a public method MapListBox_SelectionChanged in the view model if you like the use SelectionChanged method in the view model
  • kux
    kux over 2 years
    The order of Command and CommandParameter does not matter. (at least today, tested with .net 5.0) <MenuItem Header="Preview" Command="{Binding PreviewSelectedItemsCommand}" CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}}"/>
  • StayOnTarget
    StayOnTarget over 2 years
    (DataContext as MyViewModel).SelectedItems.AddRange(MyDataGrid.SelectedItems‌​.OfType<ItemType>())
  • miotis
    miotis over 2 years
    worked for me, but requires: on Xaml side: ` SelectedItems="{Binding Path=SomeSelectedItems, Mode=OneWayToSource}"` and on VM side create property public IList SomeSelectedItems{ get; set; } without any <> types. To use it's better to cast and to list (to generate new instance of list) SomeSelectedItems.Cast<YourDesiredType>().ToList()