WPF DataGridRow Double Click With MVVM

On my current project, we need to open a record for editing when the DataGridRow is double clicked. Unfortunately, the WPF DataGrid doesn’t support this functionality out of the box. There are, however, a couple ways to make this happen.

One way is to handle MouseDoubleClick event on the DataGrid. We are using Caliburn.Micro, so it is easy to get the MouseDoubleClick event to route to our view model. The view model can then assume that the selected item is to be edited. The problem with this is that double clicking anywhere on the DataGrid will trigger this event, including the scrollbars. That means that quickly scrolling down the items could trigger a double click and cause whatever item was last selected to be edited, which is unacceptable.

What we needed was to somehow handle the MouseDoubleClick event on each DataGridRow, make sure the one being double clicked on is actually selected, then call tell the view model to edit it.

I ultimately came up with two solutions. One uses a little code-behind and applies only to one case at a time. The other uses an attached DependencyProperty and is general enough to apply to all cases.

Using Code-behind

The code-behind solution may break the MVVM pattern, depending on your point of view. I feel that using code-behind makes sense in cases where you have logic that is specific to your how your view represents the UI.

First, you need to create the MouseDoubleClick event handler in the code-behind.

protected void OnMouseDoubleClick(object sender, EventArgs args)
{
    var row = sender as DataGridRow;
    if (row != null && row.IsSelected)
    {
        var viewModel = (MainViewModel)DataContext;
        viewModel.Edit();
    }
}

Next, you need the wire up each DataGridRow to use that handler for MouseDoubleClick events. You can do that with styling.

<DataGrid
    ItemsSource="{Binding People}"
    SelectedItem="{Binding SelectedPerson}"
    IsReadOnly="True">
    <DataGrid.ItemContainerStyle>
        <Style TargetType="DataGridRow">
            <EventSetter Event="MouseDoubleClick"
                        Handler="OnMouseDoubleClick" />
        </Style>
    </DataGrid.ItemContainerStyle>
</DataGrid>

That’s it. This solution is pretty straight-forward. However, there are two problems with the code-behind solution.

The first problem is that this solution doesn’t scale. What I mean by that is that you have to re-implement it every time you want to use it.

The second problem is that it uses code-behind. As I said earlier, I believe the use of code-behind has its place. However, I prefer to stay away from it if I can.

Using Attached DependencyProperty

The attached DependencyProperty solution uses a helper class so that it can be reused across your application. It also assumes that your DataGrid’s DataContext is your view model. If that is not the case, you will need to modify it.

public sealed class RowDoubleClickHandler : FrameworkElement
{
    public RowDoubleClickHandler(DataGrid dataGrid)
    {
        MouseButtonEventHandler handler = (sender, args) =>
        {
            var row = sender as DataGridRow;
            if (row != null && row.IsSelected)
            {
                var methodName = GetMethodName(dataGrid);

                var dataContextType = dataGrid.DataContext.GetType();
                var method = dataContextType.GetMethod(methodName);
                if (method == null)
                {
                    throw new MissingMethodException(methodName);
                }

                method.Invoke(dataGrid.DataContext, null);
            }
        };

        dataGrid.LoadingRow += (s, e) =>
            {
                e.Row.MouseDoubleClick += handler;
            };

        dataGrid.UnloadingRow += (s, e) =>
            {
                e.Row.MouseDoubleClick -= handler;
            };
    }

    public static string GetMethodName(DataGrid dataGrid)
    {
        return (string)dataGrid.GetValue(MethodNameProperty);
    }

    public static void SetMethodName(DataGrid dataGrid, string value)
    {
        dataGrid.SetValue(MethodNameProperty, value);
    }

    public static readonly DependencyProperty MethodNameProperty = DependencyProperty.RegisterAttached(
        "MethodName",
        typeof(string),
        typeof(RowDoubleClickHandler),
        new PropertyMetadata((o, e) =>
        {
            var dataGrid = o as DataGrid;
            if (dataGrid != null)
            {
                new RowDoubleClickHandler(dataGrid);
            }
        }));
}

This helper class ties into the DataGrid’s LoadingRow event and uses reflection to find a specified method on the DataContext and execute it. All you have left to do is use it.

<DataGrid
    ItemsSource="{Binding People}"
    SelectedItem="{Binding SelectedPerson}"
    IsReadOnly="True"
    helpers:RowDoubleClickHandler.MethodName="Edit" />

All you need to do is provide the name of the method on your view model to execute when the double click has occurred and the rest is taken care of.

Conclusion

While it’s unfortunate that WPF doesn’t provide this functionality right out of the box, it is not difficult to implement it yourself. Silverlight suffers from this same shortcoming. This solution won’t work as-is for Silverlight. It would need to be refactored into using Behaviors instead of an attached DependencyProperty.

I have posted a complete solution on github: https://github.com/brentedwards/DataGridRow_DoubleClick

Happy coding!

Share

Tags : , ,

If you enjoyed this post, please consider to leave a comment or subscribe to the feed and get future articles delivered to your feed reader.

5 Comments

Leave Comment