Search code examples
c#.netmauimobile-application

How do I access an ObservableCollection model property for databinding in a shell flyout?


I am creating my first MAUI app that is a term tracker (With community toolkit MVVM). I've added a few terms manually to the database, and I am trying to get them to display in a fly out. I have been able to get them to show up in the fly out, but I'm having trouble getting the term name to databind and I am getting an error:

Binding:Property "Name" not found on TermTracker.ViewModel.TermViewModel

Here's all my relevant files.

The term model using SQLite.

using SQLite;


namespace TermTracker.Model;

[Table("Terms")]
public class Term
{
    [PrimaryKey, AutoIncrement]
    public int Id { get; set; }
    public string? Name { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
}

Getting the terms and putting term models in an ObservableCollection and returning it

using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;

namespace TermTracker.Model
{
    public static class TermRepository
    {
        public static async Task<ObservableCollection<Term>> GetTerms()
        {
            var db = LocalDatabase.GetConnection();
            var terms = new ObservableCollection<Term>();

            var queryResult = await db.QueryAsync<Term>("SELECT * FROM Terms");

            foreach (var term in queryResult)
            {
                terms.Add(new Term
                {
                    Name = term.Name,
                    StartDate = term.StartDate,
                    EndDate = term.EndDate
                });
            }

            return terms;
        }
    }
}

Adding the relevant items for DI

using Microsoft.Extensions.Logging;
using TermTracker.Model;
using TermTracker.View;
using TermTracker.ViewModel;

namespace TermTracker
{
    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                    fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
                });

            builder.Services.AddTransient<AppShell>();
            builder.Services.AddTransient<TermViewModel>();

#if DEBUG
            builder.Logging.AddDebug();
#endif

            return builder.Build();
        }
    }
}

Setting the binding context to be the TermViewModel

using TermTracker.Model;
using TermTracker.View;
using TermTracker.ViewModel;

namespace TermTracker
{
    public partial class AppShell : Shell
    {
        public AppShell(TermViewModel viewModel)
        {
            InitializeComponent();
            BindingContext = viewModel;
            Routing.RegisterRoute(nameof(AddTermPage), typeof(AddTermPage));
        }
    }
}

Adding the view model, datatype, and setting up my flyout menu.

<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="TermTracker.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:TermTracker"
    xmlns:view="clr-namespace:TermTracker.View"
    xmlns:viewModel="clr-namespace:TermTracker.ViewModel"
    x:DataType="viewModel:TermViewModel"
    Shell.FlyoutBehavior="Flyout"
    Title="TermTracker">

    <Shell.Resources>
        <DataTemplate x:Key="FlyoutTermTemplate">
            <TextCell Text="{Binding Name}"/> 

This is where the problem is. Terms.Name doesn't work either. I want the text of the flyout item to be the name of the term model in the observable collection.

Oddly, when I do Binding Name when the app is running and it hotloads, it works but when I stop/try to restart my app I get the error. 

        </DataTemplate>
    </Shell.Resources>

    <Shell.FlyoutContentTemplate>
        <DataTemplate>
            <ListView 
                ItemsSource="{Binding Terms}" 
                ItemTemplate="{StaticResource FlyoutTermTemplate}"/>
        </DataTemplate>
    </Shell.FlyoutContentTemplate>

    <ShellContent Title="Home"
                  ContentTemplate="{DataTemplate view:MainPage}"
                  Route="MainPage"/>

</Shell>

Thanks for taking a look at my problem. This is my first post!

I tried setting relative paths, ancestors, tried accessing the terms properties in the observable collection. Can't seem to get it to work.


Solution

  • You can try to set the x:DataType for the DataTemplate. Such as:

    <Shell
        x:Class="TermTracker.AppShell"
        xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:local="clr-namespace:TermTracker"
        xmlns:view="clr-namespace:TermTracker.View" 
        xmlns:model="clr-namespace:TermTracker.Model" 
        xmlns:viewModel="clr-namespace:TermTracker.ViewModel"
        x:DataType="viewModel:TermViewModel"
        Shell.FlyoutBehavior="Flyout"
        Title="TermTracker">
    
        <Shell.Resources>
            <DataTemplate x:DataType="model:Term" x:Key="FlyoutTermTemplate">
                <TextCell Text="{Binding Name}"/>