diff --git a/src/TableView.Properties.cs b/src/TableView.Properties.cs
index 80ac94b0..f5688f14 100644
--- a/src/TableView.Properties.cs
+++ b/src/TableView.Properties.cs
@@ -310,6 +310,11 @@ public bool CanPaste
///
public static readonly DependencyProperty ShowDragRectangleProperty = DependencyProperty.Register(nameof(ShowDragRectangle), typeof(bool), typeof(TableView), new PropertyMetadata(true, OnShowDragRectangleChanged));
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty UseListViewHotkeysProperty = DependencyProperty.Register(nameof(UseListViewHotkeys), typeof(bool), typeof(TableView), new PropertyMetadata(false));
+
///
/// Gets or sets a value indicating whether opening the column filter over header right-click is enabled.
///
@@ -869,6 +874,15 @@ public bool CanReorderColumns
set => SetValue(CanReorderColumnsProperty, value);
}
+ ///
+ /// Gets or sets whether the TableView should use ListView like hotkeys for navigation and selection.
+ ///
+ public bool UseListViewHotkeys
+ {
+ get => (bool)GetValue(UseListViewHotkeysProperty);
+ set => SetValue(UseListViewHotkeysProperty, value);
+ }
+
///
/// Handles changes to the ItemsSource property.
///
diff --git a/src/TableView.cs b/src/TableView.cs
index 698c482d..abfa845a 100644
--- a/src/TableView.cs
+++ b/src/TableView.cs
@@ -170,6 +170,16 @@ protected override DependencyObject GetContainerForItemOverride()
return row;
}
+ ///
+ /// Gets a value indicating whether keyboard input should be handled in row context,
+ /// i.e. when is enabled and the effective selection unit is a row.
+ ///
+ private bool IsRowKeyboardContext =>
+ UseListViewHotkeys &&
+ (SelectionUnit == TableViewSelectionUnit.Row ||
+ (SelectionUnit == TableViewSelectionUnit.CellOrRow &&
+ LastSelectionUnit == TableViewSelectionUnit.Row));
+
///
protected override void OnKeyDown(KeyRoutedEventArgs e)
{
@@ -182,9 +192,92 @@ protected override void OnKeyDown(KeyRoutedEventArgs e)
return;
}
+ if (!IsEditing && IsRowKeyboardContext)
+ {
+ if (!shiftKey &&
+ !ctrlKey &&
+ SelectionMode == ListViewSelectionMode.Multiple &&
+ e.Key == VirtualKey.Enter)
+ {
+ ToggleCurrentRowSelection();
+ e.Handled = true;
+ return;
+ }
+
+ var rowSelectionOnly = SelectionUnit == TableViewSelectionUnit.Row;
+
+ if (e.Key is VirtualKey.Up or VirtualKey.Down or
+ VirtualKey.Home or VirtualKey.End or
+ VirtualKey.PageUp or VirtualKey.PageDown ||
+ (rowSelectionOnly && (e.Key is VirtualKey.Left or VirtualKey.Right)))
+ {
+ int? prevCellRow = null;
+ if (SelectionUnit == TableViewSelectionUnit.Row && CurrentCellSlot.HasValue)
+ {
+ prevCellRow = CurrentCellSlot.Value.Row;
+ }
+
+ base.OnKeyDown(e); // Let ListView move the row focus / selection
+
+ var focusedIndex = GetFocusedRowIndex();
+ if (focusedIndex >= 0)
+ {
+ CurrentRowIndex = focusedIndex;
+ SelectionStartRowIndex ??= focusedIndex;
+ }
+
+ if (SelectionUnit == TableViewSelectionUnit.Row &&
+ prevCellRow.HasValue &&
+ focusedIndex >= 0 &&
+ focusedIndex != prevCellRow.Value)
+ {
+ CurrentCellSlot = null; // will un-apply old cell's current-state border
+ }
+ return;
+ }
+ }
+
+ // Everything else (cell nav, F2 in cell mode, Space, etc.)
HandleNavigations(e, shiftKey, ctrlKey);
}
+ private void ToggleCurrentRowSelection()
+ {
+ var index = GetFocusedRowIndex();
+
+ if (index < 0)
+ {
+ var rowIndex = CurrentRowIndex ?? SelectedIndex;
+
+ if (rowIndex is < 0 || rowIndex >= Items.Count)
+ {
+ return;
+ }
+
+ index = rowIndex;
+ }
+
+
+ if (index < 0 || index >= Items.Count) { return; }
+
+ var isSelected = SelectedRanges.Any(r => r.IsInRange(index));
+
+ var singleIndexRange = new ItemIndexRange(index, 1u);
+
+ if (isSelected)
+ {
+ DeselectRange(singleIndexRange);
+ }
+ else
+ {
+ SelectRange(singleIndexRange);
+ }
+
+ SelectionStartRowIndex = index;
+ CurrentRowIndex = index;
+
+ }
+
///
/// Handles navigation keys.
///
@@ -533,6 +626,34 @@ private TableViewCellSlot GetNextSlot(TableViewCellSlot? currentSlot, bool isShi
return new TableViewCellSlot(nextRow, nextColumn);
}
+ private int GetFocusedRowIndex()
+ {
+ if (XamlRoot is null)
+ return -1;
+
+ var focused = FocusManager.GetFocusedElement(XamlRoot) as DependencyObject;
+ if (focused is null)
+ return -1;
+
+ var row = GetRowFromElement(focused);
+ return row?.Index ?? -1;
+ }
+
+ private static TableViewRow? GetRowFromElement(DependencyObject element)
+ {
+ var current = element;
+
+ while (current is not null)
+ {
+ if (current is TableViewRow row)
+ return row;
+
+ current = VisualTreeHelper.GetParent(current);
+ }
+
+ return null;
+ }
+
///
/// Copies the selected rows or cells content to the clipboard.
///