How can I bind a button to a viewmodel command from within a data template that's in Page.Resources?

Viewed 104

In my app using the Windows App SDK, I'm trying to get a button-tapped event to trigger a command in my viewmodel. I have gotten this to work when the data template is between the and on the page, but it won't work when the data template is in Page.Resources. I have to place some data templates in Page.Resources because I need to have multiple data templates in the TreeView and use a DataTemplateSelector to select the correct one based on the type of the item. I've gotten the template selection working, but I just can't get the button binding to work. It looks like the ElementName binding can't find the page name. Can anyone show me what I'm doing wrong?

<Page.Resources>
    <DataTemplate x:Key="treeViewSFTemplate" x:DataType="models:SF">
            <TreeViewItem ItemsSource="{Binding OwnedSFEs}">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding SFName}"/>
                    <Button Content="+">
                        <i:Interaction.Behaviors>
                            <core:EventTriggerBehavior EventName="Tapped">
                                <core:InvokeCommandAction Command="{Binding ElementName=sfSettingsPage, Path=ViewModel.AddNewSubfactorCommand}"/>
                            </core:EventTriggerBehavior>
                        </i:Interaction.Behaviors>
                    </Button>
                    <Button Content="-"/>
                </StackPanel>
            </TreeViewItem>
        </DataTemplate>

       
        <DataTemplate x:Key="treeViewSFETemplate" x:DataType="models:SFE">
        <TreeViewItem>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding SFEName}"/>
                <Button Content="+">
                    <i:Interaction.Behaviors>
                        <core:EventTriggerBehavior EventName="Tapped">
                            <core:InvokeCommandAction Command="{Binding ElementName=thisPage, Path=ViewModel.DeleteSFECommand}"/>
                        </core:EventTriggerBehavior>
                    </i:Interaction.Behaviors>
                </Button>
                <Button Content="-"/>
            </StackPanel>
        </TreeViewItem>
    </DataTemplate>
</Page.Resources>

And then in the in the page code below:

<TreeView x:Name="MyTreeView" 
                          ItemsSource="{x:Bind ViewModel.SFList}" 
                          ItemTemplateSelector="{StaticResource treeViewDataTemplateSelector}" />
2 Answers

The reason for this behavior is that you wrapped a TreeViewItem in the DataTemplate. It will cause that TreeViewItem contains sub-TreeViewItem in the Visual Tree and you could not access correct DataContext with element name in your button. Please remove TreeViewItem from your code.

Like:

 <DataTemplate x:Key="treeViewSFETemplate" x:DataType="models:SFE">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding SFEName}"/>
            <Button Content="+">
                <i:Interaction.Behaviors>
                    <core:EventTriggerBehavior EventName="Tapped">
                        <core:InvokeCommandAction Command="{Binding ElementName=thisPage, Path=ViewModel.DeleteSFECommand}"/>
                    </core:EventTriggerBehavior>
                </i:Interaction.Behaviors>
            </Button>
            <Button Content="-"/>
        </StackPanel>
</DataTemplate>

Assuming the view model MyViewModel for the page is bound to the DataContext property of the page and MyItemViewModel is the datatype of the item in a collection. The CommandParameter binding binds the view model of the item:

<Page
 ...
 x:Name="MyPage"
 ...
  <Page.Resources>
  ...
    <DataTemplate x:Key="MyDataTemplate" x:DataType="vm:MyItemViewModel>
    ...
      <Button
      ...
        Command="{Binding ElementName=MyPage, Path=DataContext.MyCommand}"
        CommandParameter="{Binding}"
        ...

Changes to the value of the property DataContext can be verified by adding an eventhandler after InitializeComponent() in the page constructor:

public MyPage()
{
  InitializeComponent();
  ...
  DataContextChanged += MyPage_DataContextChanged;
  ...
}

void MyPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
  if (DataContext is MyViewModel vm)
  {
  }
}
Related