14
votes

I'm using WPF and .NET 4.0. Recently in one of my programs I switched from using ListView with GridView to DataGrid.

I want to be able to select and highlight the whole row like I was able to do in ListView.

In ListView, when I click on the empty space right from the last column, I'm still able to select the row. The whole row is highlighted, not only the cells.

In DataGrid however, after setting SelectionMode="Single" and SelectionUnit="FullRow", the row is selectable only when I click on any cell in it, not in the empty space right to the last column.

How can I use the highlighting behavior from ListView here?

4

4 Answers

11
votes

There are two solutions:

  1. Set the width of the last column in the DataGrid to Width="*".
  2. The second solution is a workaround. Add additional empty column after the last colum (i.e. neither setting its Header nor Binding properties) and set its width to Width="*"

I personally prefer the first solution, it's more clean than the second one.

1
votes

There is one more solution if you can use code behind in your project. You can handle mouse down event of datagrid and programmatically select the clicked row:

 private void SomeGridMouseDown(object sender, MouseButtonEventArgs e)
    {
        var dependencyObject = (DependencyObject)e.OriginalSource;

        //get clicked row from Visual Tree
        while ((dependencyObject != null) && !(dependencyObject is DataGridRow))
        {
            dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
        }

        var row = dependencyObject as DataGridRow;
        if (row == null)
        {
            return;
        }

        row.IsSelected = true;
    }
1
votes

Based on previous comment from Aleksey L., here is the solution with DataGridBehavior class with FullRowSelect dependency property.

Behavior will automatically attach MouseDown event handler. Also, it will set "SelectionMode = DataGridSelectionMode.Single" so it will work properly with DataContext and SelectedItem binding.

XAML

<UserControl x:Class="WpfDemo.Views.Default"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:b="clr-namespace:WpfDemo.Behaviors"
         mc:Ignorable="d" 
         d:DesignHeight="300">

        <DataGrid b:DataGridBehavior.FullRowSelect="True">
              ...
        </DataGrid>             

DataGridBehavior class

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfDemo.Behaviors
{
    /// <summary>
    /// Extends <see cref="DataGrid"/> element functionality.
    /// </summary>
    public static class DataGridBehavior
    {
        #region - Dependency properties -

        /// <summary>
        /// Forces row selection on empty cell, full row select.
        /// </summary>
        public static readonly DependencyProperty FullRowSelectProperty = DependencyProperty.RegisterAttached("FullRowSelect",
            typeof(bool),
            typeof(DataGridBehavior),
            new UIPropertyMetadata(false, OnFullRowSelectChanged));

        #endregion

        #region - Public methods -

        /// <summary>
        /// Gets property value.
        /// </summary>
        /// <param name="grid">Frame.</param>
        /// <returns>True if row should be selected when clicked outside of the last cell, otherwise false.</returns>
        public static bool GetFullRowSelect(DataGrid grid)
        {
            return (bool)grid.GetValue(FullRowSelectProperty);
        }

        /// <summary>
        /// Sets property value.
        /// </summary>
        /// <param name="grid">Frame.</param>
        /// <param name="value">Value indicating whether row should be selected when clicked outside of the last cell.</param>
        public static void SetFullRowSelect(DataGrid grid, bool value)
        {
            grid.SetValue(FullRowSelectProperty, value);
        }

        #endregion

        #region - Private methods -

        /// <summary>
        /// Occurs when FullRowSelectProperty has changed.
        /// </summary>
        /// <param name="depObj">Dependency object.</param>
        /// <param name="e">Event arguments.</param>
        private static void OnFullRowSelectChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            DataGrid grid = depObj as DataGrid;
            if (grid == null)
                return;

            if (e.NewValue is bool == false)
            {
                grid.MouseDown -= OnMouseDown;

                return;
            }

            if ((bool)e.NewValue)
            {
                grid.SelectionMode = DataGridSelectionMode.Single;

                grid.MouseDown += OnMouseDown;
            }
        }

        private static void OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            var dependencyObject = (DependencyObject)e.OriginalSource;

            while ((dependencyObject != null) && !(dependencyObject is DataGridRow))
            {
                dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
            }

            var row = dependencyObject as DataGridRow;
            if (row == null)
            {
                return;
            }

            row.IsSelected = true;
        }

        #endregion
    }
}
1
votes

Some of these solutions would not work in my case. So what I did is re-route the event to the last cell of the row (which is the closest to the click). You can add that to a Behavior or in a code-behind:

_dataGrid.MouseLeftButtonDown += (sender, args) =>
{
        //If click was on row border, reroute to latest cell of row
        if (!(args.OriginalSource is Border border) || !(border.Child is SelectiveScrollingGrid grid)) return;

        var cellsPresenter = grid.Children.OfType<DataGridCellsPresenter>().FirstOrDefault();
        if (cellsPresenter == null || cellsPresenter.Items.Count < 1) return;

        var lastCell = (DataGridCell)cellsPresenter.ItemContainerGenerator.ContainerFromIndex(cellsPresenter.Items.Count - 1);
        lastCell.RaiseEvent(args);
};