Search code examples
xamldata-bindingbindingmauicollectionview

.NET MAUI CollectionView cannot display the string values in a list of objects


I have a Model class Person, which has some string properties. In the UI, there are some Entry fields that, when a button is pressed, does the following in a ViewModel class:

  • creates a Person object with the values from the fields populating the object's properties

  • adds the Person object to an ObservableList<Person> , which itself is created in the constructor of the ViewModel class

I then try to display some of the Person object's properties in a CollectionView list. A list of the string values. I'd like it to look something like this:
enter image description here

I know that both the Person object is being created and the ObservableList<Person> list is being populated, as I can see the values when I step through the code.

Person.cs

namespace BindingDemo.Model
{
    public partial class Person : ObservableObject
    {
        #region PersonObjectProperties
        [ObservableProperty]
        private string firstName;
        [ObservableProperty]
        private string lastName;
        [ObservableProperty]
        private string email;
        [ObservableProperty]
        private string password;
        #endregion PersonObjectProperties



        public void ClearFields()
        {
            FirstName = string.Empty;
            LastName = string.Empty;
            Email = string.Empty;
            Password = string.Empty;
        }
    }
}

ViewModel class:

using BindingDemo.Model;

namespace BindingDemo.ViewModel
{
    public partial class MainPageViewModel : ObservableObject
    {
        public Person Person { get; } = new Person();

        [ObservableProperty]
        private ObservableCollection<Person> persons;
        
        public MainPageViewModel()
        {
            persons = new ObservableCollection<Person>();
        }

        [RelayCommand]
        private void AddPersonToListAndDb()
        {
            // every field must have a value
            if (string.IsNullOrWhiteSpace(Person.FirstName) || 
                string.IsNullOrWhiteSpace(Person.LastName) || 
                string.IsNullOrWhiteSpace(Person.Email) || 
                string.IsNullOrWhiteSpace(Person.Password))
                return;

            // add the Person object to the list of Person objects
            Persons.Add(Person);

            // clear the fields in the UI so the user can add another person
            Person.ClearFields();
           
            // add the Person object to a database
        }
    }
}

View class:

<?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"
             x:Class="BindingDemo.MainPage"
             Title="Create Person"
             xmlns:viewmodel="clr-namespace:BindingDemo.ViewModel"
             x:DataType="viewmodel:MainPageViewModel">

      <Grid RowDefinitions="Auto, Auto, Auto, Auto, Auto,*"
            Margin="10">
            <Entry Grid.Row ="0" 
                   Placeholder="Enter your first name"
                   PlaceholderColor="DarkGoldenrod"
                   FontAttributes="Bold"
                   Text="{Binding Person.FirstName}" />

            <Entry Grid.Row="1"
                   Placeholder="Enter your last name"
                   PlaceholderColor="DarkGoldenrod"
                   FontAttributes="Bold"
                   Text="{Binding Person.LastName}" />

            <Entry Grid.Row="2"
                   Placeholder="Enter your email address"
                   PlaceholderColor="DarkGoldenrod"
                   FontAttributes="Bold"
                   Text="{Binding Person.Email}" />

            <Entry Grid.Row="3"                                   
                   Placeholder="Enter your password"
                   PlaceholderColor="DarkGoldenrod"
                   FontAttributes="Bold"
                   IsPassword="True"
                   Text="{Binding Person.Password}" />

            <Button Grid.Row="4"  
                    Margin="10"
                    Text="Add to Database"
                    FontAttributes="Bold"
                    HorizontalOptions="Center"
                    Command="{Binding AddPersonToListAndDbCommand}"/>



            <CollectionView Grid.Row="5" 
                            ItemsSource="{Binding Persons}">
                  <CollectionView.ItemTemplate>
                        <DataTemplate>
                              <StackLayout Margin="5">
                                    <HorizontalStackLayout>
                                          <Label Text="First name:" />
                                          <Label Text="{Binding Person.FirstName}" />
                                    </HorizontalStackLayout>
                                    <HorizontalStackLayout>
                                          <Label Text="Last name:" />
                                          <Label Text="{Binding Person.LastName}" />
                                    </HorizontalStackLayout>
                                    <HorizontalStackLayout>
                                          <Label Text="Email address: " />
                                          <Label Text="{Binding Person.Email}" />
                                    </HorizontalStackLayout>
                              </StackLayout>
                        </DataTemplate>
                  </CollectionView.ItemTemplate>
            </CollectionView>
      </Grid>
</ContentPage>

The problem is most likely in the Binding, but I don't know what else to try.

When I'm debugging, I hover my mouse over over the Text={Binding Person.FirstName} to see the value of Person.FirstName. Well, if I hover over Person I can drill down into the object and all the correct values are there, but when I hover over FirstName, it gives the error children could not be evaluated.

enter image description here

enter image description here

To illustrate that the list of Person objects is being created, here is an image of a watch, after I've created a few objects and put them into the list:
enter image description here

EDIT: this is my MainPage.xaml.cs code:

    using BindingDemo.ViewModel;

namespace BindingDemo
{
    public partial class MainPage : ContentPage
    {
        public MainPage(MainPageViewModel mainPageViewModel)
        {
            InitializeComponent();
            BindingContext = mainPageViewModel;
        }
    }
}

Solution

  • because your ItemsSource is an IEnumerable<Person>, each row of the CollectionView has a BindingContext of the current Person object

    that means the binding expression in the DataTemplate would be relative to a Person object, ie

    Text="{Binding FirstName}"
    

    however, because you are using compiled bindings, you also need to specify the DataType

    <DataTemplate x:DataType="model:Person">
    

    you will also need to add an xmlns declaration for model