I'll post some code below, but here is a summary of What I am trying to do :
I have 2 viewmodels and 1 model :
- ModelData - simple Model Class, just holds a string
- ModelDataViewModel - ViewModel class with an ObservableCollection
- MultipleCollectionsViewModel, ViewModel class with an ObservableCollection.
I have a 'parent' listbox, bound to the MultipleCollectionsViewModel. This listbox has SelectionMode=Extended. Each item represents a ModelDataViewModel, and any number of these can be selected.
I have a second 'child' listbox. This listbox should show all the ModelData objects of all selected items in the parent listbox. Basically, it shows a collection (ChildItems) of a collection (SelectedItems).
I got this to do what I want. But had quite a few issues. First off, SelectionMode=Extended just plain does not work out of box when binding to SelectedItem. The first item selected fires the property, subsequent selections don't. There are a lot of posts about this, I solved this by adding an IsSelected Style to the ListBoxItem. This gets applied to the 'ModelDataViewModel' objects.
The problem is that the parent view model, MultipleCollectionsViewModel, just doesn't have any clue that a property on one of its child viewModels got set. The correct PropertyChanged should be fired from MultipleCollectionsViewModel, not ModelDataViewModel.
I worked around this by adding an event. My understanding of MVVM is it should work around these events, and just rely on binding. But I have just been struggling bending over backwards trying to get this to work through binding/xaml alone. So, I'm posting this to see if anyone has any suggestions or different approaches on what I am trying to do.
Here is a screenshot with the multiple selections working :
Code is below:
The ModelData class is very simple :
public class ModelData
{
public ModelData(string id)
{
this.Identifier = id;
}
public string Identifier
{
get;
set;
}
}
Here is the ModelDataViewModel
public class ModelDataViewModel : ViewModelBase
{
private ObservableCollection<ModelData> _aClassInstances;
private string name;
public ModelDataViewModel() : this("No-Name") { }
public ModelDataViewModel(string name)
{
this.name = name;
this.ChildItems = new ObservableCollection<ModelData>();
this.ChildItems.Add(new ModelData(Name + "-1"));
this.ChildItems.Add(new ModelData(Name + "-2"));
this.ChildItems.Add(new ModelData(Name + "-3"));
this.AVMIsSelected = false;
}
public ObservableCollection<ModelData> ChildItems
{
get
{
return this._aClassInstances;
}
set
{
this._aClassInstances = value;
this.OnPropertyChanged("ChildItems");
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
this.OnPropertyChanged("Name");
}
}
private bool isSelected;
public bool AVMIsSelected
{
get
{
return this.isSelected;
}
set
{
this.isSelected = value;
this.OnPropertyChanged("AVMIsSelected");
}
}
}
And the MultipleCollectionsViewModel :
public class MultipleCollectionsViewModel : ViewModelBase
{
ObservableCollection<ModelDataViewModel> firstItems;
ObservableCollection<ModelDataViewModel> _selectedItems;
public MultipleCollectionsViewModel()
{
this.firstItems = new ObservableCollection<ModelDataViewModel>();
this._selectedItems = new ObservableCollection<ModelDataViewModel>();
this.FirstItems.Add(new ModelDataViewModel("First-1"));
this.FirstItems.Add(new ModelDataViewModel("First-2"));
this.FirstItems.Add(new ModelDataViewModel("First-3"));
}
public ObservableCollection<ModelDataViewModel> FirstItems
{
get
{
return firstItems;
}
set
{
firstItems = value;
}
}
public ObservableCollection<ModelDataViewModel> SelectedItems
{
get
{
return this._selectedItems;
}
set
{
this._selectedItems.Clear();
foreach (ModelDataViewModel aViewModel in this.FirstItems.Where(n => n.AVMIsSelected))
{
this._selectedItems.Add(aViewModel);
}
this.OnPropertyChanged("SelectedItems");
this.OnPropertyChanged("SelectedSubItems");
}
}
public ObservableCollection<Model.ModelData> SelectedSubItems
{
get
{
return new ObservableCollection<Model.ModelData>(this.SelectedItems.SelectMany(n => n.ChildItems).Where(m => 1 == 1));
}
}
Finally, Here is the view and the code behind :
<Window x:Class="MVVM_Help_Needed.View.SelectedItemSync"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SelectedItemSync" Height="500" Width="600">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ListBox Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" ItemsSource="{Binding Path=FirstItems}" SelectedItem="{Binding FirstSelectedItem, Mode=TwoWay}" SelectionMode="Extended" SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding AVMIsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Button Grid.Column="1" Grid.Row="0" Content="{Binding Path=SelectedItems.Count}"></Button>
<ListBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding Path=SelectedSubItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Identifier}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public partial class SelectedItemSync : Window
{
public SelectedItemSync()
{
InitializeComponent();
DataContext = new ViewModel.MultipleCollectionsViewModel();
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ViewModel.MultipleCollectionsViewModel vm = this.DataContext as ViewModel.MultipleCollectionsViewModel;
vm.SelectedItems = null; // this fires the setter, doesn't actually modify anything.
}
}