Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to keep DataGridRow focus and selection synchronized?

I have a DataGrid and I want the selected row and the focused row to be synchronized, i.e. if the focused row changes, then the selected row changes as well as if the selected row changes, it becomes the focused row.

Given a WPF window with the following XAML, how can I synchronize the focused and the selected row?

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">

    <Grid>
        <Grid.Resources>

            <x:Array x:Key="MyList" Type="sys:String" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib">
                <sys:String>Hello</sys:String>
                <sys:String>World</sys:String>
                <sys:String>World</sys:String>
                <sys:String>World</sys:String>
                <sys:String>World</sys:String>
                <sys:String>World</sys:String>
                <sys:String>World</sys:String>
                <sys:String>World</sys:String>
                <sys:String>World</sys:String>
            </x:Array>

            <Style TargetType="{x:Type DataGrid}">
                <Setter Property="AlternationCount" Value="2" />
                <Setter Property="AutoGenerateColumns" Value="False"/>
            </Style>

            <Style TargetType="{x:Type DataGridCell}">
                <Setter Property="Background" Value="Transparent"/>
                <Setter Property="Focusable" Value="False"/>
            </Style>

            <Style TargetType="{x:Type DataGridRow}">
                <Setter Property="Focusable" Value="True"/>
                <Style.Triggers>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="AlternationIndex" Value="0"/>
                            <Condition Property="IsSelected" Value="False"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Background" Value="White"/>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="AlternationIndex" Value="1"/>
                            <Condition Property="IsSelected" Value="False"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Background" Value="Gainsboro"/>
                    </MultiTrigger>
                    <Trigger Property="AlternationIndex" Value="1">
                        <Setter Property="Background" Value="Gainsboro"/>
                    </Trigger>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="Background" Value="#BF228B22"/>
                        <Setter Property="BorderBrush" Value="ForestGreen"/>
                        <Setter Property="BorderThickness" Value="1"/>
                    </Trigger>
                </Style.Triggers>
            </Style>

        </Grid.Resources>

        <DataGrid ItemsSource="{StaticResource MyList}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding}" Width="*"/>
            </DataGrid.Columns>
        </DataGrid>

    </Grid>

</Window>

With the following image, you can see that the focus rectangle in red and the selected row in green are clearly not synchronized as I believe this is the default behavior. What I want is for them to always be one in the same, i.e. SelectedRow is always Focused and FocusedRow is always Selected.

enter image description here

like image 488
Jim Avatar asked Nov 04 '22 03:11

Jim


1 Answers

Try using an attached property to listen for focus change and selection change events inside the DataGrid. When either of those things happens, you can react to it accordingly by changing the selection or focus.

Here's some example code. Pay particular attention to the DataGridAttachment class. That's where the attached property is defined and where the logic lives to react to focus and selection change events.

MainWindow.xaml

<Window x:Class="_15098869.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:_15098869"
    Title="MainWindow"
    Width="525"
    Height="350">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <DataGrid Name="DataGrid"
              ItemsSource="{Binding Path=Items}"
              local:DataGridAttachment.SyncSelectionWithFocus="True" />
    <Button Grid.Row="1"
            Click="MoveSelectionButtonOnClick"
            Content="Move Selection" />
</Grid>

MainWindow.xaml.cs (Code-Behind)

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace _15098869
{
    /// <summary>
    ///     Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = this;

            Items = new ObservableCollection<DemoItem>();
            Items.Add(new DemoItem {FirstName = "John", LastName = "Doe"});
            Items.Add(new DemoItem {FirstName = "Jane", LastName = "Doe"});
            Items.Add(new DemoItem {FirstName = "Bob", LastName = "Doe"});
        }

        public ObservableCollection<DemoItem> Items { get; private set; }

        public void MoveSelectionButtonOnClick(object sender, RoutedEventArgs e)
        {
            var max = DataGrid.Items.Count - 1;
            DataGrid.SelectedIndex = DataGrid.SelectedIndex == max ? 0 : DataGrid.SelectedIndex + 1;
        }
    }

    public class DemoItem
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public static class DataGridAttachment
    {
        public static readonly DependencyProperty SyncSelectionWithFocusProperty =
            DependencyProperty.RegisterAttached("SyncSelectionWithFocus", typeof (bool), typeof (DataGridAttachment), new PropertyMetadata(default(bool), SyncSelectionWithFocusChanged));

        private static void SyncSelectionWithFocusChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var dataGrid = dependencyObject as DataGrid;
            if (dataGrid == null) return;

            var shouldSync = GetSyncSelectionWithFocus(dataGrid);

            if (shouldSync)
            {
                dataGrid.AddHandler(UIElement.GotFocusEvent, new RoutedEventHandler(DataGridOnGotFocus));
                dataGrid.AddHandler(Selector.SelectionChangedEvent, new SelectionChangedEventHandler(DataGridOnSelectionChanged));
            }
        }

        private static void DataGridOnGotFocus(object sender, RoutedEventArgs e)
        {
            var dataGrid = sender as DataGrid;
            var element = e.OriginalSource as DataGridCell;

            if (dataGrid == null || element == null) return;
            dataGrid.SelectedItem = element.DataContext;
        }

        private static void DataGridOnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var dataGrid = sender as DataGrid;

            if (dataGrid == null) return;

            if (!dataGrid.IsKeyboardFocusWithin)
            {
                dataGrid.Focus();
            }
        }

        public static void SetSyncSelectionWithFocus(DataGrid element, bool value)
        {
            element.SetValue(SyncSelectionWithFocusProperty, value);
        }

        public static bool GetSyncSelectionWithFocus(DataGrid element)
        {
            return (bool) element.GetValue(SyncSelectionWithFocusProperty);
        }
    }
}
like image 68
Brandon Baker Avatar answered Nov 12 '22 15:11

Brandon Baker