Managing Multiple Selections with Mvvm

Managing multiple selections with MVVM

Add an IsSelected property to your child ViewModel (OrderViewModel in your case):

public bool IsSelected { get; set; }

Bind the selected property on the container to this (for ListBox in this case):

<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>

IsSelected is updated to match the corresponding field on the container.

You can get the selected children in the view model by doing the following:

public IEnumerable<OrderViewModel> SelectedOrders
{
get { return Orders.Where(o => o.IsSelected); }
}

Multiple selection in WPF MVVM ListBox

You can use this code for MVVM Pattern

XAML

<ListBox x:Name="DeleteHistoryListBoxItem" SelectedItem="{Binding Path=DeleteHistorySelectedItem,UpdateSourceTrigger=PropertyChanged}" 
ItemsSource="{Binding DeleteHistoryListBox, NotifyOnSourceUpdated=True}" SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Item}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

ViewModel

private ObservableCollection<HistoryItems> deleteHistoryListBox = new ObservableCollection<HistoryItems>();
public ObservableCollection<HistoryItems> DeleteHistoryListBox
{
get
{
return deleteHistoryListBox;
}
set
{
deleteHistoryListBox = value;
this.RaisePropertyChanged("DeleteHistoryListBox");
}
}

private HistoryItems deleteHistorySelectedItem;
public HistoryItems DeleteHistorySelectedItem
{
get
{
return deleteHistorySelectedItem;
}
set
{
var selectedItems = DeleteHistoryListBox.Where(x => x.IsSelected).Count();
this.RaisePropertyChanged("DeleteHistorySelectedItem");
}
}

Class

public class HistoryItems : INotifyPropertyChanged
{
private string item;

public string Item
{
get { return item; }
set
{
item = value;
this.RaisePropertyChanged("Item");
}
}

private bool isSelected;

public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
this.RaisePropertyChanged("IsSelected");
}
}
}

VirtualizingStackPanel + MVVM + multiple selection

I found another way of handling selection in the MVVM pattern, which solved my issue. Instead of maintaining the selection in the viewmodel, the selection is retrieved from the ListView/ListBox, and passed as a parameter to the Command. All done in XAML:

<ListView 
x:Name="_items"
ItemsSource="{Binding Items}" ... />

<Button
Content="Remove Selected"
Command="{Binding RemoveSelectedItemsCommand}"
CommandParameter="{Binding ElementName=_items, Path=SelectedItems}"/>

in my ViewModel:

private void RemoveSelection(object parameter)
{
IList selection = (IList)parameter;
...
}

Synchronizing multi-select ListBox with MVVM

You can create a Behavior that synchronizes ListBox.SelectedItems with a collection in your ViewModel:

public class MultiSelectionBehavior : Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
if (SelectedItems != null)
{
AssociatedObject.SelectedItems.Clear();
foreach (var item in SelectedItems)
{
AssociatedObject.SelectedItems.Add(item);
}
}
}

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

public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiSelectionBehavior), new UIPropertyMetadata(null, SelectedItemsChanged));

private static void SelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var behavior = o as MultiSelectionBehavior;
if (behavior == null)
return;

var oldValue = e.OldValue as INotifyCollectionChanged;
var newValue = e.NewValue as INotifyCollectionChanged;

if (oldValue != null)
{
oldValue.CollectionChanged -= behavior.SourceCollectionChanged;
behavior.AssociatedObject.SelectionChanged -= behavior.ListBoxSelectionChanged;
}
if (newValue != null)
{
behavior.AssociatedObject.SelectedItems.Clear();
foreach (var item in (IEnumerable)newValue)
{
behavior.AssociatedObject.SelectedItems.Add(item);
}

behavior.AssociatedObject.SelectionChanged += behavior.ListBoxSelectionChanged;
newValue.CollectionChanged += behavior.SourceCollectionChanged;
}
}

private bool _isUpdatingTarget;
private bool _isUpdatingSource;

void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_isUpdatingSource)
return;

try
{
_isUpdatingTarget = true;

if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
AssociatedObject.SelectedItems.Remove(item);
}
}

if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
AssociatedObject.SelectedItems.Add(item);
}
}

if (e.Action == NotifyCollectionChangedAction.Reset)
{
AssociatedObject.SelectedItems.Clear();
}
}
finally
{
_isUpdatingTarget = false;
}
}

private void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_isUpdatingTarget)
return;

var selectedItems = this.SelectedItems;
if (selectedItems == null)
return;

try
{
_isUpdatingSource = true;

foreach (var item in e.RemovedItems)
{
selectedItems.Remove(item);
}

foreach (var item in e.AddedItems)
{
selectedItems.Add(item);
}
}
finally
{
_isUpdatingSource = false;
}
}

}

This behavior can be used as shown below:

        <ListBox ItemsSource="{Binding Items}"
DisplayMemberPath="Name"
SelectionMode="Extended">
<i:Interaction.Behaviors>
<local:MultiSelectionBehavior SelectedItems="{Binding SelectedItems}" />
</i:Interaction.Behaviors>
</ListBox>

(note that the SelectedItems collection in your ViewModel has to be initialized; the behavior won't set it, it will only change its content)

Select multiple items from a DataGrid in an MVVM WPF project

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.

How to bind multiple selection of listview to viewmodel?

What you can do is you can handle the Button_Click(...) in your code-behind. Then in that code-behind method you can create a List of selected items by iterating over the selected items of the listView.

Since it is allowed to access the ViewModel from the View you can now call a method on your ViewModel and pass the list of selected items as a parameter.

I'm not sure if this would also work with Bindings only, however it is not bad practice to use code-behind as well.

Example Code:

public void Button_Click(object sender, EventArguments arg)
{
List<ListViewItem> mySelectedItems = new List<ListViewItem>();

foreach(ListViewItem item in myListView.SelectedItems)
{
mySelectedItems.Add(item);
}

ViewModel.SomeMethod(mySelectedItems);
}

EDIT

Here is a minimalist example, XAML:

<DataTemplate
x:Key="CarTemplate"
DataType="{x:Type Car}">
</DataTemplate>

<ListView x:Name="myListView"
ItemsSource="{Binding Path=Cars}"
ItemTemplate="{StaticResource CarTemplate}">
</ListView>

CODE-BEHIND:

public void Button_Click(object sender, EventArguments arg)
{
List<Car> mySelectedItems = new List<Car>();

foreach(Car item in myListView.SelectedItems)
{
mySelectedItems.Add(item);
}

ViewModel.SomeMethod(mySelectedItems);
}

ListView select multiple items programmatically in MVVM

I finally found the solution, the problem was not in the homemade component as I was first thinking (I was not searching in the right area) but simply when I was selecting the objects with :

ilSelectedPackages.Add(objDTO_PackageToSelect);

objDTO_PackageToSelect was a copy of an object and so was not comming from ocPackages the ObservableCollection that was filling the ListView.

Conclusion : We must select the exact objects of the Binded observable collection.

DTO_Package objPackInOC = ocPackages.Where(Pack => Pack.sGuid == objDTO_PackageToSelect.sGuid).FirstOrDefault();

if(objPackInOC != null)
ilSelectedPackages.Add(objPackInOC);

WPF ListView Multi-Select MVVM w/ minimal code behind

ListView has internal property to determine which item is selected and it also has SelectedItems to determine multiple selected items. However, this plural SelectedItems of ListView is not bindable. So, the solution is to pass them as a CommandParameter.

<ListView x:Name="lvAvailableStates"
ItemsSource="{Binding AvailableStates, Mode=TwoWay}"
SelectedItem="{Binding SelectedStates, Mode=TwoWay}" => remove this!
...

<Button Command="{Binding AddSelectedStatesCommand}"
CommandParameter="{Binding SelectedItems, Mode=OneWay, ElementName=lvAvailableStates}" => add this!
...

In the VM

private void AddSelectedStates(IEnumerable<SelectableItemWrapper<states_approved>> selectedItems)
{
StatesApproved = selectedItems
.Select(s => s.Item) // only retrieve the Item
.ToList();
}

As you can see at this point, you don't even really need the SelectableItemWrapper to set/unset the IsSelected property to begin with. You should just remove the wrapper and life will be easier.

How to have different selections of two listviews bound to the same instances in WPF / MVVM

If there are only two lists, you could bind IsSelected to two separate properties (ie. IsSelectedInList1, IsSelectedInList2).

But really, a viewmodel is supposed to be an in-code view-less representation of your UI. So, if you have two (or N) copies of the list in your UI which are supposed to have different states, it makes sense to create two (or N) copies of that viewmodel in memory to represent it.

And I don't want to add another collection of 4 wheels otherwise I would have a car with 8 wheels ... or 2 cars with 4 wheels but if I change wheel 3 and 4, it would change only the second car.

I think this is more true when thinking about your model. In your model, you want one instance of a particular car, and that car should have one collection of four wheels. But if you want two different views (with different states), it is OK to create two viewmodels from that model.



Related Topics



Leave a reply



Submit