Select multiple items from a DataGrid in an MVVM WPF project

C#WpfXamlMvvmDatagrid

C# Problem Overview


How can I select multiple items from a DataGrid in an MVVM WPF project?

C# Solutions


Solution 1 - C#

You can simply add a custom dependency property to do this:

public class CustomDataGrid : DataGrid
{
	public CustomDataGrid ()
	{
		this.SelectionChanged += CustomDataGrid_SelectionChanged;
	}

	void CustomDataGrid_SelectionChanged (object sender, SelectionChangedEventArgs e)
	{
		this.SelectedItemsList = this.SelectedItems;
	}
	#region SelectedItemsList

	public IList SelectedItemsList
	{
		get { return (IList)GetValue (SelectedItemsListProperty); }
		set { SetValue (SelectedItemsListProperty, value); }
	}

	public static readonly DependencyProperty SelectedItemsListProperty =
			DependencyProperty.Register ("SelectedItemsList", typeof (IList), typeof (CustomDataGrid), new PropertyMetadata (null));

	#endregion
}

Now you can use this dataGrid in the XAML:

<Window x:Class="DataGridTesting.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:local="clr-namespace:DataGridTesting.CustomDatagrid"
    Title="MainWindow"
    Height="350"
    Width="525">
  <DockPanel>
    <local:CustomDataGrid ItemsSource="{Binding Model}"
        SelectionMode="Extended"
        AlternatingRowBackground="Aquamarine"
        SelectionUnit="FullRow"
        IsReadOnly="True"
        SnapsToDevicePixels="True"
        SelectedItemsList="{Binding TestSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
  </DockPanel>
</Window>

My ViewModel:

public class MyViewModel : INotifyPropertyChanged
{
	private static object _lock = new object ();
	private List<MyModel> _myModel;

	public IEnumerable<MyModel> Model { get { return _myModel; } }

	private IList _selectedModels = new ArrayList ();

	public IList TestSelected
	{
		get { return _selectedModels; }
		set
		{
			_selectedModels = value;
			RaisePropertyChanged ("TestSelected");
		}
	}

	public MyViewModel ()
	{
		_myModel = new List<MyModel> ();
		BindingOperations.EnableCollectionSynchronization (_myModel, _lock);

		for (int i = 0; i < 10; i++)
		{
			_myModel.Add (new MyModel
			{
				Name = "Test " + i,
				Age = i * 22
			});
		}
		RaisePropertyChanged ("Model");
	}

	public event PropertyChangedEventHandler PropertyChanged;

	public void RaisePropertyChanged (string propertyName)
	{
		var pc = PropertyChanged;
		if (pc != null)
			pc (this, new PropertyChangedEventArgs (propertyName));
	}
}

My model:

public class MyModel
{
	public string Name { get; set; }
	public int Age { get; set; }
}

And finally, here is the code behind of MainWindow:

public partial class MainWindow : Window
{
	public MainWindow ()
	{
		InitializeComponent ();
		this.DataContext = new MyViewModel ();
	}
}

I hope this clean MVVM design helps.

Solution 2 - C#

What I would do is create Behaviors using System.Windows.Interactivity. You would have to reference it manually in your project.

Given a control which doesn't expose SelectedItems e.g., (ListBox, DataGrid)

You can create a behavior class something like this

public class ListBoxSelectedItemsBehavior : Behavior<ListBox>
{
    protected override void OnAttached()
    {
        AssociatedObject.SelectionChanged += AssociatedObjectSelectionChanged;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.SelectionChanged -= AssociatedObjectSelectionChanged;
    }

    void AssociatedObjectSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var array = new object[AssociatedObject.SelectedItems.Count];
        AssociatedObject.SelectedItems.CopyTo(array, 0);
        SelectedItems = array;
    }

    public static readonly DependencyProperty SelectedItemsProperty =
        DependencyProperty.Register("SelectedItems", typeof(IEnumerable), typeof(ListBoxSelectedItemsBehavior), 
        new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    public IEnumerable SelectedItems
    {
        get { return (IEnumerable)GetValue(SelectedItemsProperty); }
        set { SetValue(SelectedItemsProperty, value); }
    }
}

And on your XAML I would do the Binding like this where i is xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" and behaviors is the namespace of your Behavior class

<ListBox>
 <i:Interaction.Behaviors>
	<behaviors:ListBoxSelectedItemsBehavior SelectedItems="{Binding SelectedItems, Mode=OneWayToSource}" />
 </i:Interaction.Behaviors>

Assuming that your DataContext for the ListBox has the SelectedItems property in the ViewModel then it will automatically update the SelectedItems. You have encapsulated the event subscribing from the View i.e.,

<ListBox SelectionChanged="ListBox_SelectionChanged"/>

You can change the Behavior class to be of type DataGrid if you want.

Solution 3 - C#

I use this solution in my app:

XAML:

<i:Interaction.Triggers>
     <i:EventTrigger EventName="SelectionChanged">
         <i:InvokeCommandAction Command="{Binding SelectItemsCommand}" CommandParameter="{Binding Path=SelectedItems,ElementName=TestListView}"/>
     </i:EventTrigger>
</i:Interaction.Triggers>

at the top of you xaml file, add this line of code:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

SelectedItemsCommand is ICommand type which is written in your viewmodel.

Used DLL:

System.Windows.Interactivity.dll

Solution 4 - C#

With the default DataGrid of WPF it is not possible to use a Binding, as it is possible with the SelectedItem-Property, cause the SelectedItems-Property is not a DependencyProperty.

One way to to what you want is to register the SelectionChanged-Event of the DataGrid to update the property of your ViewModel, that stores the selected items.

The property SelectedItems of the DataGrid is of type IList so you need to cast the items in the list to your specific type.

C#

public MyViewModel {
  get{
    return this.DataContext as MyViewModel;
  }
}

private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) {
  // ... Get SelectedItems from DataGrid.
  var grid = sender as DataGrid;
  var selected = grid.SelectedItems;

  List<MyObject> selectedObjects = selected.OfType<MyObject>().ToList();
  
  MyViewModel.SelectedMyObjects = selectedObjects;
}

XAML

<Window x:Class="WpfApplication1.MainWindow"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	Title="MainWindow" Height="350" Width="525">
    <Grid>
	<DataGrid
	    SelectionChanged="DataGrid_SelectionChanged"
	    />
    </Grid>
</Window>

Solution 5 - C#

You can add the "IsSelected" property in the Model and add a checkBox in the row.

Solution 6 - C#

You can maka a reusable generic base class. This way you can select rows both from code and UI.

This is my example class i want to be selectable

public class MyClass
{
    public string MyString {get; set;}   
}

Make generic base class for selectable classes. INotifyPropertyChanged makes the UI update when you set IsSelected.

public class SelectableItem<T> : System.ComponentModel.INotifyPropertyChanged
{
    public SelectableItem(T item)
    {
        Item = item;
    }

    public T Item { get; set; }

    bool _isSelected;

    public bool IsSelected {
        get {
            return _isSelected;
        }
        set {
            if (value == _isSelected)
            {
                return;
            }

            _isSelected = value;

            if (PropertyChanged != null)
            { 
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("IsSelected"));
            }
        }
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
}

Create selectable class

public class MySelectableItem: SelectableItem<MyClass>
{
    public MySelectableItem(MyClass item)
       :base(item)
    {
    }
}

Create property to bind to

ObservableCollection<MySelectableItem> MyObservableCollection ...

Set propety

MyObservableCollection = myItems.Select(x => new MySelectableItem(x));

Bind to datagrid and add a style on the DataGridRow that binds to the IsSelected propety on the MySelectedItem

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

To get selected rows/items

var selectedItems = MyObservableCollection.Where(x=>x.IsSelected).Select(y=>y.Item);

To select rows/items

MyObservableCollection[0].IsSelected = true;

Edit———> It seems like it does not work when EnableRowVirtualization is true

Solution 7 - C#

WPF DataGrid allows for this. Simply set the DataGrid.Rows.SelectionMode and DataGrid.Rows.SelectionUnit to "Extended" and "CellOrRowHeader" respectively. This can be done in Blend, as shown in the image I have included. This will allow user to select each cell, whole rows etc. as many as they like, using either shift or ctrl key to continue selecting. enter image description here

Solution 8 - C#

The project I'm working on uses MVVM Light and I found this blog post to be the simplest solution. I'll repeat the solution here:

View Model:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
...

public class SomeVm : ViewModelBase {

	public SomeVm() {
		SelectItemsCommand = new RelayCommand<IList>((items) => {
			Selected.Clear();
			foreach (var item in items) Selected.Add((SomeClass)item);
		});

		ViewCommand = new RelayCommand(() => {
			foreach (var selected in Selected) {
				// todo do something with selected items
			}
		});
	}

	public List<SomeClass> Selected { get; set; }
	public RelayCommand<IList> SelectionChangedCommand { get; set; }
	public RelayCommand ViewCommand { get; set; }
}

XAML:

<Window
	...
	xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
	xmlns:command="http://www.galasoft.ch/mvvmlight"
	...
	<DataGrid
		Name="SomeGrid"
		...
		<i:Interaction.Triggers>
			<i:EventTrigger EventName="SelectionChanged">
				<command:EventToCommand
					Command="{Binding SelectionChangedCommand}"
					CommandParameter="{Binding SelectedItems, ElementName=SomeGrid}" />
			</i:EventTrigger>
		</i:Interaction.Triggers>
		...
		<DataGrid.ContextMenu>
			<ContextMenu>
				<MenuItem Header="View" Command="{Binding ViewCommand}" />
			</ContextMenu>
		</DataGrid.ContextMenu>
		...

Solution 9 - C#

My solution is almost a same like Sandesh. On the other hand, I did not use CustomDataGrid to solve this problem. Instead of that I used a plus button click event with the proper function in a viewmodel. In my code, the main point was that to be able to delete more than one person object from the Datagrid wich was bind to the PeopleList property (ObservableCollection)

This is my Model:

 public class Person
    {
        public Person()
        {

        }

        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }

    }
}

This is my ViewModel (only thoose parts, which are necessary):

public class PersonViewModel : BindableBase
    {
        private ObservableCollection<Person> _peopleList;
        // to be able to delete or save more than one person object
        private List<Person> _selectedPersonList;

        //MyICommand
        public MyICommand AddCommand { get; set; }
        public MyICommand DeleteCommand { get; set; }

        private string _firstName;
        private string _lastName;
        private int _age;

        public PersonViewModel()
        {
            _peopleList = new ObservableCollection<Person>();
            LoadPerson();
            AddCommand = new MyICommand(AddPerson);
            DeleteCommand = new MyICommand(DeletePerson, CanDeletePerson);
            // To be able to delete or save more than one person
            _selectedPersonList = new List<Person>();
        } 
public ObservableCollection<Person> PeopleList
        {
            get { return _peopleList; }
            set
            {
                _peopleList = value;
                RaisePropertyChanged("PeopleList");
            }
        }
 public List<Person> SelectedPersonList
        {
            get { return _selectedPersonList; }
            set
            {
                if (_selectedPersonList != value)
                {
                    RaisePropertyChanged("SelectedPersonList");
                }
            }
        }
 private void DeletePerson()
        {
            // to be able to delete more than one person object
            foreach (Person person in SelectedPersonList)
            {
                PeopleList.Remove(person);
            }
            MessageBox.Show(""+SelectedPersonList.Count); // it is only a checking
            SelectedPersonList.Clear(); // it is a temp list, so it has to be cleared after the button push
        }
 public void GetSelectedPerson(DataGrid datagrid)
        {

            IList selectedItems = datagrid.SelectedItems;
            foreach (Person item in selectedItems)
            {
                SelectedPersonList.Add(item);
            }
        }

My View (xmal):

<UserControl x:Class="DataBase.Views.PersonView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:DataBase.Views"
             xmlns:viewModel="clr-namespace:DataBase.ViewModels" d:DataContext="{d:DesignInstance Type=viewModel:PersonViewModel}"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" Background="AliceBlue">
            <TextBlock Text="First Name:"/>
            <TextBox x:Name="firstNameTxtBox" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}"/>
            <TextBlock Text="Last Name:"/>
            <TextBox x:Name="lastNameTxtBox" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}"/>
            <TextBlock Text="Age:"/>
            <TextBox x:Name="ageTxtBox" Text="{Binding Age}"/>
            <TextBlock Text="{Binding FullName}"/>
            <Button Content="Add" IsEnabled="{Binding CanAddPerson}" Command="{Binding AddCommand}"/>
            <Button Content="Delete" Command="{Binding DeleteCommand}" Click="Delete_Click"/>
            <DataGrid x:Name="datagridPeopleList" ItemsSource="{Binding PeopleList}" AutoGenerateColumns="True" SelectedItem="{Binding SelectedPerson}" SelectionMode="Extended" SelectionUnit="FullRow"/>
            <!--<ListView Height="50" ItemsSource="{Binding PeopleList}" SelectedItem="{Binding SelectedPerson}" Margin="10">
            </ListView>-->
        </StackPanel>
    </Grid>
</UserControl>

My View (.cs):

 public partial class PersonView : UserControl
    {
        public PersonViewModel pvm;
        public PersonView()
        {
            pvm = new PersonViewModel();
            InitializeComponent();
            DataContext = pvm;
        }

        private void Delete_Click(object sender, RoutedEventArgs e)
        {
            pvm.GetSelectedPerson(datagridPeopleList);
        }
    }

I hope that it is useful and not the worst (non elegant) solution in the world :D

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionManvinderView Question on Stackoverflow
Solution 1 - C#SandeshView Answer on Stackoverflow
Solution 2 - C#123 456 789 0View Answer on Stackoverflow
Solution 3 - C#Allen4TechView Answer on Stackoverflow
Solution 4 - C#JehofView Answer on Stackoverflow
Solution 5 - C#LearnerView Answer on Stackoverflow
Solution 6 - C#AxdorphCoderView Answer on Stackoverflow
Solution 7 - C#dylanswebView Answer on Stackoverflow
Solution 8 - C#Seth RenoView Answer on Stackoverflow
Solution 9 - C#turszaView Answer on Stackoverflow