WinUI DataGrid (CommunityToolkit.WinUI.UI.Controls) add a delete button

Christian Dugdale 21 Reputation points
2022-06-30T10:00:03.137+00:00

I have started a new project in WinUI 3 using the Template Studio

I want to follow the MVVM/DI pattern provided and add a button to a datagrid to delete that is bound to a command on the viewmodel. I can achieve this easily with WPF, but i can't seem to implement an elegant solution in WinUI binding to an ICommand. It doesn't fail, it does nothing! I have had a working solution by adding a parameterless constructor to my viewmodel and looking up the service like this myservice = App.GetService<IMyService>. Does this break the pattern or is an anti-pattern? What am I missing? There must be a neat way to do this without looking up the service or code behind on click event. The issue seems to be the DataContext is set to the Model and I can't break out of that model and get the DataContect of my page which is bound to my viewmodel, I could add the Command to the model, but that's an awful solution as a model shouldn't run commands.

To demonstrate the issue I have created 2 projects one with WPF and the other with WinUI 3, both binding the button to Command on my viewmodel, WPF works, WinUI doesn't.

First of all the WPF working solution

WPF View - Things to note page name is set x:name and in the button command I have used ElementName=_page

<Page  
    x:Class="DataGridExampleWPF.Views.DataGridExamplePage"  
    Style="{DynamicResource MahApps.Styles.Page}"  
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
    xmlns:properties="clr-namespace:DataGridExampleWPF.Properties"   
    xmlns:viewmodels="clr-namespace:DataGridExampleWPF.ViewModels"   
    d:DataContext="{d:DesignInstance Type=viewmodels:DataGridExampleViewModel}"  
    mc:Ignorable="d"  
    d:DesignHeight="450" d:DesignWidth="800"  
    x:Name="_page">  
    <Grid>  
        <Grid.RowDefinitions>  
            <RowDefinition Height="48" />  
            <RowDefinition Height="*" />  
        </Grid.RowDefinitions>  
        <TextBlock  
            Style="{StaticResource PageTitleStyle}"  
            Margin="{StaticResource MediumLeftMargin}"  
            Text="{x:Static properties:Resources.DataGridExamplePageTitle}" />  
        <Grid Grid.Row="1">  
            <DataGrid  
                AutoGenerateColumns="False"  
                GridLinesVisibility="Horizontal"  
                CanUserAddRows="False"  
                ItemsSource="{Binding Source}"  
                Margin="{StaticResource SmallLeftRightMargin}"  
                KeyboardNavigation.TabNavigation="Once">  
                <DataGrid.Columns>  
                    <DataGridTextColumn Binding="{Binding OrderID}" Header="OrderID" />  
                    <DataGridTextColumn Binding="{Binding OrderDate}" Header="OrderDate" />  
                    <DataGridTextColumn Binding="{Binding Company}" Header="Company" />  
                    <DataGridTextColumn Binding="{Binding ShipTo}" Header="ShipTo" />  
                    <DataGridTextColumn Binding="{Binding OrderTotal}" Header="OrderTotal" />  
                    <DataGridTextColumn Binding="{Binding Status}" Header="Status" />  
                    <DataGridTemplateColumn Header="Symbol">  
                        <DataGridTemplateColumn.CellTemplate>  
                            <DataTemplate>  
                                <TextBlock  
                                    Margin="{StaticResource SmallLeftRightMargin}"  
                                    HorizontalAlignment="Left"  
                                    Style="{StaticResource SmallIconStyle}"  
                                    Text="{Binding Symbol}" />  
                            </DataTemplate>  
                        </DataGridTemplateColumn.CellTemplate>  
                    </DataGridTemplateColumn>  
                    <DataGridTemplateColumn Header="Delete">  
                        <DataGridTemplateColumn.CellTemplate>  
                            <DataTemplate>  
                                <Button Command="{Binding DataContext.CmdDelete, ElementName=_page}" CommandParameter="{Binding}">Delete</Button>  
                            </DataTemplate>  
                        </DataGridTemplateColumn.CellTemplate>  
                    </DataGridTemplateColumn>  
                </DataGrid.Columns>  
            </DataGrid>  
        </Grid>  
    </Grid>  
</Page>  

using System.Windows.Controls;  
using DataGridExampleWPF.ViewModels;  
  
namespace DataGridExampleWPF.Views  
{  
    public partial class DataGridExamplePage : Page  
    {  
        public DataGridExamplePage(DataGridExampleViewModel viewModel)  
        {  
            InitializeComponent();  
            DataContext = viewModel;  
        }  
    }  
}  

View Model

using System;  
using System.Collections.ObjectModel;  
using System.Windows.Input;  
using DataGridExampleWPF.Contracts.ViewModels;  
using DataGridExampleWPF.Core.Contracts.Services;  
using DataGridExampleWPF.Core.Models;  
  
using Microsoft.Toolkit.Mvvm.ComponentModel;  
using Microsoft.Toolkit.Mvvm.Input;  
  
namespace DataGridExampleWPF.ViewModels  
{  
    public class DataGridExampleViewModel : ObservableObject, INavigationAware  
    {  
        private readonly ISampleDataService _sampleDataService;  
        public ObservableCollection<SampleOrder> Source { get; } = new ObservableCollection<SampleOrder>();  
        public DataGridExampleViewModel(ISampleDataService sampleDataService)  
        {  
            _sampleDataService = sampleDataService;  
        }  
        private RelayCommand <SampleOrder>_cmdButton;  
        public ICommand CmdDelete => _cmdButton ??= new RelayCommand<SampleOrder>(cmdButton_method);  
        private void cmdButton_method(SampleOrder parameter)  
        {  
            Source.Remove(parameter);  
        }  
  
        public async void OnNavigatedTo(object parameter)  
        {  
            Source.Clear();  
            var data = await _sampleDataService.GetGridDataAsync();  
            foreach (var item in data)  
            {  
                Source.Add(item);  
            }  
        }  
    }  
}  

WinUI View

<Page  
    x:Class="DataGridExampleWinUI.Views.DataGridExamplePage"  
    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:controls="using:CommunityToolkit.WinUI.UI.Controls"   
    xmlns:viewmodels="using:DataGridExampleWinUI.ViewModels"   
    d:DataContext="{d:DesignInstance Type=viewmodels:DataGridExampleViewModel}"  
    x:Name="_page"  
    mc:Ignorable="d">  
  
    <Grid x:Name="ContentArea">  
        <controls:DataGrid  
            AutoGenerateColumns="False"  
            GridLinesVisibility="Horizontal"  
            ItemsSource="{x:Bind ViewModel.Source, Mode=OneWay}">  
            <controls:DataGrid.Resources>  
                <SolidColorBrush x:Key="DataGridColumnHeaderBackgroundColor" Color="Transparent" />  
            </controls:DataGrid.Resources>  
            <controls:DataGrid.Columns>  
                <!-- TODO: Replace column definitions to match real data. Consider adding Header values to Resources.resw. -->  
                <controls:DataGridTextColumn Binding="{Binding OrderID}" Header="OrderID" />  
                <controls:DataGridTextColumn Binding="{Binding OrderDate}" Header="OrderDate" />  
                <controls:DataGridTextColumn Binding="{Binding Company}" Header="Company" />  
                <controls:DataGridTextColumn Binding="{Binding ShipTo}" Header="ShipTo" />  
                <controls:DataGridTextColumn Binding="{Binding OrderTotal}" Header="OrderTotal" />  
                <controls:DataGridTextColumn Binding="{Binding Status}" Header="Status" />  
                <controls:DataGridTemplateColumn Header="Symbol">  
                    <controls:DataGridTemplateColumn.CellTemplate>  
                        <DataTemplate>  
                            <FontIcon  
                                HorizontalAlignment="Left"  
                                FontFamily="{ThemeResource SymbolThemeFontFamily}"  
                                Glyph="{Binding Symbol}"  
                                AutomationProperties.Name="{Binding SymbolName}" />  
                        </DataTemplate>  
                    </controls:DataGridTemplateColumn.CellTemplate>  
                </controls:DataGridTemplateColumn>  
                <controls:DataGridTemplateColumn Header="Delete">  
                    <controls:DataGridTemplateColumn.CellTemplate>  
                        <DataTemplate>  
                            <Button Command="{Binding DataContext.CmdDelete, ElementName=_page}" CommandParameter="{Binding}">Delete</Button>  
                        </DataTemplate>  
                    </controls:DataGridTemplateColumn.CellTemplate>  
                </controls:DataGridTemplateColumn>  
            </controls:DataGrid.Columns>  
        </controls:DataGrid>  
    </Grid>  
</Page>  

The WinUI ViewModel is the same as the WPF ViewModel.

Windows development | Windows App SDK
0 comments No comments
{count} votes

Accepted answer
  1. Anonymous
    2022-07-01T07:25:31.377+00:00

    Hello,

    Welcome to Microsoft Q&A!

    You can't do that in a community toolkit DataGrid. The implementation of the community toolkit DataGrid is different from the DataGrid of WPF. The binding can't find the DataContext that you defined. The columns you used are actually individual listviews. When you bind properties like OrderID to the column, actually it will get all the OrderID as a list and binding to the
    target column which is a listview. So in the DataGridTemplateColumn, it won't find the command in ViewModel. That's why when you click the button, the binding does nothing.

    Since you've already solutions to this, I'd suggest using that solution directly. And you could also submit a feature request about this in the Community ToolKit Github: https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues

    Thank you.


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.