WPF datagrid collapse details row on click

Yelnic picture Yelnic · Nov 13, 2012 · Viewed 10.7k times · Source

I needed to collapse the details row of a WPF DataGrid when a user clicked on it, and re-display it when they clicked again. I also wanted to preserve the DataGridRoDetailsVisibilityMode of VisibleWhenSelected, using single selection.

I came up with this solution, based off of this post elsewhere: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/0a45b3a7-46d0-45a9-84b2-0062f07f6fec#eadc8f65-fcc6-41df-9ab9-8d93993e114c

    private bool _rowSelectionChanged;


    private void dgCompletedJobs_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        _rowSelectionChanged = true;
    }

    private void dgCompletedJobsMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        DependencyObject dep = (DependencyObject)e.OriginalSource;

        //navigate up the tree
        while (dep != null &&
            !(dep is DataGridCell) &&
            !(dep is DataGridColumnHeader))
        {
            dep = VisualTreeHelper.GetParent(dep);
        }

        if (dep == null)
        {
            return;
        }

        DataGridCell dgc = dep as DataGridCell;
        if (dgc != null)
        {
            //navigate further up the tree
            while (dep != null && !(dep is DataGridRow))
            {
                dep = VisualTreeHelper.GetParent(dep);
            }

            DataGridRow dgr = dep as DataGridRow;
            DataGrid dg = sender as DataGrid;
            if (dg != null && dgr != null)
            {
                if (dgr.IsSelected && !_rowSelectionChanged)
                {
                    dg.RowDetailsVisibilityMode =
                        (dg.RowDetailsVisibilityMode == DataGridRowDetailsVisibilityMode.VisibleWhenSelected)
                            ? DataGridRowDetailsVisibilityMode.Collapsed
                            : DataGridRowDetailsVisibilityMode.VisibleWhenSelected;
                }
                else
                {
                    dg.RowDetailsVisibilityMode = DataGridRowDetailsVisibilityMode.VisibleWhenSelected;
                }
            }
        }
        _rowSelectionChanged = false;
    }

This appears to solve my problem nicely, but I have a haunting suspicion that this could be done more simply and elegantly, especially since I'm using MVVM on this project. However, I see this as an acceptable usage of event-driven code-behind, because it's purely presentation logic.

Does anyone have a cleaner solution?

Answer

Stephen Holt picture Stephen Holt · Nov 13, 2012

To do this with "proper" MVVM, you should bind the RowDetailsVisibilityMode to a property on the view model:

<DataGrid x:Name="dgCompletedJobs" RowDetailsVisibilityMode="{Binding RowDetailsVisible}"/>

Your view model property would be something like:

private DataGridRowDetailsVisibilityMode _rowDetailsVisible;
public DataGridRowDetailsVisibilityMode RowDetailsVisible
{
    get { return _rowDetailsVisible; }
    set {
        _rowDetailsVisible = value;
        if (PropertyChanged != null) {
             PropertyChanged(this, new PropertyChangedEventArgs("RowDetailsVisible"));
        }
    }
}

To link the mouse click event to the changing of the property, you could either do some fancy attached behaviour commanding as indicated here, or just use code behind to call the view model directly (I often do this myself for simple tasks):

private void dgCompletedJobsMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    Window1ViewModel viewModel = (Window1ViewModel)DataContext;
    if (viewModel.RowDetailsVisible == DataGridRowDetailsVisibilityMode.Collapsed) {
        viewModel.RowDetailsVisible = DataGridRowDetailsVisibilityMode.VisibleWhenSelected;
    } else {
        viewModel.RowDetailsVisible = DataGridRowDetailsVisibilityMode.Collapsed;
    }
}