2

I have the following XAML

<CollectionView.ItemTemplate>
    <DataTemplate x:DataType="model:LogEntry">
        <SwipeView>
            <SwipeView.RightItems>
                <SwipeItem Text="Delete"
                           BackgroundColor="Orange"
                           Command="{Binding Source={RelativeSource AncestorType={x:Type viewModel:MainPageViewModel}}, Path=RemoveLogEntryCommand}"
                           CommandParameter="{Binding .}" />
                <SwipeItem Text="Delete" 
                           BackgroundColor="Red"
                           IsDestructive="True" />
            </SwipeView.RightItems>
            <Grid Padding="10">
                <Frame HeightRequest="125"
                           Padding="0"
                           Style="{StaticResource CardView}">
                    <Frame.GestureRecognizers>
                        <TapGestureRecognizer CommandParameter="{Binding .}"
                                              Command="{Binding Source={RelativeSource AncestorType={x:Type viewModel:MainPageViewModel}}, Path=GotoLogEntryDetailsCommand}" />
                    </Frame.GestureRecognizers>
                    <Grid Padding="0"
                              ColumnDefinitions="80,*">

with the following ICommand declarations using the community toolkit

[RelayCommand]
private async Task GotoLogEntryDetails(LogEntry logEntry)
{
    if (logEntry == null)
        return;

    await _appNavigationService.GoTo($"{nameof(LogEntryDetailsPage)}", true,
        new Dictionary<string, object>
        {
            { "LogEntry", logEntry }
        });


}

[RelayCommand]
private async Task RemoveLogEntry(LogEntry logEntry)
{

}

If I put a breakpoint in RemoveLogEntry then click my delete button the breakpoint is never reached. If I put RemoveLogEntry on the tap gesture and tap am item then the breakpoint is reached, so I know that the code generator has created a valid ICommand.

Intellisense tells me that the argument on CommandParameter . is actually a LogEntry therefore I need to declare the viewModel type.

What is wrong with the SwipeItem's ancestor binding path?

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196
Stephen York
  • 1,247
  • 1
  • 13
  • 42

2 Answers2

4

UPDATE

The first part of this answer ("Views vs Viewmodels") might be an incorrect explanation of the problem. I've seen this "viewmodel reference" syntax several places. Have not taken the time to test how/when it works. Regardless, referring to "View" ancestor always works. As does the "Alternative syntax" approach.


VIEWS vs VIEWMODELS

UI elements (views) and viewmodels are in DIFFERENT hierarchies. A viewmodel is NEVER an ancestor of a view. So you can't find such an ancestor.


VIEW Ancestor

Instead, find the VIEW that is the ancestor. That view's BindingContext will be the corresponding viewmodel.

Change:

<SwipeItem ... Command="{Binding Source={RelativeSource AncestorType={x:Type viewModel:MainPageViewModel}}, Path=RemoveLogEntryCommand}"

To:

<SwipeItem ... Command="{Binding Source={RelativeSource AncestorType={x:Type MainPageView}}, Path=BindingContext.RemoveLogEntryCommand}"

ALTERNATIVE SYNTAX

However, I never use that syntax. I find it easier to give an x:Name to the element I want to refer to. Then can use a simple x:Reference Source:

<ContentPage
  ...
  x:Name="thisPage">
    ...
    <SwipeItem ... Command={Binding BindingContext.RemoveLogEntryCommand, Source={x:Reference thisPage}"
ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196
  • 1
    Thank you so much, worked. I can't even work out the namespace required for the suggestion using the syntax I started with. Just keep getting build errors "Cannot resolve type "Lofty.Logbook.Views:MainPage" no matter what I try, but using the reference works fine. Thanks again. – Stephen York Aug 23 '22 at 01:16
  • So one thing that's troubling me about all this is how would a new comer even find out these relationships to come up with binding paths without coming here or getting to know the XAML engine source code deeply? Is there a decent source of documentation explaining it? I followed a video guide by James Montemagmo where he just used what I had originally. Never showed a click on his Delete button hitting a breakpoint, but I figured what someone in his position was showing would be correct. – Stephen York Aug 23 '22 at 01:18
  • 1
    Are you sure that is exactly what he had? He is an expert, but AFAIK referencing a viewmodel ancestor in xaml cannot work in any circumstance. Its a struggle to piece together these subtleties. One place to start re docs is [Data Binding](https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/), which leads to various links and examples. Including [Relative Binding](https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/relative-bindings). See also https://github.com/xamarin/xamarin-forms-samples/tree/main/DataBindingDemos – ToolmakerSteve Aug 23 '22 at 19:23
1

A late answer to this. But I had the same issue with the ancestor method and I fixed it by surrounding the swipeItem with SwipeItems

 <SwipeView.RightItems>
   <SwipeItems>

    <SwipeItem Text="Delete"
                       BackgroundColor="Orange"
                       Command="{Binding Source={RelativeSource AncestorType={x:Type viewModel:MainPageViewModel}}, Path=RemoveLogEntryCommand}"
                       CommandParameter="{Binding .}" />
            <SwipeItem Text="Delete" 
                       BackgroundColor="Red"
                       IsDestructive="True" />
  </SwipeItems>
        </SwipeView.RightItems>