Reputation: 315
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>
Upvotes: 2
Views: 3625
Reputation: 25956
https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/collectionview/selection states:
SelectedItems
, of type IList<object>
, the selected items in the list. This property has a default binding mode of OneWay
, and has a null
value when no items are selected.SelectionChangedCommand
, of type ICommand
, which is executed when the selected item changes.SelectedItems
(Inherited from SelectableItemsView
)public System.Collections.Generic.IList<object> SelectedItems { get; set; }
This basically means when SelectionMode=Multiple
it is expecting an IList<object>
which it will update accordingly, but, you will need to implement SelectionChangedCommand
to see changes in the selection.
// MainPage.xaml.cs
using System.Diagnostics;
using System.Windows.Input;
namespace StackOverflow.Maui.Mvvm.SelectMultiple;
public class Person
{
public string Name { get; set; }
}
public partial class MainPage : ContentPage
{
public IList<Person> Persons { get; }
= new List<Person>("Tom,Dick,Harry"
.Split(",")
.Select(s => new Person() { Name = s }));
public IList<object> SelectedPersons { get; } = new List<object>();
public ICommand CheckCommand { get; }
public MainPage()
{
CheckCommand = new Command(() =>
{
string SelectedPersonsText = string.Join(", ", SelectedPersons.Select(p => ((Person)p).Name));
Debug.WriteLine($"SelectedPersons = [{SelectedPersons.Count}] {SelectedPersonsText}");
});
OnPropertyChanged(nameof(CheckCommand));
BindingContext = this;
InitializeComponent();
}
}
<!-- MainPage.xaml -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:StackOverflow.Maui.Mvvm.SelectMultiple"
x:Class="StackOverflow.Maui.Mvvm.SelectMultiple.MainPage">
<CollectionView ItemsSource="{Binding Persons}"
SelectionMode="Multiple"
SelectedItems="{Binding SelectedPersons}"
SelectionChangedCommand="{Binding CheckCommand}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="{x:Type local:Person}">
<Frame>
<Label Text="{Binding Name}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal"/>
<VisualState Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Orange"></Setter>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
Upvotes: 0
Reputation: 89102
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
Upvotes: 2