Home > front end >  How to properly bind to VM Properties from within a DataTemplate
How to properly bind to VM Properties from within a DataTemplate

Time:11-30

Problem:

I have a page with a ListView to display a list of objects. I separated the DataTemplate to it's own file as per Microsoft's Creating a DataTemplate with a Type. I want to be able to edit or delete entries in this list, so I have been using the ViewCell's ContextActions MenuItem to provide this functionality. This worked up until I had to separate my DataTemplate from the ContentPage. The commands for handling editing and deleting reside in my ContentPage's BindingContext, which is the ViewModel.

Things I've Tried

Multiple formats of binding, both FindAncestorBindingContext and FindAncestor RelativeSource Modes, and multiple kinds of AncestorType

{Binding BindingContext.EditCommand, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContentPage}}}
{Binding Source={RelativeSource Mode=FindAncestorBindingContext, AncestorType={x:Type views:LoginView}}, Path=EditCommand}
{Binding Source={RelativeSource AncestorType={x:Type vm:LoginVM}}, Path=BindingContext.EditCommand}

Question:

How would I bind to the commands in my ViewModel from the detached DataTemplate? I have tried to use RelativeSource binding but the command is never triggered, or the ancestor is never found. Here is my basic setup:

ContentPage (LoginView):

....
<ContentPage.BindingContext>
   <vm:LoginVM x:Name="viewmodel"/>
</ContentPage.BindingContext>
    
<ContentPage.Content>
     ....
     <Frame HasShadow="True" Padding="30" VerticalOptions="End" BackgroundColor="{DynamicResource PageBackgroundColor}">
          <ListView ItemsSource="{Binding sessionList}" SelectedItem="{Binding SelectedItem}" HasUnevenRows="True" >
               <ListView.ItemTemplate>
                       <DataTemplate>
                            <templates:LoginSessionTemplate/>
                       </DataTemplate>
                  </ListView.ItemTemplate>
             </ListView>
        </Frame>
</ContentPage.Content>

DataTemplate:

<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
          xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
          xmlns:views="clr-namespace:PIM.View"
          x:Class="PIM.DataTemplates.LoginSessionTemplate">
<ViewCell.ContextActions>
    <MenuItem Command="{Binding Source={RelativeSource AncestorType={x:Type views:LoginView}}, Path=EditCommand}" CommandParameter="{Binding .}" Text="Edit"/>
</ViewCell.ContextActions>
....

The bindings between template and data object are fine, I am only having troubles binding to the ViewModel from within the detached DataTemplate. Using the code above does not trigger the command. The context menu appears and the text for the buttons is correct, however the command is either never executed or the ViewModel's BindingContext is never found. So far my only solution is to include the DataTemplate in the ContentPage, which really defeats the purpose of separating the DataTemplate in the first place. Any help is appreciated, thanks!

Update & Answer

ToolmakerSteve was kind enough to link a thread, and although the linked answer did not work, the accepted answer for the thread did. For some reason FindAncestor is not working in my case and I have to reference the ViewModel through the XAML hierarchy. I'm not sure if this is good practice for MVVM, but it is working.

TL;DR:

To reference your ViewModel from within a detatched DataTemplate, you must traverse the XAML heirarchy. To do this, give your DataTemplate's ViewCell an x:Name parameter and then use the reference in your bind. The bind will access the ViewCell's parents. The bind should look something like this:

{Binding Parent.Parent.BindingContext.MyCommand, Source={x:Reference ViewCellName}}

I only needed to change the DataTemplate

<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
          xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
          xmlns:views="clr-namespace:PIM.View"
          x:Class="PIM.DataTemplates.LoginSessionTemplate"
          x:Name="VC">
<ViewCell.ContextActions>
    <MenuItem Command="{Binding Parent.Parent.BindingContext.EditCommand, Source={x:Reference VC}}" CommandParameter="{Binding .}" Text="Edit"/>
</ViewCell.ContextActions>
....

CodePudding user response:

The suggestion made by @ToolmakerSteve in the comments didn't help, however the same thread's actual answer did. I was able to bind to my ViewModel by referencing the ViewCell's parent(s) instead of using FindAncestor.

You must give your DataTemplate's ViewCell an x:Name parameter in order to reference it. Next is to figure out how far up the heirarchy you must traverse before you reach an element that has the BindingContext object you are looking for. In my case, I chose to go up two levels the first time and it worked. Here is my modified binding, where VC is my ViewCell's x:Name.

{Binding Parent.Parent.BindingContext.EditCommand, Source={x:Reference VC}}

I find it odd that FindAncestor will not work in this situation. Perhaps it is a bug? Either way, thank you for the help.

  • Related