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();
}
You can try the following code:
File structure like this:
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.