Using MVVM to Remove Rows from a WPF Datagrid
Considering the popularity of the MVVM (Model-View-ViewModel) design pattern, WPF developers are always looking to implement every possible feature in XAML.
This results in everything seamlessly bound between the View (XAML) and the ViewModel without requiring disconnected code in the View's "code-behind" (the C# or VB.NET code that initializes the View class).
In this code example, we'll show how to configure a Button embedded within our WPF datagrid control, FlexGrid, to be bound to the ViewModel. The Button will pass the row's data object so that we can perform an operation on the data, such as removing the row. In this process, we will be:
Ready to Get Started? Download ComponentOne Today!
The sample referenced in this blog was previously created in How to Create CommandBinding a WPF Datagrid, but we will make modifications in this scenario.
Adding a Button to FlexGrid
You can display a Button (or any UI element) in FlexGrid cells by customizing the cell template. The easiest way to do this is in XAML by defining all columns and setting AutoGenerateColumns to false.
<c1:C1FlexGrid ItemsSource="{Binding EmployeeList}" AutoGenerateColumns="False">
<c1:C1FlexGrid.Columns>
<c1:Column Binding="{Binding FirstName}"/>
<c1:Column Binding="{Binding LastName}"/>
<c1:Column Binding="{Binding Age}"/>
<c1:Column Header="Remove">
<c1:Column.CellTemplate>
<DataTemplate>
<Grid>
<Button Content="Delete Record" Command="TO DO"/>
</Grid>
</DataTemplate>
</c1:Column.CellTemplate>
</c1:Column>
</c1:C1FlexGrid.Columns>
</c1:C1FlexGrid>
The Button is not completely wired up yet. We'll come back to it once we set up the ViewModel and Command. For more information on getting started with FlexGrid, check out our online documentation.
Creating the ViewModel
The ViewModel acts as the data source for our View, so anything data-related that you want to dynamically display in your View should be controlled through the ViewModel. Our ViewModel in this stripped-down sample needs two things: A data collection, and a command to remove a data row.
For the data collection, we expose an Employee list. Typically, your Views and ViewModels will implement INotifyPropertyChanged, so that you can pass updates to any property to the View.
Another alternative, if working with lists, is to use the ObservableCollection. This includes built-in update notification for any UI bound to an ObservableCollection.
class EmployeeListView
{
ICommand m_RowRemoveCommand;
ObservableCollection<Employee> _list;
public EmployeeListView()
{
}
public ObservableCollection<Employee> EmployeeList
{
get
{
if (_list == null)
{
_list = new ObservableCollection<Employee>();
_list.Add(new Employee() { FirstName = "John", LastName = "Wayne", Age = 35 });
_list.Add(new Employee() { FirstName = "Maximus", LastName = "Decimus", Age = 33 });
_list.Add(new Employee() { FirstName = "Alexander", LastName = "Conklin", Age = 42 });
_list.Add(new Employee() { FirstName = "Jason", LastName = "Bourne", Age = 66 });
}
return _list;
}
}
public ICommand RemoveCommand
{
get
{
if (m_RowRemoveCommand == null)
{
m_RowRemoveCommand = new DelegateCommand(CanRemoveRow, RemoveRow);
}
return m_RowRemoveCommand;
}
}
private void RemoveRow(object parameter)
{
int index = EmployeeList.IndexOf(parameter as Employee);
if (index > -1 && index < EmployeeList.Count)
{
EmployeeList.RemoveAt(index);
}
}
private bool CanRemoveRow(object parameter)
{
return true;
}
}
For the command, we expose an ICommand named RemoveCommand. This command takes two delegate parameters for an Execute method and a CanExecute method. It's within these two methods that we implement our business logic - 100% here in the ViewModel and not in the View.
The DelegateCommand is a common implementation as seen below. Just copy this into any project and use it for your MVVM commands.
class DelegateCommand : ICommand
{
Predicate<object> canExecute;
Action<object> execute;
public DelegateCommand(Predicate<object> _canexecute, Action<object> _execute): this()
{
canExecute = _canexecute;
execute = _execute;
}
public DelegateCommand()
{ }
public bool CanExecute(object parameter)
{
return canExecute == null ? true : canExecute(parameter);
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
execute(parameter);
}
}
Binding the Command
Now that our ViewModel has the RemoveCommand defined, the RemoveRow execute method implemented (the actual logic to remove a row), and the employee list is ObservableCollection (so updates will be automatically shown in the view), we can complete the command binding.
The Button in the View can be bound to our ViewModel by setting its Command property. In this case RemoveCommand.
<c1:Column Header="Remove">
<c1:Column.CellTemplate>
<DataTemplate>
<Grid>
<Button Content="Delete Record" Command="{Binding Path=DataContext.RemoveCommand,RelativeSource={RelativeSource AncestorType={x:Type c1:C1FlexGrid}}}" CommandParameter="{Binding}"/>
</Grid>
</DataTemplate>
</c1:Column.CellTemplate>
</c1:Column>
Here we also pass the bound row of the FlexGrid as a parameter. When the binding is just "{Binding}," that means it takes the current data context. For a row in FlexGrid, that context becomes the data record for that row. Looking above at the RemoveRow implementation, you can see how the parameter is used.
Binding to the ViewModel
Before we go, let me explain the most critical part of all of this - connecting the View to the ViewModel. There are many ways to do it, but the key is to set the View's DataContext property to an instance of your ViewModel. You can do this in code or in XAML as shown below:
<Window
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:local="clr-namespace:CommandBindingWpf"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:c1="http://schemas.componentone.com/winfx/2006/xaml" x:Class="CommandBindingWpf.MainWindow"
mc:Ignorable="d" xmlns:ViewModel="clr-namespace:CommandBindingWpf.ViewModel"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<ViewModel:EmployeeListView />
</Window.DataContext>
Download the complete sample here
This sample code combines topics from this blog and the previous one, How to Create CommandBinding a WPF Datagrid. An extra feature shown in the sample is how to implement IEquatable so that we can use the IndexOf method to retrieve the exact Employee.
Ready to Get Started? Download ComponentOne Today!