WPF DataGrid row click command
By Mirek on (tags: DataGrid, mvvm, WPF, categories: code)The Windows Presentation Foundation DataGrid control offers a huge possibilities regarding displaying tabular data. However one functionality I was always missing in data grid was the possibility to fire a command when user clicks a row.
Sure we can bind to the SelectedItem property which is exposed by the data grid control, but this is not the best way to fire a command from a property setter. In this post I will show you how it can be handled using attached property.
Assume we have simple data grid
And we have a command in our view model we want to be fired when the row is cliecked. Lets start by creating a static data grid helper class which exposes an attached property
public static class DataGridHelper
{
public static ICommand GetRowCommand(DependencyObject obj)
=> (ICommand)obj.GetValue(RowCommandProperty);
public static void SetRowCommand(DependencyObject obj, ICommand value)
=> obj.SetValue(RowCommandProperty, value);
public static readonly DependencyProperty RowCommandProperty =
DependencyProperty.RegisterAttached("RowCommand", typeof(ICommand), typeof(DataGridHelper),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(RowCommandPropertyChanged)));
...
}
The property is of type ICommand and represents the actuall command we want to fire on row click
private static void RowCommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var dg = d as DataGrid ?? throw new ApplicationException("Not applicable!");
if (e.NewValue != null)
{
WeakEventManager<DataGrid, MouseButtonEventArgs>.AddHandler(dg, nameof(DataGrid.MouseLeftButtonUp), new EventHandler<MouseButtonEventArgs>(DataGrid_MouseLeftButtonUp));
}
}
whenever the property is bound to the grid we register the safe event handler to the MouseLeftButtonUp event on our data grid. Notice, that we use here WeakEventManager to prevent memory leaks. Then on each mouse clieck we do some checks
private static void DataGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var dg = sender as DataGrid;
var dep = (DependencyObject)e.OriginalSource;
if (dep is ButtonBase) return;
/* iteratively traverse the visual tree to find the row */
while (!(dep is DataGridRow))
{
dep = VisualTreeHelper.GetParent(dep);
/* if we are not inside any row then skip */
if (dep is DataGrid) return;
}
var row = dep as DataGridRow;
var command = GetRowCommand(dg);
if (command != null)
{
if (command.CanExecute(row.DataContext))
command.Execute(row.DataContext);
}
}
The OriginalSource object represents the actuall element that was first hit when user did a mouse click. It can be a text block, button or what ever we have in the datagrid. That’s why in the while loop we have to find the closest DataGridRow by going deeper inthe visua tree. Notice that we first exclude the case when user clicks on a button. It means the button is rendered in data grid row and probably has its own click event handled separatelly.
Then we need to get the command property from the data grid and fire it, but since the command is registered on a grid, it does not have a row context. To fix that we can pass a row’s DataContext as a command parameter. We could also retreive a row index or use the SelectedItem property of the grid.
Cheers