Search code examples
c#xamlmvvmwinui

x:Bind ViewModel RelayCommand to a command inside DataTemplate


I'm trying to bind a IAsyncRelayCommand (MVVM Toolkit) from my view model using x:Bind within a DataTemplate.

This is my view model:

public class BuildingViewModel : ObservableObject
{
    public ObservableCollection<ObservableProjectItem> ProjectItems { get; } = new ObservableCollection<ObservableProjectItem>();

    public IAsyncRelayCommand AddProjectItemCommand { get; }

    public BuildingViewModel()
    {
        AddProjectItemCommand = new AsyncRelayCommand<ContentDialog>(async (dialog) => await AddProjectItem(dialog));
    }

    private async Task AddProjectItem(ContentDialog dialog)
    {
        // Do something
    }
}

And this my view (XAML):

<TreeView ItemsSource="{x:Bind ViewModel.ProjectItems}">
    <TreeView.ItemTemplate>
        <DataTemplate x:DataType="model:ObservableProjectItem">
            <TreeViewItem ItemsSource="{x:Bind Children}">
                <TextBlock Text="{x:Bind Name}" />
                <TreeViewItem.ContextFlyout>
                    <MenuFlyout>
                        <MenuFlyoutItem Command="{HERE I WANT BIND THE COMMAND}" />
                        <MenuFlyoutItem Text="Löschen" Icon="Delete" />
                    </MenuFlyout>
                </TreeViewItem.ContextFlyout>
            </TreeViewItem>
        </DataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

The view have the following code behind:

public sealed partial class BuildingView : Page
{
    public BuildingView()
    {
        this.InitializeComponent();
    }

    internal BuildingViewModel ViewModel = Ioc.Default.GetService<BuildingViewModel>();
}

My problem is {x:Bind ViewModel.AddProjectItemCommand} doesn't work. Within the DataTemplate the x:Bind scope is ObservableProjectItem. How can I navigate to my root ViewModel respectively bind my command declared in the view model?


Solution

  • Unfortunately, these kind of operations are not well supported by x:Bind (see this WinUI issue for more context). You could however use Binding to solve that problem. For this, you need to set x:Name on your page and use the ElementName property:

    <Page x:Name="MyPage">
        <TreeView ItemsSource="{x:Bind ViewModel.ProjectItems}">
            <TreeView.ItemTemplate>
                <DataTemplate x:DataType="model:ObservableProjectItem">
                    <TreeViewItem ItemsSource="{x:Bind Children}">
                        <TextBlock Text="{x:Bind Name}" />
                        <TreeViewItem.ContextFlyout>
                            <MenuFlyout>
                                <MenuFlyoutItem Command="{Binding Path=AddProjectItemCommand, ElementName=MyPage}" />
                                <MenuFlyoutItem Text="Löschen" Icon="Delete" />
                            </MenuFlyout>
                        </TreeViewItem.ContextFlyout>
                    </TreeViewItem>
                </DataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </Page>