Reputation: 631
I am using the Microsoft.Toolkit.Mvvm nuget package for building out a WPF application on .NET Core 3.1.
I'm having an issue with binding the CommandParameter of a button within a DataGridTemplateColumn of a Datagrid.
I have a RelayCommand defined on my viewmodel, using IRelayCommand, and have a CanExecute method defined that takes a parameter. I then have the CommandParameter of the button binding bound to the data of the datagrid row.
During runtime, I am seeing the CanExecute method called for every row of my datagrid, however the parameter value is always null. If I have 5 items in my ObservableCollection, I see CanExecute called 5 times, 3 items... 3 times. In all cases the value is null.
Below is the code for my viewmodel that creates an ObservableCollection of TestModel types. A RelayCommand is created, and the criteria of the CanExecute is dumbed down to just comparing the name property of the databound model.
What I'd expect here is that for all rows except the row with name "Test2", the bound button would be enabled. The button in Test2's row would be disabled.
Here is my viewmodel, and related model class.
public class MainWindowVM : ObservableObject, IViewModel
{
public MainWindowVM()
{
TestData = new ObservableCollection<TestModel>
{
new TestModel() {Name = "Test1"},
new TestModel() {Name = "Test2"},
new TestModel() {Name = "Test3"}
};
DeleteCommand = new RelayCommand<TestModel>(Delete, CanDelete);
}
public IRelayCommand<TestModel> DeleteCommand { get; }
private void Delete(TestModel model)
{
//do stuff
}
private bool CanDelete(TestModel model)
{
if (model.Name == "Test2")
{
return false;
}
return true;
}
public ObservableCollection<TestModel> TestData
{
get => _testData;
set => SetProperty(ref _testData, value);
}
private ObservableCollection<TestModel> _testData;
}
public class TestModel : ObservableObject
{
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
private string _name;
}
And my xaml for the Datagrid
<Grid>
<DataGrid ItemsSource="{Binding Path=TestData}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Username"
Binding="{Binding Path=Name}"
MinWidth="180" />
<DataGridTemplateColumn Header="" Width="160">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding Path=DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}" Content="Delete"
Width="100" >
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
What am I missing here? I've used this approach countless times in other WPF applications. This is my first time using this toolkit (I've used MVVMLight previously), and the first time building WPF on .NET Core. Is this a change in WPF on .NET Core? Something different between the two MVVM toolkits?
Any help or direction you can provide would be much appreciated, thank you.
Upvotes: 2
Views: 388
Reputation: 169240
What am I missing here?
The fact that CanExecute
is called before the CommandParameter
has been set.
Changing the binding to this seems to work for me:
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"
Also note that unless you set the CanUserAddRows
property to of the DataGrid
to false
, your CanDelete
will also be called with an MS.Internal.NamedObject
which will cause the application to crash unless you change the type argument of your RelayCommand
:
public class MainWindowVM : ObservableObject
{
public MainWindowVM()
{
TestData = new ObservableCollection<TestModel>
{
new TestModel() {Name = "Test1"},
new TestModel() {Name = "Test2"},
new TestModel() {Name = "Test3"}
};
DeleteCommand = new RelayCommand<object>(Delete, CanDelete);
}
public IRelayCommand<object> DeleteCommand { get; }
private void Delete(object parameter)
{
if (parameter is TestModel model)
{
//do stuff
}
}
private bool CanDelete(object parameter)
{
if (parameter is TestModel model && model.Name == "Test2")
{
return false;
}
return true;
}
public ObservableCollection<TestModel> TestData
{
get => _testData;
set => SetProperty(ref _testData, value);
}
private ObservableCollection<TestModel> _testData;
}
Upvotes: 3