0

I have a DataGrid in a WPF app where I want to allow the user to perform actions on a selected item in the grid. I am hosting the controls for those actions in the DetailRow of the DataGrid.

When the user clicks a row in the grid, I expand it and reveal the controls. When they are done, I'd like them to be able to click the row again to hide that detail row. The problem I'm having is that I can't get WPF to distinguish between a click on the row (effectively the "header" of the row detail) and the row detail area where all my controls are. To WPF, a click on the detail means a click on the row, and my code considers that to be a toggle, and so it hides the row.

I'm doing the toggling in the code-behind, but I am using MVVM for the app, so if there is a more decoupled approach I could take, I'd be curious to know. Here is some sample code:

ViewModel and Model:

namespace StackOverflow
{
    public class ViewModel
    {
        public ObservableCollection<MyClass> MyCollection { get; set; }

        public ViewModel()
        {
            MyCollection = new ObservableCollection<MyClass>
            {
                new MyClass(0, "Foo"),
                new MyClass(1, "Bar")
            };
        }
    }

    public class MyClass
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public MyClass(int id, string name)
        {
            Id = id;
            Name = name;
        }
    }
}

View:

<Window x:Class="StackOverflow.MainWindow"
    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:StackOverflow"
    mc:Ignorable="d"
    Title="MainWindow"
    Height="350"
    Width="525">
<Window.Resources>
    <local:ViewModel x:Key='MyVM'></local:ViewModel>
</Window.Resources>
<Grid DataContext='{StaticResource MyVM}'>
    <DataGrid HorizontalAlignment='Center' Width='200' Margin='20'
              RowDetailsVisibilityMode='Collapsed'
              ItemsSource='{Binding MyCollection}'>
        <DataGrid.RowStyle>
            <Style TargetType="{x:Type DataGridRow}">
                <EventSetter Event="UIElement.PreviewMouseLeftButtonDown"
                             Handler="OnRowLeftClicked" />
            </Style>

        </DataGrid.RowStyle>
        <DataGrid.RowDetailsTemplate>
            <DataTemplate>
                <StackPanel Background='LightBlue' HorizontalAlignment='Center' Orientation='Horizontal' Height='100'>
                    <Button VerticalAlignment='Center'
                            HorizontalAlignment='Center'>Do stuff to this specific item</Button>
                </StackPanel>
            </DataTemplate>

        </DataGrid.RowDetailsTemplate>
    </DataGrid>

</Grid>

Codebehind

namespace StackOverflow
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private DataGridRow lastRowClicked;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void OnRowLeftClicked(object sender, RoutedEventArgs e)
        {
            var clickedRow = (DataGridRow)sender;

            //If a Todo has been clicked before for this selected user, we need to decide whether a new Todo has been selected.
            if (lastRowClicked != null)
            {
                //If the Todo that was clicked last time is the same as the one that was just clicked, toggle its visibility --- this is where problem lies
                if (lastRowClicked == clickedRow)
                {
                    if (clickedRow.DetailsVisibility == Visibility.Collapsed)
                        clickedRow.DetailsVisibility = Visibility.Visible;
                    else
                        clickedRow.DetailsVisibility = Visibility.Collapsed;
                }
                //If this Todo was not clicked last time, hide the last Todo and show this current one.
                else
                {
                    lastRowClicked.DetailsVisibility = Visibility.Collapsed;
                    clickedRow.DetailsVisibility = Visibility.Visible;
                }
            }
            //If this is the first Todo that was clicked for this user, we can show it without collapsing another row.
            else
            {
                clickedRow.DetailsVisibility = Visibility.Visible;
            }

            //in any case, save the currently clicked row for next time
            lastRowClicked = clickedRow;
        }
    }
}

Update:

This was marked as a possible duplicate for I need the Expand / Collapse for RowDetailsTemplate

Although it is a very similar problem, the solution in the other question involved using an expander button as a column in the datagrid. I would prefer to simply click anywhere the datagrid row and depending on whether the row was clicked before, expand or collapse the detail pane. As a backup, I would have the user left click to expand and right click to contract, which is what I'm doing now in my project.

  • When the child opens, can you set IsHitTestVisible = false on the parent? – 15ee8f99-57ff-4f92-890c-b56153 Oct 06 '17 at 02:16
  • Possible duplicate of [I need the Expand / Collapse for RowDetailsTemplate](https://stackoverflow.com/questions/3829137/i-need-the-expand-collapse-for-rowdetailstemplate) – ASh Oct 06 '17 at 06:20
  • @EdPlunkett The problem with changing IsHitTestVisible is that the row and the detail are treated as the same object. So if I set that value to false, I won't be able to contract the detail later. What I could maybe do is replace the template of the datagrid row and surround the cells with a button and set my eventhandlers on that button (rather than the entire row, which includes the details), but that seems like a lot of work. –  Oct 06 '17 at 13:11

0 Answers0