question

JimHume avatar image
0 Votes"
JimHume asked ·

WPF DataGrid behaviour: row select and pressing spacebar

I desire the ability to make the WPF DataGrid behave similar in nature to the ListView (details) whereby pressing the spacebar will activate the checkbox of a row, effectively 'selecting' it / 'checking' it.


I have a DataGrid that is bound to an ICollectionView called 'EndpointCollectionView':

 <DataGrid ItemsSource="{Binding EndpointCollectionView}" 
                                   AutoGenerateColumns="False" CanUserSortColumns="True" CanUserResizeColumns="True" CanUserAddRows="False" 
                                   CanUserDeleteRows="False" CanUserReorderColumns="False" CanUserResizeRows="False" 
                                   AlternatingRowBackground="LightGray" 
                                   SelectionUnit="FullRow"
                                   SelectionMode="Extended">

I can select rows using CTRL or SHIFT and the arrow keys

The first column of my DataGrid represents the 'IsChecked' property on my view models and the header of said column allows for the 'Select All' pattern to be used:

 <DataGridTemplateColumn Width="26" SortMemberPath="IsSelected" CanUserResize="False">
     <DataGridTemplateColumn.HeaderTemplate>
         <DataTemplate>
             <CheckBox IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=DataGrid}}" HorizontalAlignment="Center" />
         </DataTemplate>
     </DataGridTemplateColumn.HeaderTemplate>
     <DataGridTemplateColumn.CellTemplate>
         <DataTemplate>
             <CheckBox IsTabStop="True" VerticalAlignment="Center" HorizontalAlignment="Center" IsChecked="{Binding Path=IsChecked, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
         </DataTemplate>
     </DataGridTemplateColumn.CellTemplate>
     <DataGridTemplateColumn.CellStyle>
         <Style TargetType="DataGridCell">
             <Setter Property="IsTabStop" Value="False"/>
         </Style>
     </DataGridTemplateColumn.CellStyle>
 </DataGridTemplateColumn>


Using the DataGridTemplateColumn in this form also allows me to single-click the checkbox and have it be checked, rather than have the "click to select the cell then click again to check the checkbox" problem with the WPF DataGrid. This works well.

This header template also allows for me to have a property titled 'AllSelected' in my view model that allows all items to have their IsChecked property toggled programmatically. This is ideal and also works well.


My problem: I can't find search terms to solve my problem. I desire the ability to enforce row-select (SelectionUnit=FullRow) and regardless of which cell is currently highlighted in the datagrid, pressing the spacebar should check the checkbox in the first column. To put it in simpler terms: when I press the space bar while the DataGrid is focused, I want all selected ROWS to have their 'IsChecked' property (bound via view model) toggled from true to false, or false to true.

Is there a way to make the WPF DataGrid behave like this? Basically I want similar functionality to a check list, or listview in details mode: shift/ctrl arrows will select rows, and then spacebar toggles their "is checked" state.

I can't seem to find results relating specifically to this requirement. I can find no end of "how do I add a checkbox column to the datagrid" questions and even slightly more advanced questions that refer to the "click through" problem of cells within the DataGrid ... but I can't seem to find anything similar to what I desire.

Relevant tags that I can't add to the post: wpf, datagrid, checkbox, spacebar, rowtemplate, istabstop, selectionunit, rowselect, ischecked

windows-wpf
· 2
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@JimHume How about using a KeyBinding to bind a KeyGesture to a RoutedCommand? If this is what you want, I can make a demo for you.


0 Votes 0 ·

@DaisyTian-MSFT That sounds quite interesting and is not something I'm familiar with. I would indeed greatly appreciate any code you may be able to provide. Thank you for your prompt response!

0 Votes 0 ·

1 Answer

JimHume avatar image
0 Votes"
JimHume answered ·

With the hint from @DaisyTian-MSFT and needing to monitor the selected items for context-menu purposes, I was able to come up with the following solution.

The view (Window in my case) needs the 'interactivity' library referenced. The DataGrid makes use of the EventToCommand object from the interactivity namespace via the EventTriggers. I believe this is similar to what @DaisyTian-MSFT was suggesting. Note that I am tracking the selected items along with the key presses:

 <Window xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
 ...
 ...
     <DataGrid 
         ...
         ...
         >
         <i:Interaction.Triggers>
             <i:EventTrigger EventName="KeyUp">
                 <command:EventToCommand Command="{Binding KeyUpCommand}" PassEventArgsToCommand="True"/>
             </i:EventTrigger>
             <i:EventTrigger EventName="SelectionChanged">
                 <command:EventToCommand Command="{Binding SelectionChangedCommand}" PassEventArgsToCommand="True"/>
             </i:EventTrigger>
         </i:Interaction.Triggers>
         ...
         ...
     </DataGrid> 


The viewmodel that is bound to the 'Window' above has the following code which is over-simplified for this post:

 using GalaSoft.MvvmLight;
 using GalaSoft.MvvmLight.Command;
 ...
 ...
 private readonly RelayCommand<SelectionChangedEventArgs> selectionChangedCommand;
 private readonly RelayCommand<KeyEventArgs> keyUpCommand;
 ...
 ...
 public ICommand SelectionChangedCommand => this.selectionChangedCommand;
 public ICommand KeyUpCommand => this.keyUpCommand;
    
 public MyViewModel() 
 {
     this.selectionChangedCommand = new RelayCommand<SelectionChangedEventArgs>(args =>
     {
         foreach (var item in args.AddedItems)
         {
             if (item is MyViewChildModel viewModel)
             {
                 viewModel.IsSelected = true;
             }
         }
    
         foreach (var item in args.RemovedItems)
         {
             if (item is MyViewChildModel viewModel)
             {
                 viewModel.IsSelected = false;
             }
         }
     });
    
     this.keyUpCommand = new RelayCommand<KeyEventArgs>(args =>
     {
         if (args.Key != Key.Space) return;
    
         // If the selected count isn't the same as the checked count then check them all
         // so that the state resets and all the checkboxes are being set to the same state.
         var checkAll = ChildViewModels.Count(ep => ep.IsSelected) !=
                        ChildViewModels.Count(ep => ep.IsChecked);
    
         // Because of the selectionChangedCommand, we can be reasonably certain that our 
         // view models will have their 'IsSelected' property in the appropriate state.
         foreach (var viewModel in ChildViewModels.Where(ep => ep.IsSelected))
         {
             viewModel.IsChecked = checkAll || !viewModel.IsChecked;
         }
     });
 }


Finally, the child view models are very basic and extend ViewModelBase from the Galasoft.Mvvm library:

 using GalaSoft.MvvmLight;
    
 namespace MyNamespace
 {
     public class MyChildViewModel : ViewModelBase
     {
         private bool isChecked;
         private bool isSelected;
    
         public bool IsChecked
         {
             get => this.isChecked;
             set => Set(ref this.isChecked, value);
         }
    
         public bool IsSelected
         {
             get => this.isSelected;
             set => Set(ref this.isSelected, value);
         }
         ...
         ...
     }
 }

While searching for more posts relating to my issue I came across my own post (this one) so I believed it would be prudent to answer my own question as a result.

Thank you for the hint @DaisyTian-MSFT -- it definitely sent me in the right direction.

· Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.