commandmauidatatemplatebinding-context

.net MAUI DataTemplate's. Command Bindings in Parents View Model


I have a ContentView which contains a CollectionView.

...
             xmlns:templates="clr-namespace:PropertyManagement.Portfolio.DataTemplates"
             xmlns:vm="clr-namespace:PropertyManagement.Portfolio.ContentViews.ViewModels"
             x:Class="PropertyManagement.Portfolio.ContentViews.AgreementListView">


    <ScrollView>
        <CollectionView x:DataType="vm:AgreementListVM" x:Name="MyCollectionView"  ItemsSource="{Binding AllAgreements}" Margin="10,0">
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <templates:AgreementListViewDataTemplate />
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </ScrollView>

The DataTemplate contains a few databound Labels and an ImageButton

<Border
    x:Class="PropertyManagement.Portfolio.DataTemplates.AgreementListViewDataTemplate"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:models="clr-namespace:PropertyManagement.Portfolio.Models"
    xmlns:vm="clr-namespace:PropertyManagement.Portfolio.ContentViews.ViewModels"
    x:DataType="models:Agreement">

    <VerticalStackLayout HorizontalOptions="Start" Padding="10">
        <HorizontalStackLayout HorizontalOptions="Start" Spacing="10">
            <Label FontAttributes="Bold" Text="{Binding Name}" />
            <Label FontAttributes="Bold" Text="{Binding Tenant.FirstName}" />
            <Label FontAttributes="Bold" Text="{Binding LetProperty.Address1}" />
            <ImageButton
                Source="edit.png"
                WidthRequest="15" />
        </HorizontalStackLayout>
    </VerticalStackLayout>
</Border>

This all works just fine.

Now i want to bind the image button Command to an ICommand (RelayCommand using CommunityToolkit) which is in the parent ContentViews view model ie AgreementListVM. If I use Command="{Binding Source={vm:AgreementListVM}, Path=EditAgreementCommand}" then that works correctly, but that requires the DataTemplate to know about the parent view's ViewModel. I want the Data Template to be useable without relying on knowing the viewmodel to use.

I have tried Command="{Binding EditAgreementCommand, Source={RelativeSource AncestorType={x:Type vm:AgreementListVM}}}" as described here[ https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/data-binding/relative-bindings?view=net-maui-8.0#bind-to-an-ancestor] It doesnt work, no binding failures just doesn't fire the relay command and it still requires knowing the viemodel to use.

I have tried Command="{Binding Source={RelativeSource AncestorType={x:Type vm:AgreementListVM}}, Path=EditAgreementCommand}" Project compiles and runs, intellisense even knows that it is an IRelayCommand but the command does not fire and it still requires knowing the view model. It'm wondering why I would use thesewhen the working code seems so much simpler?

Tried this, which would be exactly what I want! Command="{Binding Source={x:Reference MyCollectionView}, Path=BindingContext.EditAgreementCommand}" as described here [https://github.com/dotnet/maui/discussions/17771] Compiles and runs but I get a binding failure 'BindingContext' property not found on 'Microsoft.Maui.Controls.Xaml.ReferenceExtension', target property: 'Microsoft.Maui.Controls.ImageButton.Command' C:\Users\kev12\OneDrive\Documents\Current Projects\MAUI\PropertyManagement\Portfolio\DataTemplates\AgreementListViewDataTemplate.xaml.

Doing my head in!! Can somebody please point out what I am doing wrong?

PS Doesnt change anything if I assign the Content Views viewmodel in the code behind

    public AgreementListView()
    {
        InitializeComponent();
        BindingContext = new AgreementListVM();
    }

Solution

  • You can try the following code:

    File structure like this:

    enter image description here

    Models/NameId.cs:

    public class NameId
    {
        public string Name { get; set; }
    
        public string Id { get; set; }
    }
    

    ViewModels/BaseViewModel.cs:

    public class BaseViewModel
    {
         public ObservableCollection<NameId> NameIdList { get; set; }
    
    
        public ICommand AddCharCommand { get; private set; }
         public BaseViewModel()
         {
             NameIdList = new ObservableCollection<NameId>
             {
         new NameId { Id = "Id A", Name = "Name A" },
         new NameId { Id = "Id B", Name = "Name B" },
         new NameId { Id = "Id C", Name = "Name C" }
             };
             AddCharCommand = new Command(Addc);
         }
    
    
        private void Addc(object obj)
         {
             Console.WriteLine("click");
         }
    }
    

    DataTemplates/CardViews.cs:

    public class CardView : ContentView
    {
        public static readonly BindableProperty NameProperty =
            BindableProperty.Create(nameof(Name), typeof(string), typeof(CardView), string.Empty);
    
       public static readonly BindableProperty IDProperty =
            BindableProperty.Create(nameof(ID), typeof(string), typeof(CardView), string.Empty);
    
       public string Name
        {
            get => (string)GetValue(NameProperty);
            set => SetValue(NameProperty, value);
        }
    
       public string ID
        {
            get => (string)GetValue(IDProperty);
            set => SetValue(IDProperty, value);
        }
    }
    

    Create a ControlTemplate in App.xaml:

    <Application ...
                 xmlns:m="clr-namespace:MauiApp5.Models"
                 xmlns:vm="clr-namespace:MauiApp5.ViewModels"
                 xmlns:local="clr-namespace:MauiApp5"
                 x:Class="MauiApp5.App">
        <Application.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    ....
                </ResourceDictionary.MergedDictionaries>
    
                <ControlTemplate x:Key="AgreementListViewControlTemplate">
    
                    <VerticalStackLayout HorizontalOptions="Start" Padding="10" BindingContext="{Binding Source={RelativeSource TemplatedParent}}">
                        <HorizontalStackLayout HorizontalOptions="Start" Spacing="10">
    
                            <Label FontAttributes="Bold" Text="{Binding Name}" />
                            <Label FontAttributes="Bold" Text="{Binding ID}" />
                            <ImageButton
                                        Source="kb.png"
                                        Aspect="AspectFill"
                                        WidthRequest="50" HeightRequest="50"
                                        Command="{Binding Source={RelativeSource AncestorType={x:Type vm:BaseViewModel}}, Path=AddCharCommand}"/>
    
                        </HorizontalStackLayout>
                    </VerticalStackLayout>
                </ControlTemplate>
            </ResourceDictionary>
        </Application.Resources>
    </Application>
    

    Consume AgreementListViewControlTemplate in Specific page:

    <ContentPage ...
                 xmlns:vm="clr-namespace:MauiApp5.ViewModels"
                 xmlns:m="clr-namespace:MauiApp5.Models"
                 xmlns:templates="clr-namespace:MauiApp5.DataTemplates"
                 x:Class="MauiApp5.NewPage1"
                 Title="NewPage1">
        
        <ContentPage.BindingContext>
            <vm:BaseViewModel/>
        </ContentPage.BindingContext>
        
        <VerticalStackLayout>
            <CollectionView ItemsSource="{Binding NameIdList}">
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <templates:CardView Name="{Binding Name}"
                                            ID="{Binding Id}"
                                            ControlTemplate="{StaticResource AgreementListViewControlTemplate}"/>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
        </VerticalStackLayout>
    </ContentPage>
    

    And here is the effect. When you click the imagebutton, it will print "click" in Output.