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
});
}
}
}