CollectionView binding does not work correctly

79 views Asked by At

In my .Net MAUI app, I have a viewmodel VideoChatPageModel that contains the following observable properties:

    [ObservableProperty]
    private LanguageViewModel _selectedLanguage;

    [ObservableProperty]
    private List<LanguageViewModel> _languages = new()
    {
        new LanguageViewModel { LanguageName = "English", FlagImage = "flag_united_kingdom.png", LanguageCode = "en" },
        new LanguageViewModel { LanguageName = "Spanish", FlagImage = "flag_spain.png", LanguageCode = "es" },
        new LanguageViewModel { LanguageName = "French", FlagImage = "flag_france.png", LanguageCode = "fr" }
    };

Here is the LanguageViewModel class:

public class LanguageViewModel
{
    public string LanguageName { get; set; }
    public string LanguageCode { get;set; }
    public string FlagImage { get; set; }
}

I have the following CollectionView on the page:

             <CollectionView 
                    Margin="5,5,5,15"
                    ItemSizingStrategy="MeasureAllItems"
                    ItemsSource="{Binding Languages}"
                    SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}"
                    SelectionMode="Single">
                <CollectionView.ItemsLayout>
                    <GridItemsLayout Orientation="Vertical" />
                </CollectionView.ItemsLayout>
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <ContentView Padding="5" >
                            <Grid ColumnDefinitions="*,3*">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="40" />
                                </Grid.RowDefinitions>
                                <Image Source="{Binding FlagImage}" Grid.Column="0" HorizontalOptions="Start" />
                                <Label Text="{Binding LanguageName}" Grid.Column="1" HorizontalOptions="Start" />
                            </Grid>
                        </ContentView>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>

For some reason, it does not compile:

Error XFC0045 Binding: Property "FlagImage" not found on "Features.VideoChat.VideoChatPageModel".

It is clearly looking for the property not in the LanguageViewModel class, but in the VideoChatPageModel, which is the binding context for the whole page:

public VideoChatPage(VideoChatPageModel pageModel)
{
    InitializeComponent();
    BindingContext = pageModel;
}

But why does it behave so? If I replace the CollectionView with the following picker, it works:

            <Picker ItemsSource="{Binding Languages}" SelectedItem="{Binding SelectedLanguage}" ItemDisplayBinding="{Binding LanguageName}">                    
            </Picker>
2

There are 2 answers

0
Jason On BEST ANSWER

you need to add an x:DataType to the DataTemplate so it knows what the type of its BindingContext is

7
Stephen Quan On

To further clarify my comment about LanguageViewModel can be replaced by CultureInfo, you can initialize it just by passing a locale name in the constructor and it will automatically populate other useful properties such as NativeName, EnglishName, LCID, TwoLetterISOLanguageName and so forth. You can use any of those parameters to generate a flag filename.

Here's a working example:

// MauiProgram.cs ...
builder.Services.AddTransient<MainPage>();
builder.Services.AddTransient<MainViewModel>();
// MainViewModel.cs ...
public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    private CultureInfo _selectedLanguage;

    [ObservableProperty]
    private List<CultureInfo> _languages = new List<CultureInfo>()
    {
        new CultureInfo("en-US"),
        new CultureInfo("es-ES"),
        new CultureInfo("fr-FR")
    };
}
// MainPage.xaml.cs
public partial class MainPage : ContentPage
{
    public MainPage(MainViewModel VM)
    {
        InitializeComponent();
        BindingContext = VM;
    }
}
<!-- MainPage.xaml ... -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:g="clr-namespace:System.Globalization;assembly=mscorlib"
             x:Class="maui_language_picker.MainPage">
    <CollectionView ItemSizingStrategy="MeasureAllItems"
                ItemsSource="{Binding Languages}"
                SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}"
                SelectionMode="Single">
        <CollectionView.ItemsLayout>
            <GridItemsLayout Orientation="Vertical" />
        </CollectionView.ItemsLayout>
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="g:CultureInfo">
                <ContentView Padding="5" >
                    <Grid ColumnDefinitions="*,3*">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="40" />
                        </Grid.RowDefinitions>
                        <Image Source="{Binding LCID, StringFormat='flag_{0}.png'}" Grid.Column="0" HorizontalOptions="Start" />
                        <Label Text="{Binding NativeName}" Grid.Column="1" HorizontalOptions="Start" />
                    </Grid>
                </ContentView>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</ContentPage>

And you create your flag resources as:

  • Resources/Images/flag_1033.svg // en-US
  • Resources/Images/flag_3082.svg // es-ES
  • Resources/Images/flag_1036.svg // fr-FR

You can limit the flag resolution to conservatively by setting BaseSize in your csproj, e.g.

<!-- Images -->
<MauiImage Update="Resources\Images\flag_*.svg" BaseSize="60,40" />

References: