Yelnic
Yelnic

Reputation: 551

WPF datagrid collapse details row on click

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?

Upvotes: 5

Views: 12254

Answers (4)

Jinjinov
Jinjinov

Reputation: 2683

This combines the answer from Grafix with the answer from Prethen.

Use it if you want the row detail to toggle only when the row is already selected:

private void DataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (sender is DataGrid dataGrid && 
        e.OriginalSource is FrameworkElement frameworkElement && 
        frameworkElement.DataContext == dataGrid.SelectedItem)
    {
        if (dataGrid.RowDetailsVisibilityMode == DataGridRowDetailsVisibilityMode.VisibleWhenSelected)
            dataGrid.RowDetailsVisibilityMode = DataGridRowDetailsVisibilityMode.Collapsed;
        else
            dataGrid.RowDetailsVisibilityMode = DataGridRowDetailsVisibilityMode.VisibleWhenSelected;
    }
}

Upvotes: 2

Prethen
Prethen

Reputation: 277

I came up with a different way, but not a "proper" MVVM way since it uses code behind (as does some of the code in the proposed answers above) but it does the trick with just a few lines of code.

By coding against the PreviewMouseUp event I was able to get the exact behavior I needed. The code ensures that you've actually clicked on something in the grid and to collapse it has to be the same row already opened.

 private void UIElement_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        DataGrid grid = sender as DataGrid;

        if (grid != null)
        {
            FrameworkElement element = e.OriginalSource as FrameworkElement;

            if (element?.DataContext is MyCustomObject)
            {
                if (grid.SelectedItem == (MyCustomObject) ((FrameworkElement) e.OriginalSource).DataContext)
                {
                    grid.SelectedIndex = -1;
                    e.Handled = true;
                }
            }
        }
    }

Upvotes: 1

Stephen Holt
Stephen Holt

Reputation: 2368

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;
    }
}

Upvotes: 4

Grafix
Grafix

Reputation: 726

Why don't you use the sender param? If the event is defined on the DataGrid, the sender is always the DataGrid! Use a safe cast en check for null to be safe, but that should do the trick.

The code seems unnecessary complicated as you are working back from the original source to your DataGrid through the visual tree.

        private void dataGridMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        DataGrid dg = sender as DataGrid;
        if (dg == null)
            return;
        if (dg.RowDetailsVisibilityMode == DataGridRowDetailsVisibilityMode.VisibleWhenSelected)
            dg.RowDetailsVisibilityMode = DataGridRowDetailsVisibilityMode.Collapsed;
        else
            dg.RowDetailsVisibilityMode = DataGridRowDetailsVisibilityMode.VisibleWhenSelected;
    }

Upvotes: 1

Related Questions