Home > Back-end >  WinUI 3 Binding from a DataTemplate to the parent ViewModel
WinUI 3 Binding from a DataTemplate to the parent ViewModel

Time:10-20

In a WinUI 3 application, using CommunityToolkit.Mvvm, I have XXXPage which has a ListDetailsView.

I defined a DateTemplate for the ListDetailsView DetailsTemplate, which contains a user control : XXXDetailControl.

I am trying to bind the InstallClicked event of the XXXDetailControl to the page's ViewModel InstallCommand, with no success.

<DataTemplate x:Key="DetailsTemplate">
    <Grid>
        <views:XXXDetailControl 
            DetailMenuItem="{Binding}"                   
            InstallClicked="{ ????  }" />
   </Grid>
        ...
</DataTemplate>

How can I setup this binding so that the event from the control defined in the DataTemplate is binded to the page viewmodel command ? How can I setup this binding so that the selected item is sent with the event ?

XXXPage.xaml :

    <Page
    x:Class="XXXPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:models="using:XXX.Models"
    xmlns:views="using:XXX.Views"
    xmlns:behaviors="using:XXX.Behaviors" 
    xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
    xmlns:viewmodels="using:XXX.ViewModels"
    behaviors:NavigationViewHeaderBehavior.HeaderMode="Never"    
    mc:Ignorable="d">
    <Page.Resources>
        ...
        <DataTemplate x:Key="DetailsTemplate">
            <Grid>
                <views:XXXDetailControl 
                    DetailMenuItem="{Binding}"                   
                    InstallClicked="{Binding ViewModel.InstallCommand, ElementName=?}" CommandParameter="{x:Bind (viewmodels:XXXDetailViewModel)}" />
            </Grid>
        ...
        </DataTemplate>
    </Page.Resources>

    <Grid x:Name="ContentArea">
    ...
        <controls:ListDetailsView
            x:Uid="ListDetails"
            x:Name="ListDetailsViewControl"
            DetailsTemplate="{StaticResource DetailsTemplate}"
            ItemsSource="{x:Bind ViewModel.Items}"/>

    </Grid>
    </Page>

XXXPage.cs :

public sealed partial class XXXPage: Page
{
    public XXXViewModel ViewModel
    {
        get;
    }

    public XXXPage()
    {
        ViewModel = App.GetService<XXXViewModel >();
        InitializeComponent();
    }
}

the XXXViewModel :

public class XXXViewModel : ObservableRecipient, INavigationAware
{
       private XXXDetailViewModel? _selected;
    public XXXDetailViewModel? Selected
    {
        get => _selected;
        set
        {
            SetProperty(ref _selected, value);
        }
    }
    public ObservableCollection<XXXDetailViewModel> Items { get; private set; } = new ObservableCollection<XXXDetailViewModel>();

    public ICommand InstallCommand;
}

CodePudding user response:

If you can use Command and CommandParameter inside your user control, you can do it this way.

DetailsControl.xaml.cs

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Windows.Input;

namespace CustomControlEvent;

public sealed partial class DetailControl : UserControl
{
    public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
        nameof(Text),
        typeof(string),
        typeof(DetailControl),
        new PropertyMetadata(default));

    public static readonly DependencyProperty ClickCommandProperty = DependencyProperty.Register(
        nameof(ClickCommand),
        typeof(ICommand),
        typeof(DetailControl),
        new PropertyMetadata(default));

    public static readonly DependencyProperty ClickCommandParameterProperty = DependencyProperty.Register(
        nameof(ClickCommandParameter),
        typeof(object),
        typeof(DetailControl),
        new PropertyMetadata(default));

    public DetailControl()
    {
        this.InitializeComponent();
    }

    public object ClickCommandParameter
    {
        get => (object)GetValue(ClickCommandParameterProperty);
        set => SetValue(ClickCommandParameterProperty, value);
    }

    public string Text
    {
        get => (string)GetValue(TextProperty);
        set => SetValue(TextProperty, value);
    }

    public ICommand ClickCommand
    {
        get => (ICommand)GetValue(ClickCommandProperty);
        set => SetValue(ClickCommandProperty, value);
    }
}

DetailsControl.xaml

<UserControl
    x:Class="CustomControlEvent.DetailControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:CustomControlEvent"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid>
        <Button
            Command="{x:Bind ClickCommand, Mode=OneWay}"
            CommandParameter="{x:Bind ClickCommandParameter, Mode=OneWay}"
            Content="{x:Bind Text, Mode=OneWay}" />
    </Grid>
</UserControl>

MainPageViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;

namespace CustomControlEvent;

public partial class MainPageViewModel : ObservableObject
{
    [RelayCommand]
    private void Run(object commandParameter)
    {
    }

    [ObservableProperty]
    private ObservableCollection<DetailsViewModel> items = new()
    {
        new DetailsViewModel() { Details = "A" },
        new DetailsViewModel() { Details = "B" },
        new DetailsViewModel() { Details = "C" },
    };
}

MainPage.xaml.cs

using Microsoft.UI.Xaml.Controls;

namespace CustomControlEvent;

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

    public MainPageViewModel ViewModel { get; } = new();
}

MainPage.xaml

This page is named ThisPage in order to bind from the DataTemplate.

<Page
    x:Class="CustomControlEvent.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:CustomControlEvent"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    x:Name="ThisPage"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">

    <StackPanel>
        <StackPanel.Resources>
            <DataTemplate
                x:Key="DetailsTemplate"
                x:DataType="local:DetailsViewModel">
                <Grid>
                    <local:DetailControl
                        ClickCommand="{Binding ElementName=ThisPage, Path=ViewModel.RunCommand}"
                        ClickCommandParameter="{x:Bind}"
                        Text="{x:Bind Details, Mode=OneWay}" />
                </Grid>
            </DataTemplate>
        </StackPanel.Resources>

        <ListView
            ItemTemplate="{StaticResource DetailsTemplate}"
            ItemsSource="{x:Bind ViewModel.Items, Mode=OneWay}" />

    </StackPanel>

</Page>

CodePudding user response:

Did you try to give the Page a Name like this?:

<Page ...
    Name="thePage">
    <Page.Resources>
        ...
        <DataTemplate x:Key="DetailsTemplate">
            <Grid>
                <views:XXXDetailControl 
                    DetailMenuItem="{Binding}"                   
                    InstallClicked="{Binding ViewModel.InstallCommand, ElementName=thePage}" ... />
            </Grid>
        ...
        </DataTemplate>
    </Page.Resources>

This seems to work for a ListView.

  • Related