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.