WPF DataGrid full row selection
Asked Answered
E

4

14

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?

Elemi answered 1/4, 2011 at 21:0 Comment(0)
H
12

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 an additional empty column after the last column (i.e. neither setting its Header nor Binding properties) and set its width to Width="*"

I personally prefer the first solution; it's cleaner than the second one.

Horan answered 1/4, 2011 at 21:25 Comment(5)
A note of caution when setting the last column to Width="*": The horizontal scrollbar will no longer be visible if it is needed.Gardner
@Metro Smurf: A workaround is to set a feasible MinWidth for each column, moreover, you can set a tool-tip for each column to display the content in case its value is greater than the available width.Horan
even if you put a MinWidth on each column the last column with the Width="*" will have hidden content that is unreachable by the horizontal scroll bar. Don't get me wrong, its a good method, but there are some minor side-effects.Gardner
@Metro Smurf: Yeah, I total agree with you, but you can mitigate this by using tool-tips, and I think it's better that using a scroll-bar. Using tool-tips if the user want to see the hidden parts of the cell content he needs just to hover over it, on the other hand, using the scroll bar, the user has to drag the scroll-bar all the way to see the hidden parts of the cell content or even whole hidden columns.Horan
@Metro Smurf: It's a tradeoff. In my opinion, if you have a reasonable number of columns (not too much) its better not to use scroll-bars, but some times you are forced to display a large number of columns, in this case it's better to use a scroll-bar in-order for all the columns to have reasonable width, in this case you won't have the empty space that appears after the last column.Horan
E
2

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;
    }
Exsect answered 18/8, 2013 at 17:5 Comment(1)
Fast and clean solution, you probably want to add SomeGrid.UnselectAll(); just before selecting the new row to avoid multiple selections.Presto
M
2

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
    }
}
Marasmus answered 27/5, 2016 at 11:3 Comment(1)
Adding my 2 cents to this great response: if you use InputBindings in the control, it won't work anymore because InputBindings needs focus to work properly. To fix it you need to manually set focus on the DataGrid: var dg = sender as DataGrid; dg.Focus();Corby
I
1

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);
};
Indissoluble answered 6/2, 2021 at 22:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.