I'm attempting a multi selection using CollectionView
and MvvM
. The (official docs don't do the greatest job of differentiating between normal code-behind and MVVM, and for us noobies that hurts.
I can get the single selection working, but making the leap to multiple selection is beyond me.
I will show my working code for single selection and discuss how to make it work for multiple selection. Maybe someone knows more than I?
Here's the working code for single selection:
Pass an ObservableCollection
of type Person
to a ModelView. Declare an instance of Person
which will be the "selected object".
namespace Sandbox.ViewModel;
[QueryProperty("Persons", "Persons")]
public partial class SelectPageViewModel : ObservableObject
{
[ObservableProperty]
private ObservableCollection<Person> persons;
[ObservableProperty]
private Person selectedPerson;
public SelectPageViewModel()
{
Persons = new();
}
}
In the View, create a CollectionView
and make some good guesses for its attributes:
<Grid>
<Label Text="Select from List"/>
<CollectionView ItemsSource="{Binding Persons}"
SelectionMode="Single"
SelectedItem="{Binding SelectedPerson}"
SelectionChangedCommand="{Binding SelectionChangedCommand}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Person">
<Grid>
<Label Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
Back in the ViewModel, the SelectionChanged
command: if the user is satisfied with their choice of SelectedPerson
, I pass it back to the page from whence it came, otherwise I null the selection and return:
[RelayCommand]
private async Task SelectionChanged()
{
bool keepSelection = await App.Current.MainPage.DisplayAlert(SelectedPerson.Name, "Keep this selection?", "Yes", "No");
if (keepSelection)
{
Dictionary<string, object> throwParam = new()
{
{ "SelectedPerson", SelectedPerson }
};
await Shell.Current.GoToAsync("..", throwParam);
}
// else clear the selection and return
SelectedPerson = null;
return;
}
After much wrestling, here is working code. Something very important: note the type of the ObservableCollection
that is used to in the binding to the collection (hint, it's Object
).
My current code is the same as the above code, but I will show both ViewModel and View in total, plus screenshots of the List that's supposed to be populated.
ViewModel:
namespace Sandbox.ViewModel;
[QueryProperty("Persons","Persons")]
public partial class SelectPageViewModel : ObservableObject
{
[ObservableProperty]
private ObservableCollection<Person> persons;
[ObservableProperty]
private ObservableCollection<Object> selectedPersons;
[ObservableProperty]
private Person selectedPerson;
public SelectPageViewModel()
{
Persons = new();
SelectedPersons = new();
}
[RelayCommand]
private void SelectionChanged()
{
// every time something is selected, the object is added to SelectedPersons automagically.
int a = SelectedPersons.Count; // will +1 every time
}
}
View:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodel="clr-namespace:Sandbox.ViewModel"
xmlns:model="clr-namespace:Sandbox.Model"
x:DataType="viewmodel:SelectPageViewModel"
x:Class="Sandbox.View.SelectPage"
Title="SelectPage">
<Grid RowDefinitions="Auto,Auto" Padding="10">
<Label Grid.Row="0"
Text="Select from List"
FontSize="Large"
FontAttributes="Bold" />
<CollectionView Grid.Row="1"
ItemsSource="{Binding Persons}"
SelectionMode="Multiple"
SelectedItems="{Binding SelectedPersons, Mode=TwoWay}"
SelectionChangedCommand="{Binding SelectionChangedCommand}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Person">
<Grid Padding="10">
<Label Text="{Binding Name}"
FontSize="Medium" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</ContentPage>
create a property in your VM (note that it needs to be a collection of object (see this question)
[ObservableProperty]
private ObservableCollection<Person> persons;
[ObservableProperty]
private ObservableCollection<object> selectedPersons;
initialize them
public SelectPageViewModel()
{
Persons = new();
SelectedPersons = new();
}
then bind your CollectionView
to it
<CollectionView ItemsSource="{Binding Persons}"
SelectionMode="Multiple"
SelectedItems="{Binding SelectedPersons}"
SelectionChangedCommand="{Binding SelectionChangedCommand}">
if the user selects 3 rows, those 3 objects will be contained in SelectedPersons
. SelectedPersons
will be a subset of your ItemsSource
Persons
[RelayCommand]
private void SelectionChanged()
{
foreach(var p in SelectedPersons)
{
if (p is Person person)
{
Console.WriteLine($"{person.Name} is selected");
}
}
}
[ObservableProperty]
private ObservableCollection<Person> persons;
[ObservableProperty]
private ObservableCollection<object> selectedPersons