Rad
Rad

Reputation: 963

Unable to compile and run .net 8 MAUI app due to XamlC error XFC0045: Binding: Property "Name" not found on view model

Update: I added converter classes. This is the project I was trying to modify. https://github.com/jfversluis/Plugin.Maui.Audio.git Here is a patch file with my changes: https://raw.githubusercontent.com/radrad/Test2/master/my_changes.patch If you want to replicate my code changes you can clone git repo and apply patch with: git apply my_changes.patch to have all code.

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="&#xf007;" FontFamily="FontAwesome" Size="20" />
        <FontImageSource x:Key="FolderIcon" Glyph="&#xf07b;" FontFamily="FontAwesome" Size="20" />
        <FontImageSource x:Key="FileIcon" Glyph="&#xf15b;" FontFamily="FontAwesome" Size="20" />
        <FontImageSource x:Key="ExpandIcon" Glyph="&#xf067;" FontFamily="FontAwesome" Size="20" />
        <FontImageSource x:Key="CollapseIcon" Glyph="&#xf068;" 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>

Pages\MyLibraryPage.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;
    }
}


ViewModels\Patient.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>();
    }
}

ViewModels\FolderItem.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>();
    }
}

ViewModels\MyLibraryPageViewModel.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
                });
        }
    }
}

Converters
------------------------------------------------------
namespace Plugin.Maui.Audio.Sample.Converters;
public class ExpandCollapseConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool isExpanded)
        {
            return isExpanded ? "CollapseIcon" : "ExpandIcon";
        }
        return "ExpandIcon";
    }

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

public class FolderFileFontAttributeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool isFolder && isFolder)
        {
            return "BoldFontAttribute";
        }
        return "NoneFontAttribute";
    }

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

public class FolderFileIconConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool isFolder)
        {
            return isFolder ? "FolderIcon" : "FileIcon";
        }
        return "FileIcon";
    }

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

Upvotes: 0

Views: 311

Answers (3)

Rad
Rad

Reputation: 963

I actually found the answer. I had to add x:DataType on DataTemplate and not on CollectionView itself.

<CollectionView x:Name="FilesCollectionView" ItemsSource="{Binding Patients}" SelectionMode="Single" Grid.Row="1">
   <CollectionView.ItemTemplate>
       <DataTemplate x:DataType="viewmodels:Patient">


<CollectionView ItemsSource="{Binding Visits}" IsVisible="{Binding IsExpanded}" SelectionMode="Single">
   <CollectionView.ItemTemplate>
      <DataTemplate x:DataType="viewmodels:FolderItem">

Upvotes: 0

Gerald Versluis
Gerald Versluis

Reputation: 34083

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

Upvotes: 0

Abhishek khatri
Abhishek khatri

Reputation: 13

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>

Upvotes: 0

Related Questions