0

In my demo .NET MAUI application I have this model:

public class Person
{
    public string Name { get; set; }
    public string Email { get; set; }
}

This is my ViewModel:

public class MainViewModel : BaseViewModel
{
    private Person _selectedPerson;
    public Person SelectedPerson
    {
        get => _selectedPerson;
        set
        {
            if (_selectedPerson != value)
            {
                _selectedPerson = value;
                OnPropertyChanged(nameof(SelectedPerson));
                OnPropertyChanged(nameof(People));
            }
        }
    }

    public ObservableCollection<Person> People { get; } = new ObservableCollection<Person>
    {
        new Person { Name = "John Doe", Email = "john.doe@example.com" },
        new Person { Name = "Jane Doe", Email = "jane.doe@example.com" },
        new Person { Name = "Bob Smith", Email = "bob.smith@example.com" },
        new Person { Name = "Alice Johnson", Email = "alice.johnson@example.com" },
    };

    public ICommand SelectPersonCommand => new Command<Person>(person =>
    {
        SelectedPerson = person;
    });

    public MainViewModel()
    {

    }
}

I have the enumerable People bounded to my ListView:

<ContentPage
    x:Class="MAUIPlayground.MainPage"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:converters="clr-namespace:MAUIPlayground.Converters"
    x:Name="mainPage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <converters:SelectedItemToBooleanConverter x:Key="SelectedItemToBooleanConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>
    <StackLayout>
        <ListView ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Orientation="Horizontal">
                            <CheckBox IsChecked="{Binding ., Converter={StaticResource SelectedItemToBooleanConverter}, ConverterParameter={Binding Source={x:Reference mainPage}, Path=BindingContext.SelectedPerson}}" />
                            <Label Text="{Binding Name}" />
                            <Label Text="{Binding Email}" />
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage>

And this is my SelectedItemToBooleanConverter:

public class SelectedItemToBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null)
        {
            var person = (Person)value;
            var selectedPerson = (Person)parameter;
            return person == selectedPerson;
        }

        return false;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

From what I understand I should be able to parse both value and parameter arguments on the converter to People. This is true for value, however parameter is passed as Microsoft.Maui.Controls.Binding when I'm trying to pass Person as argument. How can I achieve this?

adamasan
  • 1,092
  • 1
  • 12
  • 33
  • ```ConverterParameter``` is not a BindableObject. So cannot use binding for it. – Liqun Shen-MSFT May 04 '23 at 08:45
  • Damn. Any idea on how I could achieve this? If the SelectedPerson on the ViewModel is equal to the person that the user tapped on the ListView (or collection) I want to display a checkbox checked only for that item in the collection. – adamasan May 04 '23 at 09:39
  • https://learn.microsoft.com/en-us/dotnet/maui/user-interface/visual-states?view=net-maui-7.0 – H.A.H. May 04 '23 at 10:48
  • Or alternatively, bind checkbox to a boolean property on the model item. Set that property as needed whenever SelectedPerson changes. (Keep track of "previousSelection", so you can clear its checkbox, then set checkbox of new selection.) – ToolmakerSteve May 04 '23 at 23:40

1 Answers1

1

Since you have binded the SelectedItem of ListView in the ViewModel, each time you select a new item, the setter of SelectedPerson will fire. And then you could change the state of CheckBox for the item. Consider my workaround:

1.Create a new IsChecked property in Person class

public class Person : INotifyPropertyChanged
{
    ...
    private bool isChecked;

    public event PropertyChangedEventHandler PropertyChanged;

    public bool IsChecked
    { get
        {
            return isChecked;
        }
        set
        {
            isChecked = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsChecked)));
        }
    }

2.In xaml, bind the IsChecked property of CheckBox:

<DataTemplate>
    <ViewCell>
        <StackLayout Orientation="Horizontal">
            <CheckBox IsChecked="{Binding IsChecked}"/>
            <Label Text="{Binding Name}" />
            <Label Text="{Binding Email}" />

3.In ViewModel, change the IsChecked property in Person instance when SelectedPerson change

public Person SelectedPerson
{
    get => _selectedPerson;
    set
    {
        if (_selectedPerson != value)
        {
            if (_selectedPerson != null)
            {
                _selectedPerson.IsChecked = false;
            }
            _selectedPerson = value;
            _selectedPerson.IsChecked = true;
            ....
            }
        }
    }

Hope it works.

Liqun Shen-MSFT
  • 3,490
  • 2
  • 3
  • 11
  • This works, however, being new to MVVM, I have a few questions, should I implement INotifyPropertyChanged on my models? Shouldn't the models remain free of UI concerns and deal only with data representation? The second question, I don't have a IsChecked property on my domain model? In this case the practice would be to have several models for different representations of data? Like I would with a DTO? – adamasan May 11 '23 at 01:36
  • Yes sometimes we have to implement the INotifyPropertyChanged for model. Here is docs about [The MVVM Pattern](https://learn.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/mvvm#the-mvvm-pattern). Sometimes model also send notifications. And there is also some discussion about this topic : [In MVVM should the ViewModel or Model implement INotifyPropertyChanged?](https://stackoverflow.com/questions/772214/in-mvvm-should-the-viewmodel-or-model-implement-inotifypropertychanged) – Liqun Shen-MSFT May 11 '23 at 03:03
  • [Why is INotifyPropertyChanged in both Model and ViewModel?](https://stackoverflow.com/questions/30283393/why-is-inotifypropertychanged-in-both-model-and-viewmodel) – Liqun Shen-MSFT May 11 '23 at 03:06
  • Thank you for the links. As this solves the problem at hand I've accepted the answer and will take a look into the more philosophical aspects of MVVM later. – adamasan May 11 '23 at 03:13