skip to Main Content

For some reason I have an error when data biding within parent CollectionView with ItemsSource="{Binding Patients}" and nested CollectionView with ItemsSource="{Binding Visits}"

Within DataTemplate of the nested CollectionView I have properties of FolderItem bound like IsFolder, Name and IsExpanded. When I use Visual Studio 2022 and open this Maui 8 app and the page: MyLibraryPage.xaml I can click on these bound Property names and press F8 and it will bring me to corresponding properties of Visitors property collection item: FolderItem
I am getting this which prevents me to properly compile the code and run it.

MyLibraryPage.xaml(47,15): XamlC error XFC0045: Binding: Property "Name" not found on "Demo.ViewModels.MyLibraryPageViewModel".

If I comment this line, the same error appears for different binding.

<?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:viewmodels="clr-namespace:Plugin.Maui.Audio.Sample.ViewModels"
             xmlns:converters="clr-namespace:Plugin.Maui.Audio.Sample.Converters"
             x:Class="Plugin.Maui.Audio.Sample.Pages.MyLibraryPage"
             Title="My Library"
             x:Name="Page"
             x:DataType="viewmodels:MyLibraryPageViewModel">

    <ContentPage.Resources>
        <Style x:Key="border_gallery_card" TargetType="Border">
            <Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
            <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
            <Setter Property="Padding" Value="16" />
            <Setter Property="StrokeThickness" Value="1" />
            <Setter Property="StrokeShape" Value="RoundRectangle 8" />
        </Style>

        <converters:FolderFileIconConverter x:Key="FolderFileIconConverter" />
        <converters:FolderFileFontAttributeConverter x:Key="FolderFileFontAttributeConverter" />
        <converters:ExpandCollapseConverter x:Key="ExpandCollapseConverter" />

        <!-- Font attributes as resources -->
        <FontAttributes x:Key="BoldFontAttribute">Bold</FontAttributes>
        <FontAttributes x:Key="NoneFontAttribute">None</FontAttributes>

        <!-- Icons -->
        <FontImageSource x:Key="PersonIcon" Glyph="" FontFamily="FontAwesome" Size="20" />
        <FontImageSource x:Key="FolderIcon" Glyph="" FontFamily="FontAwesome" Size="20" />
        <FontImageSource x:Key="FileIcon" Glyph="" FontFamily="FontAwesome" Size="20" />
        <FontImageSource x:Key="ExpandIcon" Glyph="" FontFamily="FontAwesome" Size="20" />
        <FontImageSource x:Key="CollapseIcon" Glyph="" FontFamily="FontAwesome" Size="20" />
    </ContentPage.Resources>

    <Grid RowDefinitions="60,*,150">
        <Grid ColumnDefinitions="*,Auto">
            <Entry x:Name="PatientIdEntry" Placeholder="Enter Patient ID" Grid.Column="0" />
            <Button Text="Create Recording" Grid.Column="1" Command="{Binding AddRecordingCommand}" />
        </Grid>
        <CollectionView x:Name="FilesCollectionView" ItemsSource="{Binding Patients}" SelectionMode="Single" Grid.Row="1">
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <StackLayout>
                        <Grid ColumnDefinitions="Auto, *, Auto">
                            <Image Source="{StaticResource PersonIcon}" VerticalOptions="Center" />
                            <Label Text="{Binding Name}" FontAttributes="Bold" VerticalOptions="Center" />
                            <ImageButton Source="{Binding IsExpanded, Converter={StaticResource ExpandCollapseConverter}}" Command="{Binding Path=BindingContext.OpenMusicCommand, Source={x:Reference Page}}" CommandParameter="{Binding .}" />
                        </Grid>
                        <CollectionView ItemsSource="{Binding Visits}" IsVisible="{Binding IsExpanded}" SelectionMode="Single">
                            <CollectionView.ItemTemplate>
                                <DataTemplate>
                                    <StackLayout Padding="20,0,0,0">
                                        <Grid ColumnDefinitions="Auto, *, Auto">
                                            <Image Source="{Binding IsFolder, Converter={StaticResource FolderFileIconConverter}}" VerticalOptions="Center" />
                                            <Label Text="{Binding Name}" FontAttributes="{Binding IsFolder, Converter={StaticResource FolderFileFontAttributeConverter}}" VerticalOptions="Center" />
                                            <ImageButton Source="{Binding IsExpanded, Converter={StaticResource ExpandCollapseConverter}}" Command="{Binding Path=BindingContext.OpenMusicCommand, Source={x:Reference Page}}" CommandParameter="{Binding .}" />
                                        </Grid>
                                        <CollectionView ItemsSource="{Binding Children}" IsVisible="{Binding IsExpanded}" SelectionMode="Single">
                                            <CollectionView.ItemTemplate>
                                                <DataTemplate>
                                                    <StackLayout Padding="20,0,0,0">
                                                        <Border Style="{StaticResource border_gallery_card}">
                                                            <Border.GestureRecognizers>
                                                                <TapGestureRecognizer Command="{Binding Path=BindingContext.OpenMusicCommand, Source={x:Reference Page}}" CommandParameter="{Binding .}" />
                                                            </Border.GestureRecognizers>
                                                            <Label Text="{Binding Name}" />
                                                        </Border>
                                                    </StackLayout>
                                                </DataTemplate>
                                            </CollectionView.ItemTemplate>
                                        </CollectionView>
                                    </StackLayout>
                                </DataTemplate>
                            </CollectionView.ItemTemplate>
                        </CollectionView>
                    </StackLayout>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>

        <!-- Patient Details Section -->
        <StackLayout Grid.Row="2" Padding="10">
            <Label Text="Patient Details" FontAttributes="Bold" FontSize="Medium" />
            <Image Source="{Binding SelectedPatient.PictureUrl}" HeightRequest="100" WidthRequest="100" />
            <Label Text="Name: " />
            <Label Text="{Binding SelectedPatient.Name}" />
            <Label Text="Health Card Number: " />
            <Label Text="{Binding SelectedPatient.HealthCardNumber}" />
            <Label Text="Last Visit: " />
            <Label Text="{Binding SelectedPatient.LastVisitDateTime}" />
            <Label Text="Length of Visit: " />
            <Label Text="{Binding SelectedPatient.LengthOfVisit}" />
        </StackLayout>
    </Grid>
</ContentPage>
PagesMyLibraryPage.xaml.cs
---------------------------------------
using Plugin.Maui.Audio.Sample.ViewModels;

namespace Plugin.Maui.Audio.Sample.Pages;

public partial class MyLibraryPage : ContentPage
{
    public MyLibraryPage(MyLibraryPageViewModel myLibraryPageViewModel)
    {
        InitializeComponent();

        BindingContext = myLibraryPageViewModel;
    }
}


ViewModelsPatient.cs
------------------------------------------
using System.Collections.ObjectModel;

namespace Plugin.Maui.Audio.Sample.ViewModels;

public class Patient : BaseViewModel
{
    bool isExpanded;
    public string PatientId { get; set; }
    public string Name { get; set; }
    public string PictureUrl { get; set; }
    public string HealthCardNumber { get; set; }
    public DateTime LastVisitDateTime { get; set; }
    public string LengthOfVisit { get; set; }
    public ObservableCollection<FolderItem> Visits { get; set; }

    public bool IsExpanded
    {
        get => isExpanded;
        set
        {
            isExpanded = value;
            NotifyPropertyChanged();
        }
    }

    public Patient()
    {
        Visits = new ObservableCollection<FolderItem>();
    }
}

ViewModelsFolderItem.cs
-----------------------------------
using System.Collections.ObjectModel;

namespace Plugin.Maui.Audio.Sample.ViewModels;

public class FolderItem : BaseViewModel
{
    bool isExpanded;

    public string Name { get; set; }
    public bool IsFolder { get; set; }
    public ObservableCollection<FolderItem> Children { get; set; }
    public bool IsExpanded
    {
        get => isExpanded;
        set
        {
            isExpanded = value;
            NotifyPropertyChanged();
        }
    }

    public FolderItem()
    {
        Children = new ObservableCollection<FolderItem>();
    }
}

ViewModelsMyLibraryPageViewModel.cs
--------------------------------------------
using System.Collections.ObjectModel;

namespace Plugin.Maui.Audio.Sample.ViewModels;
public class MyLibraryPageViewModel : BaseViewModel
{
    public Command AddRecordingCommand { get; }
    public Command<FolderItem> OpenMusicCommand { get; }

    ObservableCollection<Patient> patients;
    Patient selectedPatient;

    public ObservableCollection<Patient> Patients
    {
        get => patients;
        set
        {
            patients = value;
            NotifyPropertyChanged();
        }
    }
    public Patient SelectedPatient
    {
        get => selectedPatient;
        set
        {
            selectedPatient = value;
            NotifyPropertyChanged();
        }
    }

    public MyLibraryPageViewModel()
    {
         Patients = new ObservableCollection<Patient>
         {
            new Patient
            {
                PatientId = "100",
                Name = "John Doe",
                Visits = new ObservableCollection<FolderItem>
                {
                    new FolderItem
                    {
                        Name = "2023-09-22",
                        IsFolder = true,
                        Children = new ObservableCollection<FolderItem>
                        {
                            new FolderItem
                            {
                                Name = "100_2023-09-22_10-30-00.mp3",
                                IsFolder = false
                            },
                            new FolderItem
                            {
                                Name = "100_2023-09-22_11-00-00.mp3",
                                IsFolder = false
                            }
                        }
                    },
                    new FolderItem 
                    { 
                        Name = "2023-09-21", 
                        IsFolder = true,
                        Children = new ObservableCollection<FolderItem>
                        {
                            new FolderItem
                            {
                                Name = "100_2023-09-21_13-30-00.mp3",
                                IsFolder = false
                            },
                            new FolderItem
                            {
                                Name = "100_2023-09-22_11-00-00.mp3",
                                IsFolder = false
                            }
                        }
                    }
                }
            }
        };

        AddRecordingCommand = new Command(async () => await AddRecording());
        OpenMusicCommand = new Command<FolderItem>(async (item) => await OnMusicItemSelected(item));
    }

    static async Task AddRecording()
    {
        await Shell.Current.GoToAsync("AudioRecorderPage");
    }

    static async Task OnMusicItemSelected(FolderItem item)
    {
        if (item.IsFolder)
        {
            item.IsExpanded = !item.IsExpanded;
        }
        else
        {
            await Shell.Current.GoToAsync(
                "MusicPlayerPage",
                new Dictionary<string, object>
                {
                    ["Music"] = item
                });
        }
    }
}

2

Answers


  1. When you nest a CollectionView inside another CollectionView, the inner CollectionView’s binding context becomes the current item of the outer CollectionView. Therefore, you need to ensure that the DataTemplate of the nested CollectionView is properly set to the correct context.

    To fix this, you should set the binding context of the nested CollectionView to the appropriate data type. Here’s an updated version of your XAML with a focus on the nested CollectionView:

    <CollectionView ItemsSource="{Binding Patients}">
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <StackLayout>
                        <Label Text="{Binding Name}" /> <!-- Assuming Patients have a Name property -->
                        
                        <!-- Nested CollectionView for Visits -->
                        <CollectionView ItemsSource="{Binding Visits}">
                            <CollectionView.ItemTemplate>
                                <DataTemplate>
                                    <StackLayout>
                                        <Label Text="{Binding Name}" />
                                        <Label Text="{Binding IsFolder}" />
                                        <Label Text="{Binding IsExpanded}" />
                                        <!-- Other properties of FolderItem -->
                                    </StackLayout>
                                </DataTemplate>
                            </CollectionView.ItemTemplate>
                        </CollectionView>
                    </StackLayout>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    
    Login or Signup to reply.
  2. When you’re using a CollectionView (or ListView or anything that uses a DataTemplate or lists data) the scope of the data-binding shifts.

    For your full page, your data binding object type is MyLibraryPageViewModel, which you have specified in your x:DataType="viewmodels:MyLibraryPageViewModel" on your root level.

    But when you’re inside of the DataTemplate of the CollectionView you are now looking at a Patient. So you will want to add x:DataType="viewmodels: Patient" on your DataTemplate

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search