Search code examples
wpfeventstogglewpfdatagrid

WPF DataGrid toggle selection mode & Button in CellTemplate


I'm building a POS application, and I want the end user to be able to have a toggle selection mode for the datagrid, I.E. they can click on multiple rows and each clicked item will accumulate on the SelectedItems property - also clicking on a row that was already selected will deselect the row. I found this code in another stackoverflow question:

<DataGrid.Resources>
    <Style TargetType="{x:Type DataGridCell}">
        <EventSetter Event="PreviewMouseLeftButtonDown" Handler="DoCheckRow" />
    </Style>
</DataGrid.Resources>

public void DoCheckRow(object sender, MouseButtonEventArgs e)
{
    DataGridCell cell = sender as DataGridCell;
    if (cell != null && !cell.IsEditing)
    {
        DataGridRow row = VisualHelpers.TryFindParent<DataGridRow>(cell);
        if (row != null)
        {
            row.IsSelected = !row.IsSelected;
            e.Handled = true;
            Debug.WriteLine(sender);
        }
    }
}

That effectively gives me what I want as far as the toggle selection mode, however, when I add a button as a CellTemplate, the buttons command isn't fired when clicked because I'm setting e.Handled = true; in the above code which stops the event bubble. Is there a way that I can accommodate both?


Solution

  • I was able to solve it by using some helper functions to find the visual child / parent and some hit testing:

    public void DoCheckRow(object sender, MouseButtonEventArgs e)
    {
        DataGridCell cell = sender as DataGridCell;
        if (cell != null && !cell.IsEditing)
        {
            DataGridRow row = VisualHelpers.TryFindParent<DataGridRow>(cell);
            if (row != null)
            {
                Button button = VisualHelpers.FindVisualChild<Button>(cell, "ViewButton");
    
                if (button != null)
                {
                    HitTestResult result = VisualTreeHelper.HitTest(button, e.GetPosition(cell));
    
                    if (result != null)
                    {
                        // execute button and do not select / deselect row
                        button.Command.Execute(row.DataContext);
                        e.Handled = true;
                        return;
                    }
                }
    
                row.IsSelected = !row.IsSelected;
                e.Handled = true;
            }
        }
    }
    

    Granted its not the most elegant solution, but it works with the MVVM pattern that I use.