How to select a row when clicking in an empty column?

699 Views Asked by At

The XAML markup has a DataGrid that is partially filled with columns.
Behind the data columns there is an empty column that fills the remaining width of the DataGrid.
When the user clicks on a row in this space, the row is not selected.
Can this be changed somehow?

enter image description here

Clarification of the question in connection with the answer from @BionicCode.

An empty column is not a cell. This is understandable and does not raise questions.
BUT!!! It is a ROW!
It's easy enough to make sure of this if you set the processing of the row event (there is an example below).
The example given at the beginning of the question is a simplification of a real task to demonstrate the question.
In a real task, apart from the "last empty column", there is also a problem with the cells in which the margin for the DataGridCell is set.
In this case, there is a row background around the cell.
Select a line when clicking on this background does not work.
And in the selected line, this background is not highlighted.
Since this is a common problem with highlighting in an "empty column", I did not write about it in the question initially.

<Window x:Class="SelectRow.SelectRowWind"
        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:SelectRow"
        xmlns:spec="clr-namespace:System.Collections.Specialized;assembly=System"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        Title="SelectRowWind" Height="450" Width="800">
    <Window.Resources>
        <spec:StringCollection x:Key="source">
            <sys:String>First</sys:String>
            <sys:String>Second</sys:String>
        </spec:StringCollection>
    </Window.Resources>
    <Grid>
        <DataGrid ItemsSource="{DynamicResource source}">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding}"/>
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate DataType="{x:Type sys:String}">
                            <TextBlock Text="{Binding Length}"
                                       Background="Aqua" Padding="5"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellStyle>
                        <Style TargetType="DataGridCell">
                            <Setter Property="Margin" Value="10"/>
                        </Style>
                    </DataGridTemplateColumn.CellStyle>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
            <DataGrid.ItemContainerStyle>
                <Style TargetType="DataGridRow">
                    <EventSetter Event="MouseDoubleClick" Handler="OnMouseDoubleClick"/>
                </Style>
            </DataGrid.ItemContainerStyle>
        </DataGrid>
    </Grid>
    <x:Code>
        <![CDATA[
        private void OnMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            MessageBox.Show($"DoubleClick in Row: \"{((FrameworkElement)sender).DataContext}\"");
        }
        ]]>
    </x:Code>
</Window>

enter image description here

1

There are 1 best solutions below

7
BionicCode On

The highlighted empty space is not a real row or column in the sense that it is represented as DataGridCell or DataGridColumn. The grid lines are rendered using the Grid.ShowGridLines feature.

Of course, you could capture the mouse position and map it to a row, using the framework's hit-test API by calling VisualTreeHelper.HitTest.

Stretch the last column

A simpler solution would be to to allow the last column to occupy the remaining space by setting DataGridColumn.Width to *.

If your columns are defined explicitly, then you can set it locally on the column element:

<DataGrid AutoGenerateColumns="False">
  <DataGrid.Columns>
    <DataGridTextColumn />
    <DataGridTextColumn Header="Length"
                        Width="*" />
  </DataGrid.Columns>
</DataGrid>

Otherwise you should handle the DataGrid.AutoGeneratedColumns event:

XAML

<DataGrid AutoGenerateColumns="True"
          AutoGeneratedColumns="DataGrid_AutoGeneratedColumns" />

Code-behind

private void DataGrid_AutoGeneratedColumns(object sender, EventArgs e)
{
  var dataGrid = sender as DataGrid;
  double totalColumnWidth = dataGrid.Columns.Sum(column => column.ActualWidth);
  if (totalColumnWidth < dataGrid.ActualWidth)
  {
    dataGrid.Columns.Last().Width = new DataGridLength(1, DataGridLengthUnitType.Star);
  }
}

Alternatively override the default styles

This solution is not recommended as it requires the reimplemention of the selection behavior. Allowing the last column to stretch is far less complex and doesn't risk to break the default behavior.

DataGridRow click handler

private void DataGridRow_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
  var row = sender as DataGridRow;

  // TODO::Implement visual tree traversal to lookup the DataGrid
  // In case of Cell based selection you would alos have to lookup the cell host
  if (row.TryFindVisualParentElement(out DataGrid owner))
  {
    if (owner.SelectionUnit == DataGridSelectionUnit.FullRow)
    {
      if (owner.SelectionMode == DataGridSelectionMode.Extended)
      {
        foreach (var item in owner.ItemContainerGenerator.Items)
        {
          var itemContainer = owner.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
          itemContainer.IsSelected = false;
        }
      }
      row.IsSelected = true;
    }
  }
}

DataGridRow Style

<Style TargetType="{x:Type DataGridRow}">

  <EventSetter Event="MouseDoubleClick"
               Handler="DataGridRow_MouseDoubleClick" />
  <Setter Property="SnapsToDevicePixels"
          Value="true" />
  <Setter Property="Validation.ErrorTemplate"
          Value="{x:Null}" />
  <Setter Property="ValidationErrorTemplate">
    <Setter.Value>
      <ControlTemplate>
        <TextBlock Foreground="Red"
                   Margin="2,0,0,0"
                   Text="!"
                   VerticalAlignment="Center" />
      </ControlTemplate>
    </Setter.Value>
  </Setter>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type DataGridRow}">
        <Border x:Name="DGR_Border"
                Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"
                SnapsToDevicePixels="True">
          <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
              <VisualState x:Name="Normal" />

              <!--Provide a different appearance for every other row.-->
              <VisualState x:Name="Normal_AlternatingRow">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
                                                Storyboard.TargetProperty="(Panel.Background).
                  (GradientStop.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource ContentAreaColorLight}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>

              <!--In this example, a row in Editing or selected mode has an
          identical appearances. In other words, the states 
          Normal_Selected, Unfocused_Selected, Normal_Editing, 
          MouseOver_Editing, MouseOver_Unfocused_Editing,
          and Unfocused_Editing are identical.-->
              <VisualState x:Name="Normal_Selected">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
                                                Storyboard.TargetProperty="(Panel.Background).
                  (GradientStop.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{x:Static SystemColors.HighlightColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>

              <VisualState x:Name="Unfocused_Selected">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
                                                Storyboard.TargetProperty="(Panel.Background).
                  (SolidColorBrush.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource ControlMediumColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>

              <VisualState x:Name="Normal_Editing">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
                                                Storyboard.TargetProperty="(Panel.Background).
                  (SolidColorBrush.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource ControlMediumColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>

              <VisualState x:Name="MouseOver_Editing">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
                                                Storyboard.TargetProperty="(Panel.Background).
                  (SolidColorBrush.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource ControlMediumColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>

              <VisualState x:Name="MouseOver_Unfocused_Editing">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
                                                Storyboard.TargetProperty="(Panel.Background).
                  (SolidColorBrush.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource ControlMediumColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>

              <VisualState x:Name="Unfocused_Editing">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
                                                Storyboard.TargetProperty="(Panel.Background).
                  (SolidColorBrush.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource ControlMediumColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>

              <VisualState x:Name="MouseOver">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
                                                Storyboard.TargetProperty="(Panel.Background).
                  (SolidColorBrush.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource ControlMediumColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>

              <!--In this example, the appearance of a selected row 
          that has the mouse over it is the same regardless of
          whether the row is selected.  In other words, the states 
          MouseOver_Editing and MouseOver_Unfocused_Editing are identical.-->
              <VisualState x:Name="MouseOver_Selected">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
                                                Storyboard.TargetProperty="(Panel.Background).
                  (SolidColorBrush.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource ControlMouseOverColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>

              <VisualState x:Name="MouseOver_Unfocused_Selected">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
                                                Storyboard.TargetProperty="(Panel.Background).
                  (SolidColorBrush.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource ControlMouseOverColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>
            </VisualStateGroup>
          </VisualStateManager.VisualStateGroups>

          <SelectiveScrollingGrid>
            <SelectiveScrollingGrid.ColumnDefinitions>
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="*" />
            </SelectiveScrollingGrid.ColumnDefinitions>
            <SelectiveScrollingGrid.RowDefinitions>
              <RowDefinition Height="*" />
              <RowDefinition Height="Auto" />
            </SelectiveScrollingGrid.RowDefinitions>
            <DataGridCellsPresenter Grid.Column="1"
                                    ItemsPanel="{TemplateBinding ItemsPanel}"
                                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
            <DataGridDetailsPresenter Grid.Column="1"
                                      Grid.Row="1"
                                      Visibility="{TemplateBinding DetailsVisibility}"
                                      SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding AreRowDetailsFrozen, 
            ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical},
            Converter={x:Static DataGrid.RowDetailsScrollingConverter}, 
            RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
            <DataGridRowHeader Grid.RowSpan="2"
                               SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"
                               Visibility="{Binding HeadersVisibility, 
            ConverterParameter={x:Static DataGridHeadersVisibility.Row}, 
            Converter={x:Static DataGrid.HeadersVisibilityConverter}, 
            RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
          </SelectiveScrollingGrid>
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

DataGridCell Style

<Style TargetType="{x:Type DataGridCell}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type DataGridCell}">
        <Border x:Name="border"
                BorderBrush="Transparent"
                BorderThickness="1"
                Background="Transparent"
                SnapsToDevicePixels="True">
          <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="FocusStates">
              <VisualState x:Name="Unfocused" />
              <VisualState x:Name="Focused" />
            </VisualStateGroup>
            <VisualStateGroup x:Name="CurrentStates">
              <VisualState x:Name="Regular" />
              <VisualState x:Name="Current">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="border"
                                                Storyboard.TargetProperty="(Border.BorderBrush).
                  (SolidColorBrush.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource DatagridCurrentCellBorderColor}" />
                  </ColorAnimationUsingKeyFrames
                                            >
                </Storyboard>
              </VisualState>
            </VisualStateGroup>
          </VisualStateManager.VisualStateGroups>
          <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

Remarks
This implementation only targets the requirement to double click the non cell area to highlight the entire row. The select row on cell click behavior and the remaining sellection unit behaviors (DataGridSelectionUnit.Cell and DataGridSelectionUnit.CellOrRowHeader) are still missing and required in order to provide the default behavior.