diff --git a/samples/WinUI.TableView.SampleApp/App.xaml.cs b/samples/WinUI.TableView.SampleApp/App.xaml.cs
index 0d7a1074..c602ae72 100644
--- a/samples/WinUI.TableView.SampleApp/App.xaml.cs
+++ b/samples/WinUI.TableView.SampleApp/App.xaml.cs
@@ -1,5 +1,6 @@
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml;
+using System.Diagnostics;
namespace WinUI.TableView.SampleApp;
@@ -25,17 +26,17 @@ public App()
#if DEBUG && WINDOWS
private void DebugSettings_BindingFailed(object sender, BindingFailedEventArgs e)
{
- System.Diagnostics.Debug.WriteLine(e.Message);
+ Debug.WriteLine(e.Message);
}
private void DebugSettings_XamlResourceReferenceFailed(DebugSettings sender, XamlResourceReferenceFailedEventArgs args)
{
- System.Diagnostics.Debug.WriteLine(args.Message);
+ Debug.WriteLine(args.Message);
}
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
- if (System.Diagnostics.Debugger.IsAttached) System.Diagnostics.Debugger.Break();
+ if (Debugger.IsAttached) Debugger.Break();
}
#endif
@@ -45,21 +46,27 @@ private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledEx
/// Details about the launch request and process.
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
-#if DEBUG && !WINDOWS
+#if DEBUG
+ if (Debugger.IsAttached)
+ {
+ DebugSettings.EnableFrameRateCounter = true;
+ }
+#if !WINDOWS
MainWindow.UseStudio();
MainWindow.SetWindowIcon();
+#endif
#endif
MainWindow.Activate();
}
public static void InitializeLogging()
{
+
#if DEBUG
var factory = LoggerFactory.Create(builder =>
{
#if __WASM__
builder.AddProvider(new global::Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider());
- // Note: DebugSettings.EnableFrameRateCounter requires an Application instance
#elif !WINDOWS
builder.AddConsole();
#else
diff --git a/src/Extensions/ItemIndexRangeExtensions.cs b/src/Extensions/ItemIndexRangeExtensions.cs
index 7afae48c..13a6db7c 100644
--- a/src/Extensions/ItemIndexRangeExtensions.cs
+++ b/src/Extensions/ItemIndexRangeExtensions.cs
@@ -1,4 +1,5 @@
using Microsoft.UI.Xaml.Data;
+using System.Runtime.CompilerServices;
namespace WinUI.TableView.Extensions;
@@ -21,11 +22,55 @@ public static bool IsInRange(this ItemIndexRange range, int index)
///
/// Determines whether the given item index range is valid within the TableView.
///
- /// The ItemIndexRange to check.
+ /// The ItemIndexRange to check.
/// The TableView to check against.
/// True if the item index range of TableView is valid; otherwise, false.
- public static bool IsValid(this ItemIndexRange itemIndexRange, TableView tableView)
+ public static bool IsValid(this ItemIndexRange range, TableView tableView)
+ {
+ return range.FirstIndex >= 0 && range.LastIndex < tableView?.Items.Count;
+ }
+
+ ///
+ /// Determines whether the specified range completely contains another range.
+ ///
+ /// The range to check.
+ /// The range to check against.
+ /// True if the range completely contains the other range; otherwise, false.
+ public static bool Contains(this ItemIndexRange range, ItemIndexRange other)
{
- return itemIndexRange.FirstIndex >= 0 && itemIndexRange.LastIndex < tableView?.Items.Count;
+ return other.FirstIndex >= range.FirstIndex && other.LastIndex <= range.LastIndex;
+ }
+
+ ///
+ /// Subtracts another ItemIndexRange from the current range and returns the resulting range.
+ ///
+ /// The range to subtract from.
+ /// The range to subtract.
+ /// The resulting range after subtraction.
+ public static IEnumerable Subtract(this ItemIndexRange range, ItemIndexRange other)
+ {
+ var start = range.FirstIndex;
+ var end = start + (int)range.Length - 1;
+
+ var otherStart = other.FirstIndex;
+ var otherEnd = otherStart + (int)other.Length - 1;
+
+ // No overlap.
+ if (otherEnd < start || otherStart > end)
+ {
+ yield break;
+ }
+
+ // Left remainder.
+ if (otherStart > start)
+ {
+ yield return new ItemIndexRange(start, (uint)(otherStart - start));
+ }
+
+ // Right remainder.
+ if (otherEnd < end)
+ {
+ yield return new ItemIndexRange(otherEnd + 1, (uint)(end - otherEnd));
+ }
}
}
diff --git a/src/Extensions/TableViewCellSlotRangeExtensions.cs b/src/Extensions/TableViewCellSlotRangeExtensions.cs
new file mode 100644
index 00000000..01fbd49e
--- /dev/null
+++ b/src/Extensions/TableViewCellSlotRangeExtensions.cs
@@ -0,0 +1,189 @@
+using Microsoft.UI.Xaml.Data;
+
+namespace WinUI.TableView.Extensions;
+
+///
+/// Provides extension methods for the TableViewCellSlotRange type.
+///
+internal static class TableViewCellSlotRangeExtensions
+{
+ ///
+ /// Determines whether a specified cell slot is within the range.
+ ///
+ /// The TableViewCellSlotRange to check.
+ /// The cell slot to check.
+ /// True if the slot is within the range; otherwise, false.
+ public static bool IsInRange(this TableViewCellSlotRange? range, TableViewCellSlot slot)
+ {
+ if (range is null || range.Length <= 0) return false;
+
+ var minRow = Math.Min(range.FirstRow, range.LastRow);
+ var maxRow = Math.Max(range.FirstRow, range.LastRow);
+ var minColumn = Math.Min(range.FirstColumn, range.LastColumn);
+ var maxColumn = Math.Max(range.FirstColumn, range.LastColumn);
+
+ return slot.Row >= minRow && slot.Row <= maxRow
+ && slot.Column >= minColumn && slot.Column <= maxColumn;
+ }
+
+ ///
+ /// Determines whether a specified row index is within the range.
+ ///
+ /// The TableViewCellSlotRange to check.
+ /// The row index to check.
+ /// True if the row index is within the range; otherwise, false.
+ public static bool IsRowInRange(this TableViewCellSlotRange? range, int row)
+ {
+ return range?.Length > 0
+ && row >= Math.Min(range.FirstRow, range.LastRow)
+ && row <= Math.Max(range.FirstRow, range.LastRow);
+ }
+
+ ///
+ /// Determines whether a specified column index is within the range.
+ ///
+ /// The TableViewCellSlotRange to check.
+ /// The column index to check.
+ /// True if the column index is within the range; otherwise, false.
+ public static bool IsColumnInRange(this TableViewCellSlotRange? range, int column)
+ {
+ return range?.Length > 0
+ && column >= Math.Min(range.FirstColumn, range.LastColumn)
+ && column <= Math.Max(range.FirstColumn, range.LastColumn);
+ }
+
+ ///
+ /// Determines whether the given cell slot range is valid within the TableView.
+ ///
+ /// The TableViewCellSlotRange to check.
+ /// The TableView to check against.
+ /// True if the cell slot range of TableView is valid; otherwise, false.
+ public static bool IsValid(this TableViewCellSlotRange range, TableView tableView)
+ {
+ return range.FirstSlot.IsValid(tableView) && range.LastSlot.IsValid(tableView);
+ }
+
+ ///
+ /// Returns all cell slots contained within this range, enumerated row by row.
+ ///
+ public static IEnumerable GetSlots(this TableViewCellSlotRange range)
+ {
+ for (var row = range.FirstRow; row <= range.LastRow; row++)
+ {
+ for (var col = range.FirstColumn; col <= range.LastColumn; col++)
+ {
+ yield return new TableViewCellSlot(row, col);
+ }
+ }
+ }
+
+ ///
+ /// Determines whether a specific cell slot falls within this range.
+ ///
+ public static bool Contains(this TableViewCellSlotRange range, int rowIndex, int columnIndex)
+ {
+ return rowIndex >= range.FirstRow && rowIndex <= range.LastRow &&
+ columnIndex >= range.FirstColumn && columnIndex <= range.LastColumn;
+ }
+
+ ///
+ /// Determines whether another TableViewCellSlotRange is completely contained within this range.
+ ///
+ public static bool Contains(this TableViewCellSlotRange? range, TableViewCellSlotRange? other)
+ {
+ if (range == null || other == null) return false;
+
+ return range.Contains(other.FirstRow, other.FirstColumn) &&
+ range.Contains(other.LastRow, other.LastColumn);
+ }
+
+ ///
+ /// Determines whether another range intersects with this range.
+ ///
+ public static bool IntersectsWith(this TableViewCellSlotRange range, TableViewCellSlotRange other)
+ {
+ if (other == null) return false;
+
+ return range.FirstRow <= other.LastRow && range.LastRow >= other.FirstRow &&
+ range.FirstColumn <= other.LastColumn && range.LastColumn >= other.FirstColumn;
+ }
+
+ ///
+ /// Subtracts another range from this range and returns the resulting ranges.
+ ///
+ /// The range to subtract from.
+ /// The range to subtract.
+ /// An enumerable of resulting ranges after subtraction.
+ public static IEnumerable Subtract(this TableViewCellSlotRange range, TableViewCellSlotRange other)
+ {
+ // No overlap.
+ if (!range.IntersectsWith(other))
+ {
+ yield return range;
+ yield break;
+ }
+
+ // Intersection rectangle.
+ var top = Math.Max(range.FirstRow, other.FirstRow);
+ var left = Math.Max(range.FirstColumn, other.FirstColumn);
+ var bottom = Math.Min(range.LastRow, other.LastRow);
+ var right = Math.Min(range.LastColumn, other.LastColumn);
+
+ // Top strip.
+ if (range.FirstRow < top)
+ {
+ yield return new TableViewCellSlotRange(
+ range.FirstRow,
+ range.FirstColumn,
+ top - range.FirstRow,
+ range.Columns);
+ }
+
+ // Bottom strip.
+ if (bottom < range.LastRow)
+ {
+ yield return new TableViewCellSlotRange(
+ bottom + 1,
+ range.FirstColumn,
+ range.LastRow - bottom,
+ range.Columns);
+ }
+
+ // Left strip.
+ if (range.FirstColumn < left)
+ {
+ yield return new TableViewCellSlotRange(
+ top,
+ range.FirstColumn,
+ bottom - top + 1,
+ left - range.FirstColumn);
+ }
+
+ // Right strip.
+ if (right < range.LastColumn)
+ {
+ yield return new TableViewCellSlotRange(
+ top,
+ right + 1,
+ bottom - top + 1,
+ range.LastColumn - right);
+ }
+ }
+
+ ///
+ /// Merges two TableViewCellSlotRanges into a single range that encompasses both.
+ ///
+ public static TableViewCellSlotRange Merge(this TableViewCellSlotRange range, TableViewCellSlotRange other)
+ {
+ var firstRow = Math.Min(range.FirstRow, other.FirstRow);
+ var firstColumn = Math.Min(range.FirstColumn, other.FirstColumn);
+ var lastRow = Math.Max(range.LastRow, other.LastRow);
+ var lastColumn = Math.Max(range.LastColumn, other.LastColumn);
+
+ return TableViewCellSlotRange.FromCoordinates(
+ firstRow,
+ firstColumn,
+ lastRow,
+ lastColumn);
+ }
+}
\ No newline at end of file
diff --git a/src/Helpers/IndexRangeHelper.cs b/src/Helpers/IndexRangeHelper.cs
new file mode 100644
index 00000000..59b73b39
--- /dev/null
+++ b/src/Helpers/IndexRangeHelper.cs
@@ -0,0 +1,44 @@
+using Microsoft.UI.Xaml.Data;
+
+namespace WinUI.TableView.Helpers;
+
+///
+/// Provides helper methods for working with index ranges in a TableView.
+///
+internal static class IndexRangeHelper
+{
+ ///
+ /// Gets a list of contiguous index ranges from a collection of indexes.
+ ///
+ /// The collection of indexes to process.
+ /// A list of contiguous index ranges.
+ public static List GetRanges(IEnumerable indexes)
+ {
+ var sorted = indexes.Order().ToArray();
+
+ if (sorted.Length == 0)
+ return [];
+
+ List ranges = [];
+
+ var first = sorted[0];
+ var previous = sorted[0];
+
+ for (var i = 1; i < sorted.Length; i++)
+ {
+ if (sorted[i] == previous + 1)
+ {
+ previous = sorted[i];
+ continue;
+ }
+
+ ranges.Add(new ItemIndexRange(first, (uint)(previous - first + 1)));
+
+ first = previous = sorted[i];
+ }
+
+ ranges.Add(new ItemIndexRange(first, (uint)(previous - first + 1)));
+
+ return ranges;
+ }
+}
diff --git a/src/Helpers/TableViewTrace.cs b/src/Helpers/TableViewTrace.cs
new file mode 100644
index 00000000..6f31ca24
--- /dev/null
+++ b/src/Helpers/TableViewTrace.cs
@@ -0,0 +1,15 @@
+using System.Diagnostics;
+
+namespace WinUI.TableView.Helpers;
+
+internal static class TableViewTrace
+{
+ [Conditional("DEBUG")]
+ public static void Write(string message)
+ {
+ if (Debugger.IsAttached)
+ {
+ Debug.WriteLine($"[TableView] {message}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/TableView.Events.cs b/src/TableView.Events.cs
index 47868c32..5d313047 100644
--- a/src/TableView.Events.cs
+++ b/src/TableView.Events.cs
@@ -1,6 +1,6 @@
using Microsoft.UI.Xaml;
-using System;
-using System.ComponentModel;
+using System.Diagnostics;
+using WinUI.TableView.Helpers;
namespace WinUI.TableView;
@@ -186,6 +186,7 @@ protected internal virtual void OnClearSorting(TableViewClearSortingEventArgs ar
///
protected virtual void OnCellSelectionChanged(TableViewCellSelectionChangedEventArgs args)
{
+ TableViewTrace.Write($"TableViewCellSelectionChanged: Added={args.AddedCells.Count}, Removed={args.RemovedCells.Count}");
CellSelectionChanged?.Invoke(this, args);
}
diff --git a/src/TableView.Properties.cs b/src/TableView.Properties.cs
index 80ac94b0..05f9697b 100644
--- a/src/TableView.Properties.cs
+++ b/src/TableView.Properties.cs
@@ -416,7 +416,7 @@ public bool ForceRowOrCellSelectionOnContextRequested
///
/// Gets the selected cell ranges.
///
- internal HashSet> SelectedCellRanges { get; } = [];
+ internal HashSet SelectedCellRanges { get; } = [];
///
/// Gets or sets a value indicating whether the TableView is in editing mode.
@@ -897,7 +897,7 @@ private static void OnSelectionModeChanged(DependencyObject d, DependencyPropert
if (tableView.SelectionMode is ListViewSelectionMode.Single && tableView.CurrentCellSlot.HasValue)
{
- tableView.SelectedCellRanges.Add([tableView.CurrentCellSlot.Value]);
+ tableView.SelectedCellRanges.Add(TableViewCellSlotRange.FromSlots(tableView.CurrentCellSlot.Value));
}
tableView.OnCellSelectionChanged();
diff --git a/src/TableView.cs b/src/TableView.cs
index 698c482d..16af828f 100644
--- a/src/TableView.cs
+++ b/src/TableView.cs
@@ -3,18 +3,11 @@
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
-using Microsoft.UI.Xaml.Media;
-using System;
using System.Collections;
-using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
using System.Reflection;
using System.Text;
-using System.Threading.Tasks;
using Windows.ApplicationModel.DataTransfer;
using Windows.Foundation;
using Windows.Storage;
@@ -22,6 +15,7 @@
using Windows.System;
using WinUI.TableView.Extensions;
using WinUI.TableView.Helpers;
+using Pointer = Microsoft.UI.Xaml.Input.Pointer;
namespace WinUI.TableView;
@@ -42,7 +36,6 @@ public partial class TableView : ListView
private readonly CollectionView _collectionView = [];
private Border? _dragRectangle;
private Point? _dragStartPoint;
- private bool _cellSelectionDirty;
private bool _suppressSelectionChangedCellClear;
private Point? _lastDragCanvasPoint;
private DispatcherTimer? _autoScrollTimer;
@@ -50,6 +43,12 @@ public partial class TableView : ListView
private double _autoScrollHorizontalDelta;
private double _dragStartVerticalOffset;
private double _dragStartHorizontalOffset;
+ private Pointer? _tableViewDragPointer;
+ private UIElement? _pointerCaptureElement;
+ private TableViewCellSlotRange? _lastDragSelectionCellRange;
+ private ItemIndexRange? _lastDragSelectionRowRange;
+ private bool _cellStateDispatchPending;
+ private readonly HashSet _pendingCellStateRows = [];
///
/// Initializes a new instance of the TableView class.
@@ -72,6 +71,9 @@ public TableView()
Unloaded += OnUnloaded;
SelectionChanged += TableView_SelectionChanged;
_collectionView.ItemPropertyChanged += OnItemPropertyChanged;
+
+ AddHandler(PointerPressedEvent, new PointerEventHandler(OnAnyPointerPressed), handledEventsToo: true);
+ AddHandler(PointerReleasedEvent, new PointerEventHandler(OnAnyPointerReleased), handledEventsToo: true);
}
///
@@ -79,6 +81,8 @@ public TableView()
///
private void TableView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
+ TableViewTrace.Write($"TableViewSelectionChanged: AddedItems={e.AddedItems.Count}, RemovedItems={e.RemovedItems.Count}");
+
if (_suppressSelectionChangedCellClear)
{
_suppressSelectionChangedCellClear = false;
@@ -91,11 +95,17 @@ private void TableView_SelectionChanged(object sender, SelectionChangedEventArgs
}
else
{
- SelectedCellRanges.RemoveWhere(slots =>
+ var addedIndexes = e.AddedItems
+ .Select(item => Items.IndexOf(item))
+ .Where(i => i >= 0);
+
+ if (Columns.VisibleColumns.Count == 0) return;
+
+ foreach (var range in IndexRangeHelper.GetRanges(addedIndexes))
{
- slots.RemoveWhere(slot => SelectedRanges.Any(range => range.IsInRange(slot.Row)));
- return slots.Count == 0;
- });
+ var slotRange = TableViewCellSlotRange.FromCoordinates(range.FirstIndex, 0, range.LastIndex, Columns.VisibleColumns.Count - 1);
+ SubtractCellRangeFromSelection(slotRange);
+ }
}
CurrentCellSlot = null;
@@ -108,6 +118,23 @@ private void TableView_SelectionChanged(object sender, SelectionChangedEventArgs
}
}
+ ///
+ /// Subtracts a specified cell range from the current selection.
+ ///
+ /// The cell range to subtract from the current selection.
+ private void SubtractCellRangeFromSelection(TableViewCellSlotRange slotRange)
+ {
+ while (SelectedCellRanges.FirstOrDefault(r => r.IntersectsWith(slotRange)) is { } intersectingRange)
+ {
+ foreach (var slicedRange in intersectingRange.Subtract(slotRange))
+ {
+ SelectedCellRanges.Add(slicedRange);
+ }
+
+ SelectedCellRanges.Remove(intersectingRange);
+ }
+ }
+
///
/// Handles the PropertyChanged event of an item in the TableView.
///
@@ -134,7 +161,14 @@ protected override void PrepareContainerForItemOverride(DependencyObject element
row.TableView = this;
row.EnsureCellsStyle(default, item);
- row.ApplyCellsSelectionState();
+
+ _pendingCellStateRows.Add(row.Index);
+ if (!_cellStateDispatchPending)
+ {
+ _cellStateDispatchPending = true;
+ DispatcherQueue.TryEnqueue(ApplyPendingCellStates);
+ }
+
row.RowPresenter?.ApplyDetailsPaneState(item);
if (CurrentCellSlot.HasValue)
@@ -185,6 +219,257 @@ protected override void OnKeyDown(KeyRoutedEventArgs e)
HandleNavigations(e, shiftKey, ctrlKey);
}
+ ///
+ /// Handles pointer-pressed for all cases, including when elements sets e.Handled = true.
+ ///
+ private void OnAnyPointerPressed(object sender, PointerRoutedEventArgs e)
+ {
+ var pointerPoint = e.GetCurrentPoint(this);
+ var position = pointerPoint.Position;
+ var canvasPoint = GetCanvasPoint(position);
+ var ctrlKey = KeyboardHelper.IsCtrlKeyDown();
+ var isShiftkey = KeyboardHelper.IsShiftKeyDown();
+ var orignalSoruce = e.OriginalSource as FrameworkElement;
+
+ if (SelectionMode is ListViewSelectionMode.None // Skip selection when SelectionMode is None
+ || IsDragSelecting // Skip selection when a drag is already in progress
+ || orignalSoruce is ScrollBar // Skip selection when the pointer is over the ScrollBar
+ || orignalSoruce?.FindAscendant() is { } // Skip selection when the pointer is within a ScrollBar
+ || !pointerPoint.Properties.IsLeftButtonPressed // Skip selection when the left mouse button is not pressed
+ || canvasPoint is null // Skip selection when canvasPoint is null (e.g., pointer is outside the scroll canvas)
+ || canvasPoint.Value.Y < 0 // Skip selection when the pointer is in the column header area (above the scroll canvas)
+ || canvasPoint.Value.X < CellsHorizontalOffset // Skip selection when the pointer is in the row header area (to the left of the scroll canvas)
+ || isShiftkey) // Skip selection when the Shift key is held
+ {
+ return;
+ }
+
+ _lastDragCanvasPoint = null;
+ CurrentCellSlot = null;
+ SelectionStartCellSlot = null;
+ SelectionStartRowIndex = null;
+ _lastDragSelectionRowRange = null;
+ _lastDragSelectionCellRange = null;
+ LastSelectionUnit = TableViewSelectionUnit.Row;
+
+ if (e.OriginalSource is UIElement element)
+ {
+ UIElement? pressedElement = element.FindAscendant(); // Check if the pointer is over a TableViewCell
+ pressedElement ??= element.FindAscendant(); // If not, check if the pointer is over a TableViewRow
+
+#if !WINDOWS
+ _dragStartCell = pressedElement as TableViewCell;
+ _dragStartRow = element.FindAscendant();
+#endif
+
+ // Skip selection when the pointer is not over a Cell or Row, and ShowDragRectangle is false.
+ if (pressedElement == null && !ShowDragRectangle) return;
+
+ pressedElement ??= this; // If not, default to the TableView itself
+
+ SelectionStartCellSlot = (pressedElement as TableViewCell)?.Slot;
+ SelectionStartRowIndex = (pressedElement as TableViewRow)?.Index;
+
+ LastSelectionUnit = SelectionUnit switch
+ {
+ TableViewSelectionUnit.Cell => TableViewSelectionUnit.Cell,
+ TableViewSelectionUnit.Row => TableViewSelectionUnit.Row,
+ _ => pressedElement is TableViewCell
+ ? TableViewSelectionUnit.Cell
+ : TableViewSelectionUnit.Row
+ };
+
+ if (SelectionMode is ListViewSelectionMode.Single)
+ {
+ _lastDragCanvasPoint = canvasPoint;
+ MakeSelectionInDragRect();
+ SetCurrentCellFromCanvasPoint(_lastDragCanvasPoint.Value);
+
+ return;
+ }
+
+ pressedElement.Focus(FocusState.Programmatic);
+#if WINDOWS
+ _pointerCaptureElement = pressedElement;
+#else
+ _pointerCaptureElement = this;
+#endif
+
+ _pointerCaptureElement.CapturePointer(e.Pointer);
+ _tableViewDragPointer = e.Pointer;
+
+ if (!ctrlKey && SelectionMode is not ListViewSelectionMode.Multiple && LastSelectionUnit is not TableViewSelectionUnit.Cell)
+ DeselectAll();
+
+ StartDragSelection(canvasPoint.Value);
+
+ if (!IsDragSelecting)
+ {
+ _pointerCaptureElement?.ReleasePointerCaptures();
+ _pointerCaptureElement = null;
+ _tableViewDragPointer = null;
+ return;
+ }
+
+ MakeSelectionInDragRect();
+ }
+ }
+
+ ///
+ protected override void OnPointerMoved(PointerRoutedEventArgs e)
+ {
+ base.OnPointerMoved(e);
+
+ if (!IsDragSelecting)
+ {
+ return;
+ }
+
+ var canvasPoint = GetCanvasPoint(e.GetCurrentPoint(this).Position);
+ if (canvasPoint is null)
+ {
+ return;
+ }
+
+ // Drive the rect visual for all drag sources (cell-initiated drags bubble pointer events here).
+ UpdateDragRectangleVisual(canvasPoint.Value);
+
+ // Selection-by-hit-test is only needed for TableView-initiated drags; cell-initiated
+ // drags perform selection in the cell's OnManipulationDelta via FindCell.
+ if (_tableViewDragPointer is not null)
+ {
+ MakeSelectionInDragRect();
+ }
+ }
+
+ ///
+ /// Makes selection based on the current drag rectangle, selecting either rows or cells depending on the last selection unit.
+ ///
+ private void MakeSelectionInDragRect()
+ {
+ if (_lastDragCanvasPoint is null) return;
+
+ if (LastSelectionUnit is not TableViewSelectionUnit.Cell && GetRowIndexAtCanvasPoint(_lastDragCanvasPoint.Value) is int row)
+ {
+ SelectionStartRowIndex ??= row;
+ var minRow = Math.Min(SelectionStartRowIndex.Value, row);
+ var maxRow = Math.Max(SelectionStartRowIndex.Value, row);
+ var rows = new ItemIndexRange(minRow, (uint)(maxRow - minRow + 1));
+
+ SelectRowsInDragRect(rows);
+ }
+ else if (LastSelectionUnit is not TableViewSelectionUnit.Row && GetSlotAtCanvasPoint(_lastDragCanvasPoint.Value) is { } slot)
+ {
+ if (SelectionStartCellSlot is null)
+ {
+ var startColumn = slot.Column;
+
+ if (_dragStartPoint is not null)
+ {
+ var horizontalScrollDelta = HorizontalOffset - _dragStartHorizontalOffset;
+ var startX = _dragStartPoint.Value.X - horizontalScrollDelta;
+ startColumn = GetColumnIndexAtCanvasX(startX)
+ ?? (startX < CellsHorizontalOffset - HorizontalOffset ? 0 : Columns.VisibleColumns.Count - 1);
+ }
+
+ SelectionStartCellSlot = new(SelectionStartRowIndex ?? slot.Row, startColumn);
+ }
+
+ var startRow = Math.Min(SelectionStartCellSlot.Value.Row, slot.Row);
+ var endRow = Math.Max(SelectionStartCellSlot.Value.Row, slot.Row);
+ var startCol = Math.Min(SelectionStartCellSlot.Value.Column, slot.Column);
+ var endCol = Math.Max(SelectionStartCellSlot.Value.Column, slot.Column);
+ var cells = TableViewCellSlotRange.FromCoordinates(startRow, startCol, endRow, endCol);
+
+ SelectCellsInDragRect(cells);
+ }
+ else if (LastSelectionUnit is not TableViewSelectionUnit.Cell && _lastDragSelectionRowRange?.Length > 0)
+ {
+ DeselectRange(_lastDragSelectionRowRange);
+
+ _lastDragSelectionRowRange = null;
+ SelectionStartRowIndex = null;
+ }
+ else if (LastSelectionUnit is not TableViewSelectionUnit.Row && _lastDragSelectionCellRange?.Length > 0)
+ {
+ DeselectCellRange(_lastDragSelectionCellRange);
+
+ _lastDragSelectionCellRange = null;
+ SelectionStartCellSlot = null;
+ }
+ }
+
+ ///
+ /// Selects rows that intersect with the current drag rectangle, updating the selection state accordingly.
+ ///
+ private void SelectRowsInDragRect(ItemIndexRange rows)
+ {
+ if (_lastDragSelectionRowRange?.FirstIndex == rows.FirstIndex && _lastDragSelectionRowRange?.LastIndex == rows.LastIndex) return;
+
+ if (SelectionMode is ListViewSelectionMode.Single && rows.Length is 1)
+ {
+ SelectedIndex = rows.FirstIndex;
+ }
+ else if (_lastDragSelectionRowRange is not null && _lastDragSelectionRowRange.Contains(rows))
+ {
+ foreach (var slicedRange in _lastDragSelectionRowRange.Subtract(rows))
+ {
+ DeselectRange(slicedRange);
+ }
+ }
+ else if (rows.Length > 0)
+ {
+ SelectRange(rows);
+ }
+
+ _lastDragSelectionRowRange = rows;
+ }
+
+ ///
+ /// Selects cells that intersect with the current drag rectangle, updating the selection state accordingly.
+ ///
+ private void SelectCellsInDragRect(TableViewCellSlotRange cells)
+ {
+ if (_lastDragSelectionCellRange == cells) return;
+
+ DispatcherQueue.TryEnqueue(() =>
+ {
+ if (_lastDragSelectionCellRange is null
+ && !KeyboardHelper.IsCtrlKeyDown()
+ && SelectionMode is not ListViewSelectionMode.Multiple)
+ {
+ DeselectAllItems();
+ SelectedCellRanges.Clear();
+ }
+ else if (_lastDragSelectionCellRange is not null && cells is not null)
+ {
+ foreach (var range in _lastDragSelectionCellRange.Subtract(cells))
+ {
+ SubtractCellRangeFromSelection(range);
+ }
+ }
+
+ if (SelectedCellRanges.Any(r => r == cells))
+ {
+ OnCellSelectionChanged();
+ }
+ else if (cells?.Length > 0)
+ {
+ SelectCellRange(cells);
+ }
+
+ _lastDragSelectionCellRange = cells;
+ });
+ }
+
+ ///
+ /// Handles pointer-released for all cases, including when elements sets e.Handled = true.
+ ///
+ private void OnAnyPointerReleased(object sender, PointerRoutedEventArgs e)
+ {
+ EndDragSelection();
+ }
+
///
/// Handles navigation keys.
///
@@ -313,6 +598,9 @@ private int CalculateAvailablePageSize()
return (int)Math.Floor(availableHeight / rowHeight);
}
+ ///
+ /// Ends the editing of a cell, committing or canceling the edit based on the specified action.
+ ///
internal bool EndCellEditing(TableViewEditAction editAction, TableViewCell cell)
{
var editingElement = cell.Content as FrameworkElement;
@@ -370,6 +658,7 @@ protected async override void OnApplyTemplate()
DragRectangleCanvas = GetTemplateChild("DragRectangleCanvas") as Canvas;
_dragRectangle = GetTemplateChild("DragRectangle") as Border;
_scrollViewer?.Loaded += OnScrollViewerLoaded;
+ _scrollViewer?.ViewChanged += OnScrollViewerViewChanged;
if (IsLoaded)
{
@@ -381,6 +670,17 @@ protected async override void OnApplyTemplate()
SetHeadersVisibility();
}
+ ///
+ /// Handles the ViewChanged event of the ScrollViewer control, updating the position of each row when the view changes.
+ ///
+ private void OnScrollViewerViewChanged(object? sender, ScrollViewerViewChangedEventArgs e)
+ {
+ foreach (var row in _rows)
+ {
+ row.UpdatePosition();
+ }
+ }
+
///
/// Handles the Loaded event of the ScrollViewer control.
///
@@ -573,8 +873,7 @@ internal void CopyToClipboardInternal(bool includeHeaders)
{
// Clipboard failures are normal on Windows (e.g., CLIPBRD_E_CANT_OPEN).
// Swallow to avoid crashing the application.
- Debug.WriteLine(
- $"TableView: Clipboard.SetContent failed: {ex}");
+ TableViewTrace.Write($"TableView: Clipboard.SetContent failed: {ex}");
}
}
@@ -1053,7 +1352,7 @@ private void SelectAllCells()
if (Items.Count > 0 && Columns.VisibleColumns.Count > 0)
{
SelectedCellRanges.Clear();
- SelectedCellRanges.Add([new TableViewCellSlot(0, 0)]);
+ SelectedCellRanges.Add(TableViewCellSlotRange.FromSlots(new(0, 0)));
}
break;
case ListViewSelectionMode.Multiple:
@@ -1145,7 +1444,7 @@ internal void MakeSelection(TableViewCellSlot slot, bool shiftKey, bool ctrlKey
else
{
if (SelectionUnit is TableViewSelectionUnit.CellWithRow)
- {
+ {
SelectRows(slot, shiftKey, ctrlKey);
}
else if (!ctrlKey)
@@ -1246,17 +1545,15 @@ private void SelectCells(TableViewCellSlot slot, bool shiftKey, bool ctrlKey)
}
}
- var selectionRange = (SelectionStartCellSlot is null ? null : SelectedCellRanges.LastOrDefault(x => SelectionStartCellSlot.HasValue && x.Contains(SelectionStartCellSlot.Value))) ?? [];
+ var selectionRange = (SelectionStartCellSlot is null ? null : SelectedCellRanges.LastOrDefault(x => SelectionStartCellSlot.HasValue && x.Contains(SelectionStartCellSlot.Value.Row, SelectionStartCellSlot.Value.Column)));
if (ctrlKey && SelectionMode is ListViewSelectionMode.Multiple or ListViewSelectionMode.Extended)
{
- selectionRange = SelectedCellRanges.SelectMany(x => x).ToHashSet();
- SelectedCellRanges.Clear();
+ // Keep existing ranges; the new slot/range will be added alongside them.
}
else
{
- SelectedCellRanges.Remove(selectionRange);
- selectionRange.Clear();
+ SelectedCellRanges.Remove(selectionRange!);
}
SelectionStartCellSlot ??= CurrentCellSlot;
@@ -1264,36 +1561,14 @@ private void SelectCells(TableViewCellSlot slot, bool shiftKey, bool ctrlKey)
if (shiftKey && SelectionMode is ListViewSelectionMode.Multiple or ListViewSelectionMode.Extended)
{
- var currentSlot = SelectionStartCellSlot.Value;
- var startRow = Math.Min(slot.Row, currentSlot.Row);
- var endRow = Math.Max(slot.Row, currentSlot.Row);
- var startCol = Math.Min(slot.Column, currentSlot.Column);
- var endCol = Math.Max(slot.Column, currentSlot.Column);
- for (var row = startRow; row <= endRow; row++)
- {
- for (var column = startCol; column <= endCol; column++)
- {
- var nextSlot = new TableViewCellSlot(row, column);
- selectionRange.Add(nextSlot);
- if (SelectedCellRanges.LastOrDefault(x => x.Contains(nextSlot)) is { } range)
- {
- range.Remove(nextSlot);
- }
- }
- }
+ var newRange = TableViewCellSlotRange.FromSlots(SelectionStartCellSlot.Value, slot);
+ SelectedCellRanges.Add(newRange);
}
else
{
SelectionStartCellSlot = slot;
- selectionRange.Add(slot);
-
- if (SelectedCellRanges.LastOrDefault(x => x.Contains(slot)) is { } range)
- {
- range.Remove(slot);
- }
+ SelectedCellRanges.Add(TableViewCellSlotRange.FromSlots(slot));
}
-
- SelectedCellRanges.Add(selectionRange);
OnCellSelectionChanged();
CurrentCellSlot = slot;
}
@@ -1303,18 +1578,65 @@ private void SelectCells(TableViewCellSlot slot, bool shiftKey, bool ctrlKey)
///
internal void DeselectCell(TableViewCellSlot slot)
{
- var selectionRange = SelectedCellRanges.LastOrDefault(x => x.Contains(slot));
- selectionRange?.Remove(slot);
+ var singleCellRange = TableViewCellSlotRange.FromSlots(slot);
+ var containingRanges = SelectedCellRanges.Where(x => x.Contains(slot.Row, slot.Column)).ToList();
- if (selectionRange?.Count == 0)
+ foreach (var range in containingRanges)
{
- SelectedCellRanges.Remove(selectionRange);
+ SelectedCellRanges.Remove(range);
+ foreach (var remaining in range.Subtract(singleCellRange))
+ {
+ SelectedCellRanges.Add(remaining);
+ }
}
CurrentCellSlot = slot;
OnCellSelectionChanged();
}
+ ///
+ /// Selects all the cells within the specified range, raising the event only once.
+ ///
+ /// The range of cell slots to select.
+ public void SelectCellRange(TableViewCellSlotRange? range)
+ {
+ if (range is null || range.Length <= 0
+ || !range.IsValid(this)
+ || SelectionMode is ListViewSelectionMode.None
+ || SelectionUnit is TableViewSelectionUnit.Row)
+ {
+ return;
+ }
+
+ if (SelectedCellRanges.Any(x => x == range)) return;
+
+ if (SelectionUnit is TableViewSelectionUnit.CellWithRow)
+ {
+ _suppressSelectionChangedCellClear = true;
+ var rowRange = new ItemIndexRange(range.FirstRow, (uint)range.Rows);
+ SelectRange(rowRange);
+ }
+
+ SubtractCellRangeFromSelection(range);
+ SelectedCellRanges.Add(range);
+ OnCellSelectionChanged();
+ }
+
+ ///
+ /// Deselects all the cells within the specified range, raising the event only once.
+ ///
+ /// The range of cell slots to deselect.
+ public void DeselectCellRange(TableViewCellSlotRange? range)
+ {
+ if (range is null || range.Length <= 0 || SelectedCellRanges.Count is 0)
+ {
+ return;
+ }
+
+ SubtractCellRangeFromSelection(range);
+ OnCellSelectionChanged();
+ }
+
///
/// Handles changes to the current cell in the table view.
///
@@ -1333,18 +1655,9 @@ private async Task OnCurrentCellChanged(TableViewCellSlot? oldSlot, TableViewCel
if (newSlot.HasValue)
{
- // During drag selection, skip expensive scroll-into-view and focus operations.
- // The drag rectangle handles visual feedback, and focus is restored when dragging ends.
- if (IsDragSelecting)
- {
- var cell = GetCellFromSlot(newSlot.Value);
- cell?.ApplyCurrentCellState(skipFocus: true);
- }
- else
- {
- var cell = await ScrollCellIntoView(newSlot.Value);
- cell?.ApplyCurrentCellState();
- }
+ var cell = await ScrollCellIntoView(newSlot.Value);
+ cell?.ApplyCurrentCellState();
+ cell?.Focus(FocusState.Programmatic);
}
}
@@ -1353,43 +1666,38 @@ private async Task OnCurrentCellChanged(TableViewCellSlot? oldSlot, TableViewCel
///
private void OnCellSelectionChanged()
{
- if (_cellSelectionDirty) return;
- _cellSelectionDirty = true;
+ var newSelection = SelectedCellRanges.SelectMany(x => x.GetSlots()).ToHashSet();
+ var removedCells = SelectedCells.Where(s => !newSelection.Contains(s)).ToList();
+ var addedCells = newSelection.Where(s => !SelectedCells.Contains(s)).ToList();
- if (!DispatcherQueue.TryEnqueue(() =>
- {
- _cellSelectionDirty = false;
+ if (removedCells.Count is 0 && addedCells.Count is 0) return;
- var oldSelection = SelectedCells;
- SelectedCells = [.. SelectedCellRanges.SelectMany(x => x)];
+ foreach (var slot in removedCells) SelectedCells.Remove(slot);
+ foreach (var slot in addedCells) SelectedCells.Add(slot);
- var rowIndexes = oldSelection.Select(x => x.Row).Concat(SelectedCells.Select(x => x.Row)).Distinct();
+ OnCellSelectionChanged(new TableViewCellSelectionChangedEventArgs(removedCells, addedCells));
- foreach (var rowIndex in rowIndexes)
- {
- var row = _rows.FirstOrDefault(x => x.Index == rowIndex);
- row?.ApplyCellsSelectionState();
- }
+ foreach (var slot in removedCells.Concat(addedCells))
+ _pendingCellStateRows.Add(slot.Row);
- InvokeCellSelectionChangedEvent(oldSelection);
- }))
+ if (!_cellStateDispatchPending)
{
- _cellSelectionDirty = false;
+ _cellStateDispatchPending = true;
+ DispatcherQueue.TryEnqueue(ApplyPendingCellStates);
}
}
- ///
- /// Invokes the event to notify subscribers of changes in the selected cells.
- ///
- private void InvokeCellSelectionChangedEvent(HashSet oldSelection)
+ private void ApplyPendingCellStates()
{
- var removedCells = oldSelection.Except(SelectedCells).ToList();
- var addedCells = SelectedCells.Except(oldSelection).ToList();
+ _cellStateDispatchPending = false;
+ if (_pendingCellStateRows.Count is 0) return;
- if (removedCells.Count > 0 || addedCells.Count > 0)
+ foreach (var row in _rows)
{
- OnCellSelectionChanged(new TableViewCellSelectionChangedEventArgs(removedCells, addedCells));
+ if (_pendingCellStateRows.Contains(row.Index))
+ row.ApplyCellsSelectionState();
}
+ _pendingCellStateRows.Clear();
}
///
@@ -1414,13 +1722,10 @@ internal void StartDragSelection(Point startPoint)
_dragStartVerticalOffset = _scrollViewer?.VerticalOffset ?? 0;
_dragStartHorizontalOffset = HorizontalOffset;
- if (_scrollViewer is not null)
- {
- _scrollViewer.ViewChanged += OnScrollViewerViewChangedDuringDrag;
- }
+ _scrollViewer?.ViewChanged += OnScrollViewerViewChangedDuringDrag;
// Show the drag rectangle visual if enabled and template parts are available
- if (ShowDragRectangle && DragRectangleCanvas is not null && _dragRectangle is not null)
+ if (DragRectangleCanvas is not null && _dragRectangle is not null)
{
_dragStartPoint = startPoint;
@@ -1429,7 +1734,7 @@ internal void StartDragSelection(Point startPoint)
_dragRectangle.Width = 0;
_dragRectangle.Height = 0;
- _dragRectangle.Visibility = Visibility.Visible;
+ _dragRectangle.Visibility = ShowDragRectangle ? Visibility.Visible : Visibility.Collapsed;
}
}
@@ -1455,6 +1760,26 @@ internal void UpdateDragRectangleVisual(Point currentPoint)
UpdateAutoScroll(currentPoint);
}
+ ///
+ /// Transforms a point relative to this into coordinates relative to the .
+ /// Returns null when the canvas is unavailable or the transform cannot be computed.
+ /// A negative Y value indicates the point is above the scroll area (column header territory).
+ ///
+ /// The position relative to this TableView.
+ /// The canvas-relative point, or null if unavailable.
+ private Point? GetCanvasPoint(Point position)
+ {
+ if (DragRectangleCanvas is null) return null;
+ try
+ {
+ return TransformToVisual(DragRectangleCanvas).TransformPoint(position);
+ }
+ catch (ArgumentException)
+ {
+ return null;
+ }
+ }
+
///
/// Positions the drag rectangle visual from the scroll-adjusted start point to the current point,
/// so the rectangle follows the mouse and extends naturally when content scrolls.
@@ -1529,9 +1854,9 @@ private void UpdateAutoScroll(Point canvasPoint)
{
_autoScrollTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(16) };
_autoScrollTimer.Tick += OnAutoScrollTimerTick;
+ _autoScrollTimer.Start();
}
-
- _autoScrollTimer.Start();
+ // else: timer already running — delta values above are picked up on the next tick
}
else
{
@@ -1588,17 +1913,21 @@ private void OnAutoScrollTimerTick(object? sender, object e)
return;
}
- // Horizontal scroll via HorizontalOffset DP does not fire ViewChanged,
- // so reposition rectangle and update selection here.
- // Vertical scroll fires ViewChanged which handles it via OnScrollViewerViewChangedDuringDrag.
- if (Math.Abs(_autoScrollHorizontalDelta) > 0.5 && _lastDragCanvasPoint is not null)
+ // Horizontal scroll does not fire ViewChanged, so reposition the rectangle here.
+ // Selection is updated for all scroll directions from the timer tick, not from ViewChanged,
+ // so that MakeSelectionInDragRect runs after ChangeView completes rather than inside the layout pass.
+ if (_lastDragCanvasPoint is not null)
{
- if (_dragStartPoint is not null && DragRectangleCanvas is not null && _dragRectangle is not null)
+ if (Math.Abs(_autoScrollHorizontalDelta) > 0.5 &&
+ _dragStartPoint is not null && DragRectangleCanvas is not null && _dragRectangle is not null)
{
PositionDragRectangle(_lastDragCanvasPoint.Value);
}
- SelectCellAtDragPoint();
+ if (_tableViewDragPointer is not null)
+ {
+ MakeSelectionInDragRect();
+ }
}
}
@@ -1628,52 +1957,11 @@ private void OnScrollViewerViewChangedDuringDrag(object? sender, ScrollViewerVie
PositionDragRectangle(_lastDragCanvasPoint.Value);
}
- // Update selection for newly visible rows during auto-scroll
- SelectCellAtDragPoint();
- }
-
- ///
- /// Selects the cell at the last known drag pointer position.
- /// Used during auto-scroll to select newly visible cells when the pointer isn't moving.
- ///
- private void SelectCellAtDragPoint()
- {
- if (_scrollViewer is null || _lastDragCanvasPoint is null || DragRectangleCanvas is null)
- {
- return;
- }
-
- // Clamp to the cell area within the viewport.
- // CellsHorizontalOffset accounts for row headers so we don't hit-test on header area.
- var canvasPoint = _lastDragCanvasPoint.Value;
- var minX = CellsHorizontalOffset + 1;
- var clampedPoint = new Point(
- Math.Clamp(canvasPoint.X, minX, Math.Max(minX, _scrollViewer.ViewportWidth - 1)),
- Math.Clamp(canvasPoint.Y, 1, Math.Max(1, _scrollViewer.ViewportHeight - 1)));
-
- try
- {
- var screenPoint = DragRectangleCanvas.TransformToVisual(null).TransformPoint(clampedPoint);
-#if WINDOWS
- var cell = VisualTreeHelper.FindElementsInHostCoordinates(screenPoint, _scrollViewer)
-#else
- var cell = VisualTreeHelper.FindElementsInHostCoordinates(screenPoint, _scrollViewer, true)
- .OfType()
- .Where(x => x.Name is "Content")
- .Select(x => x.FindAscendant() is { } c ? c : default)
-#endif
- .OfType()
- .FirstOrDefault();
-
- if (cell is not null && cell.Slot != CurrentCellSlot)
- {
- var ctrlKey = KeyboardHelper.IsCtrlKeyDown();
- MakeSelection(cell.Slot, true, ctrlKey);
- }
- }
- catch (ArgumentException)
+ // During auto-scroll the timer tick owns selection updates to keep MakeSelectionInDragRect
+ // out of the scroll layout pass. Only update here for non-auto-scroll scrolls (e.g. scroll wheel).
+ if (_autoScrollTimer is null && _tableViewDragPointer is not null)
{
- // Element not in visual tree during container recycling
+ MakeSelectionInDragRect();
}
}
@@ -1682,36 +1970,60 @@ private void SelectCellAtDragPoint()
///
internal async void EndDragSelection()
{
- if (!IsDragSelecting) return;
+ if (!IsDragSelecting || _lastDragCanvasPoint is null) return;
StopAutoScroll();
- if (_scrollViewer is not null)
- {
- _scrollViewer.ViewChanged -= OnScrollViewerViewChangedDuringDrag;
- }
+ _pointerCaptureElement?.ReleasePointerCaptures();
+ _pointerCaptureElement = null;
+ _tableViewDragPointer = null;
- if (_dragRectangle is not null)
- {
- _dragRectangle.Visibility = Visibility.Collapsed;
- }
+ _scrollViewer?.ViewChanged -= OnScrollViewerViewChangedDuringDrag;
+ _dragRectangle?.Visibility = Visibility.Collapsed;
+
+ SetCurrentCellFromCanvasPoint(_lastDragCanvasPoint.Value);
IsDragSelecting = false;
_dragStartPoint = null;
_lastDragCanvasPoint = null;
+ SelectionStartCellSlot = null;
+ }
- // Restore focus and scroll to the current cell now that dragging has ended
- try
+ private void SetCurrentCellFromCanvasPoint(Point canvasPoint)
+ {
+ if (GetSlotAtCanvasPoint(canvasPoint, exactMatch: true) is { } endSlot)
{
- if (CurrentCellSlot.HasValue)
+ CurrentRowIndex = endSlot.Row;
+
+ if (!(SelectionUnit is TableViewSelectionUnit.Row && IsReadOnly))
{
- var cell = await ScrollCellIntoView(CurrentCellSlot.Value);
- cell?.ApplyCurrentCellState();
+ CurrentCellSlot = endSlot;
+#if !WINDOWS
+ if (_dragStartCell is not null
+ && _dragStartPoint is not null
+ && endSlot != _dragStartCell.Slot)
+ {
+ VisualStates.GoToState(_dragStartCell, false, VisualStates.StateNormal);
+
+ if (_dragStartCell.IsSelected)
+ {
+ VisualStates.GoToState(_dragStartCell, false, VisualStates.StateSelected);
+ }
+ }
+#endif
}
- }
- catch (Exception)
- {
- // Focus restoration is best-effort after drag ends
+
+#if !WINDOWS
+ if (_dragStartRow is not null && _dragStartRow.Index != endSlot.Row)
+ {
+ VisualStates.GoToState(_dragStartRow, false, VisualStates.StateNormal);
+
+ if (_dragStartRow.IsSelected)
+ {
+ VisualStates.GoToState(_dragStartRow, false, VisualStates.StateSelected);
+ }
+ }
+#endif
}
}
@@ -1828,6 +2140,119 @@ void ViewChanged(object? _, ScrollViewerViewChangedEventArgs e)
return slot.IsValid(this) && ContainerFromIndex(slot.Row) is TableViewRow row ? row.Cells[slot.Column] : default;
}
+ ///
+ /// Returns the index of the row that contains , or the nearest row
+ /// within the vertical span between the drag start point and when
+ /// the point falls in empty space. Returns null when no realized row falls in that span.
+ ///
+ private int? GetRowIndexAtCanvasPoint(Point canvasPoint, bool exactMatch = false)
+ {
+ if (DragRectangleCanvas is null) return null;
+
+ if (exactMatch)
+ {
+ foreach (var row in _rows)
+ {
+ var rowTop = row.Position;
+ var rowBottom = rowTop + row.ActualHeight;
+
+ if (canvasPoint.Y >= rowTop && canvasPoint.Y < rowBottom)
+ {
+ return row.Index;
+ }
+ }
+
+ return null;
+ }
+
+ // Compute the vertical span of the drag so we can snap to the nearest in-span row when the
+ // pointer is in empty space. If there is no drag start (called outside a drag), minY == maxY
+ // == canvasPoint.Y, which collapses back to the original exact hit-test behaviour.
+ var verticalScrollDelta = (_scrollViewer?.VerticalOffset ?? 0) - _dragStartVerticalOffset;
+ var adjustedStartY = _dragStartPoint is not null
+ ? _dragStartPoint.Value.Y - verticalScrollDelta
+ : canvasPoint.Y;
+
+ var minY = Math.Min(adjustedStartY, canvasPoint.Y);
+ var maxY = Math.Max(adjustedStartY, canvasPoint.Y);
+
+ TableViewRow? nearestRow = null;
+ var nearestDistance = double.MaxValue;
+
+ foreach (var row in _rows)
+ {
+ var rowTop = row.Position;
+ var rowBottom = rowTop + row.ActualHeight;
+
+ if (rowBottom <= minY || rowTop >= maxY)
+ continue;
+
+ var distance = Math.Max(0d, Math.Max(rowTop - canvasPoint.Y, canvasPoint.Y - rowBottom));
+ if (distance < nearestDistance)
+ {
+ nearestDistance = distance;
+ nearestRow = row;
+ }
+ }
+
+ return nearestRow?.Index;
+ }
+
+ ///
+ /// Returns the index of the visible column whose bounds contain the given canvas X coordinate.
+ /// Returns null when x falls outside the column area or there are no visible columns.
+ ///
+ private int? GetColumnIndexAtCanvasX(double x)
+ {
+ var columnLeft = CellsHorizontalOffset - HorizontalOffset;
+ for (var i = 0; i < Columns.VisibleColumns.Count; i++)
+ {
+ var columnRight = columnLeft + Columns.VisibleColumns[i].ActualWidth;
+ if (x >= columnLeft && x < columnRight)
+ return i;
+ columnLeft = columnRight;
+ }
+ return null;
+ }
+
+ ///
+ /// Resolves the cell slot at , snapping to the nearest row and
+ /// column within the horizontal and vertical span of the current drag when the point falls in
+ /// empty space. Returns null when no realized row or visible column falls in that span.
+ ///
+ private TableViewCellSlot? GetSlotAtCanvasPoint(Point canvasPoint, bool exactMatch = false)
+ {
+ if (DragRectangleCanvas is null) return null;
+
+ if (GetRowIndexAtCanvasPoint(canvasPoint, exactMatch) is not int rowIndex) return null;
+
+ // Mirror the row snapping: find the nearest column within the horizontal drag span.
+ var horizontalScrollDelta = HorizontalOffset - _dragStartHorizontalOffset;
+ var adjustedPointerX = canvasPoint.X - horizontalScrollDelta;
+
+ var columnLeft = CellsHorizontalOffset - HorizontalOffset;
+
+ for (var i = 0; i < Columns.VisibleColumns.Count; i++)
+ {
+ var columnRight = columnLeft + Columns.VisibleColumns[i].ActualWidth;
+
+ if (adjustedPointerX <= columnRight)
+ {
+ // Require the pointer to actually be inside the cell.
+ if (exactMatch && adjustedPointerX < columnLeft)
+ return null;
+
+ return new(rowIndex, i);
+ }
+
+ columnLeft = columnRight;
+ }
+
+ return exactMatch || Columns.VisibleColumns.Count == 0
+ ? null
+ : new(rowIndex, Columns.VisibleColumns.Count - 1);
+ }
+
///
/// Gets the columns currently in view.
///
diff --git a/src/TableViewCell.cs b/src/TableViewCell.cs
index 523f8a46..d1955299 100644
--- a/src/TableViewCell.cs
+++ b/src/TableViewCell.cs
@@ -5,10 +5,6 @@
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
using Windows.Foundation;
using WinUI.TableView.Extensions;
using WinUI.TableView.Helpers;
@@ -29,7 +25,6 @@ namespace WinUI.TableView;
#endif
public partial class TableViewCell : ContentControl
{
- private ScrollViewer? _scrollViewer;
private ContentPresenter? _contentPresenter;
private Border? _selectionBorder;
private Rectangle? _v_gridLine;
@@ -230,12 +225,6 @@ protected override void OnTapped(TappedRoutedEventArgs e)
e.Handled = true;
return;
}
-
- if (TableView?.CurrentCellSlot != Slot || TableView?.LastSelectionUnit is TableViewSelectionUnit.Row)
- {
- MakeSelection();
- e.Handled = true;
- }
}
///
@@ -248,76 +237,15 @@ protected override void OnPointerPressed(PointerRoutedEventArgs e)
e.Handled = true;
return;
}
-
- if (!KeyboardHelper.IsShiftKeyDown() && TableView is not null)
- {
- TableView.SelectionStartCellSlot = TableView.SelectionUnit is not TableViewSelectionUnit.Row || !IsReadOnly ? Slot : default;
- TableView.SelectionStartRowIndex = Index;
- CapturePointer(e.Pointer);
-
- // Start drag selection (auto-scroll + optional rectangle visual)
- var point = e.GetCurrentPoint(this).Position;
- var canvasPoint = TransformPointToCanvas(point);
- if (canvasPoint.HasValue)
- {
- TableView.StartDragSelection(canvasPoint.Value);
- }
- }
}
-
///
protected override void OnPointerReleased(PointerRoutedEventArgs e)
{
base.OnPointerReleased(e);
- if (!KeyboardHelper.IsShiftKeyDown() && TableView is not null)
- {
- var cell = FindCell(e.GetCurrentPoint(this).Position);
- TableView.SelectionStartCellSlot = TableView.SelectionUnit is not TableViewSelectionUnit.Row || !IsReadOnly ? cell?.Slot : default;
- TableView.SelectionStartRowIndex = cell?.Slot.Row;
- }
-
- TableView?.EndDragSelection();
- ReleasePointerCaptures();
-
- e.Handled = true;
- }
-
- ///
- protected override void OnPointerCaptureLost(PointerRoutedEventArgs e)
- {
- base.OnPointerCaptureLost(e);
-
- TableView?.EndDragSelection();
- }
-
- ///
- protected override void OnManipulationDelta(ManipulationDeltaRoutedEventArgs e)
- {
- base.OnManipulationDelta(e);
-
- if (PointerCaptures?.Any() is true)
+ if(TableView?.SelectionUnit is not TableViewSelectionUnit.Row)
{
- // Update drag rectangle visual and auto-scroll
- if (TableView?.IsDragSelecting is true)
- {
- var canvasPoint = TransformPointToCanvas(e.Position);
- if (canvasPoint.HasValue)
- {
- TableView.UpdateDragRectangleVisual(canvasPoint.Value);
- }
- }
-
- // Selection via FindCell — same proven path whether rectangle is on or off.
- // When the pointer is outside the viewport, FindCell returns null and selection
- // is updated by the ViewChanged handler on the next auto-scroll tick.
- var cell = FindCell(e.Position);
-
- if (cell is not null && cell.Slot != TableView?.CurrentCellSlot)
- {
- var ctrlKey = KeyboardHelper.IsCtrlKeyDown();
- TableView?.MakeSelection(cell.Slot, true, ctrlKey);
- }
+ e.Handled = true;
}
}
@@ -350,45 +278,6 @@ private double GetHorizontalGridlineHeight()
? TableView.HorizontalGridLinesStrokeThickness : 0d;
}
- ///
- /// Finds the cell at the specified position.
- ///
- private TableViewCell? FindCell(Point position)
- {
- _scrollViewer ??= TableView?.FindDescendant();
- if (_scrollViewer is null) return null;
-
- var transformedPoint = TransformToVisual(null).TransformPoint(position);
-#if WINDOWS
- return VisualTreeHelper.FindElementsInHostCoordinates(transformedPoint, _scrollViewer)
-#else
- return VisualTreeHelper.FindElementsInHostCoordinates(transformedPoint, _scrollViewer, true)
- .OfType()
- .Where(x => x.Name is "Content")
- .Select(x => x.FindAscendant() is { } header ? header : default)
-#endif
- .OfType()
- .FirstOrDefault();
- }
-
- ///
- /// Transforms a point relative to this cell to coordinates relative to the drag rectangle canvas.
- ///
- private Point? TransformPointToCanvas(Point position)
- {
- if (TableView?.DragRectangleCanvas is null) return null;
-
- try
- {
- var transform = TransformToVisual(TableView.DragRectangleCanvas);
- return transform.TransformPoint(position);
- }
- catch (ArgumentException)
- {
- return null;
- }
- }
-
///
protected override void OnDoubleTapped(DoubleTappedRoutedEventArgs e)
{
@@ -400,49 +289,7 @@ protected override void OnDoubleTapped(DoubleTappedRoutedEventArgs e)
base.OnDoubleTapped(e);
- if (!IsReadOnly && TableView is not null && !TableView.IsEditing && !Column?.UseSingleElement is true)
- {
- e.Handled = BeginCellEditing(e);
- }
- else
- {
- e.Handled = true;
- }
- }
-
- ///
- /// Makes a selection based on the current cell.
- ///
- private void MakeSelection()
- {
- var shiftKey = KeyboardHelper.IsShiftKeyDown();
- var ctrlKey = KeyboardHelper.IsCtrlKeyDown();
-
- if (TableView is null || Column is null)
- {
- return;
- }
-
- if ((TableView.IsEditing || Column.UseSingleElement) && IsCurrent)
- {
- return;
- }
-
- if (IsSelected && (ctrlKey || TableView.SelectionMode is ListViewSelectionMode.Multiple) && !shiftKey)
- {
- TableView.DeselectCell(Slot);
- }
- else
- {
- if (Column.UseSingleElement)
- {
- TableView.DeselectCell(Slot);
- }
-
- TableView.MakeSelection(Slot, shiftKey, ctrlKey);
- }
-
- TableView.SetIsEditing(false);
+ e.Handled = IsReadOnly || TableView is null || TableView.IsEditing || !Column?.UseSingleElement is not true || BeginCellEditing(e);
}
///
diff --git a/src/TableViewCellSlot.cs b/src/TableViewCellSlot.cs
index 980ef25d..093ce5d2 100644
--- a/src/TableViewCellSlot.cs
+++ b/src/TableViewCellSlot.cs
@@ -3,4 +3,11 @@
///
/// Represents a slot of a TableView cell, identified by its row and column indices.
///
-public readonly record struct TableViewCellSlot(int Row, int Column);
+public readonly record struct TableViewCellSlot(int Row, int Column)
+{
+ ///
+ public override string ToString()
+ {
+ return $"Row: {Row}, Col: {Column}";
+ }
+}
diff --git a/src/TableViewCellSlotRange.cs b/src/TableViewCellSlotRange.cs
new file mode 100644
index 00000000..8a70a7c0
--- /dev/null
+++ b/src/TableViewCellSlotRange.cs
@@ -0,0 +1,133 @@
+namespace WinUI.TableView;
+
+///
+/// Represents a coordinate-based range of cell slots in a TableView.
+///
+public class TableViewCellSlotRange
+{
+ ///
+ /// Gets the starting row index of the range.
+ ///
+ public int FirstRow { get; }
+
+ ///
+ /// Gets the starting column index of the range.
+ ///
+ public int FirstColumn { get; }
+
+ ///
+ /// Gets the first cell slot in the range.
+ ///
+ public TableViewCellSlot FirstSlot => new(FirstRow, FirstColumn);
+
+ ///
+ /// Gets the number of rows spanned by this range.
+ ///
+ public int Rows { get; }
+
+ ///
+ /// Gets the number of columns spanned by this range.
+ ///
+ public int Columns { get; }
+
+ ///
+ /// Gets the total number of cell slots in the range.
+ ///
+ public int Length => Rows * Columns;
+
+ ///
+ /// Gets the last row index included in the range.
+ ///
+ public int LastRow => FirstRow + Rows - 1;
+
+ ///
+ /// Gets the last column index included in the range.
+ ///
+ public int LastColumn => FirstColumn + Columns - 1;
+
+ ///
+ /// Gets the last cell slot in the range.
+ ///
+ public TableViewCellSlot LastSlot => new(LastRow, LastColumn);
+
+ ///
+ /// Initializes a new instance of the TableViewCellSlotRange class using starting indices and dimensions.
+ ///
+ public TableViewCellSlotRange(int firstRowIndex, int firstColumnIndex, int rowCount, int columnCount)
+ {
+ if (firstRowIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(firstRowIndex), "Index cannot be negative.");
+
+ if (firstColumnIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(firstColumnIndex), "Index cannot be negative.");
+
+ if (rowCount < 1)
+ throw new ArgumentOutOfRangeException(nameof(rowCount), "Count must be at least 1.");
+ if (columnCount < 1)
+ throw new ArgumentOutOfRangeException(nameof(columnCount), "Count must be at least 1.");
+
+ FirstRow = firstRowIndex;
+ FirstColumn = firstColumnIndex;
+ Rows = rowCount;
+ Columns = columnCount;
+ }
+
+ ///
+ /// Helper factory to initialize using start and end coordinates directly.
+ ///
+ public static TableViewCellSlotRange FromCoordinates(int startRow, int startCol, int endRow, int endCol)
+ {
+ var firstRow = Math.Min(startRow, endRow);
+ var firstCol = Math.Min(startCol, endCol);
+ var rowCount = Math.Abs(endRow - startRow) + 1;
+ var colCount = Math.Abs(endCol - startCol) + 1;
+
+ return new TableViewCellSlotRange(firstRow, firstCol, rowCount, colCount);
+ }
+
+ ///
+ /// Helper factory to initialize using two TableViewCellSlot instances.
+ ///
+ public static TableViewCellSlotRange FromSlots(TableViewCellSlot firstSlot, TableViewCellSlot? lastSlot = default)
+ {
+ lastSlot ??= firstSlot;
+ return FromCoordinates(firstSlot.Row, firstSlot.Column, lastSlot.Value.Row, lastSlot.Value.Column);
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ if (obj is TableViewCellSlotRange other)
+ {
+ return FirstRow == other.FirstRow &&
+ FirstColumn == other.FirstColumn &&
+ Rows == other.Rows &&
+ Columns == other.Columns;
+ }
+ return false;
+ }
+
+ ///
+ public static bool operator ==(TableViewCellSlotRange? left, TableViewCellSlotRange? right)
+ {
+ return Equals(left, right);
+ }
+
+ ///
+ public static bool operator !=(TableViewCellSlotRange? left, TableViewCellSlotRange? right)
+ {
+ return !Equals(left, right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(FirstRow, FirstColumn, Rows, Columns);
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"[{FirstSlot}]..[{LastSlot}] ({Length})";
+ }
+}
\ No newline at end of file
diff --git a/src/TableViewRow.cs b/src/TableViewRow.cs
index b2fe947d..d2a65c31 100644
--- a/src/TableViewRow.cs
+++ b/src/TableViewRow.cs
@@ -156,44 +156,6 @@ protected override void OnContentChanged(object oldContent, object newContent)
TableView?.EnsureAlternateRowColors();
}
- ///
- protected override void OnPointerPressed(PointerRoutedEventArgs e)
- {
- if (TableView is { IsEditing: false })
- {
- base.OnPointerPressed(e);
- }
-
- if (!KeyboardHelper.IsShiftKeyDown() && TableView is not null)
- {
- TableView.SelectionStartRowIndex = Index;
- }
- }
-
- ///
- protected override void OnPointerReleased(PointerRoutedEventArgs e)
- {
- base.OnPointerReleased(e);
-
- if (!KeyboardHelper.IsShiftKeyDown() && TableView is not null)
- {
- TableView.SelectionStartCellSlot = null;
- TableView.SelectionStartRowIndex = Index;
- }
- }
-
- ///
- protected override void OnTapped(TappedRoutedEventArgs e)
- {
- base.OnTapped(e);
-
- if (TableView?.SelectionUnit is TableViewSelectionUnit.Row or TableViewSelectionUnit.CellOrRow or TableViewSelectionUnit.CellWithRow)
- {
- TableView.CurrentRowIndex = Index;
- TableView.LastSelectionUnit = TableViewSelectionUnit.Row;
- }
- }
-
///
protected override void OnDoubleTapped(DoubleTappedRoutedEventArgs e)
{
@@ -213,10 +175,35 @@ protected override Size ArrangeOverride(Size finalSize)
var left = Math.Max(cornerRadius.TopLeft, cornerRadius.BottomLeft);
_itemPresenter?.Arrange(new Rect(-left, 0, _itemPresenter.ActualWidth + left, _itemPresenter.ActualHeight));
+
+ UpdatePosition();
return finalSize;
}
+ ///
+ /// Updates the position of the row relative to the TableView.
+ ///
+ internal void UpdatePosition()
+ {
+ DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
+ {
+ if (TableView is not null)
+ {
+ try
+ {
+ Position = TransformToVisual(TableView.DragRectangleCanvas).TransformPoint(default).Y;
+ }
+ catch { }
+ }
+ });
+ }
+
+ ///
+ /// Gets or sets the position of the row relative to the TableView.
+ ///
+ internal double Position { get; set; }
+
///
/// Ensures cells are created for the row.
///
diff --git a/src/Tableview.Uno.cs b/src/Tableview.Uno.cs
index 91fa2fcf..b69e7061 100644
--- a/src/Tableview.Uno.cs
+++ b/src/Tableview.Uno.cs
@@ -17,6 +17,8 @@ partial class TableView
private const BindingFlags BindingAttr = BindingFlags.NonPublic | BindingFlags.Instance;
private PropertyInfo? _disableRaiseSelectionChangedPropertyInfo;
private MethodInfo? _invokeSelectionChangedMethodInfo;
+ private TableViewRow? _dragStartRow;
+ private TableViewCell? _dragStartCell;
private void SetDisableRaiseSelectionChanged(bool value)
{
diff --git a/tests/DragSelectionRectangleTests.cs b/tests/DragSelectionRectangleTests.cs
index a85a3ace..805fddca 100644
--- a/tests/DragSelectionRectangleTests.cs
+++ b/tests/DragSelectionRectangleTests.cs
@@ -192,4 +192,40 @@ public void UpdateDragRectangleVisual_DoesNotCrash_WhenNotDragging()
Assert.IsFalse(tv.IsDragSelecting);
}
+
+ [UITestMethod]
+ public async Task SelectCellRange_SelectsTheProvidedRangeAndRaisesOneSelectionChangedEvent()
+ {
+ var tv = await CreateAndLoadTableView();
+ var eventCount = 0;
+ tv.CellSelectionChanged += (_, _) => eventCount++;
+
+ tv.SelectCellRange(TableViewCellSlotRange.FromCoordinates(0, 0, 1, 1));
+
+ Assert.AreEqual(4, tv.SelectedCells.Count);
+ Assert.IsTrue(tv.SelectedCells.Contains(new TableViewCellSlot(0, 0)));
+ Assert.IsTrue(tv.SelectedCells.Contains(new TableViewCellSlot(0, 1)));
+ Assert.IsTrue(tv.SelectedCells.Contains(new TableViewCellSlot(1, 0)));
+ Assert.IsTrue(tv.SelectedCells.Contains(new TableViewCellSlot(1, 1)));
+ Assert.AreEqual(1, eventCount);
+
+ await UnitTestApp.Current.MainWindow.UnloadTestContentAsync(tv);
+ }
+
+ [UITestMethod]
+ public async Task DeselectCellRange_DeselectsTheProvidedRangeAndRaisesOneSelectionChangedEvent()
+ {
+ var tv = await CreateAndLoadTableView();
+ tv.SelectCellRange(TableViewCellSlotRange.FromCoordinates(0, 0, 1, 1));
+
+ var eventCount = 0;
+ tv.CellSelectionChanged += (_, _) => eventCount++;
+
+ tv.DeselectCellRange(TableViewCellSlotRange.FromCoordinates(0, 0, 1, 1));
+
+ Assert.AreEqual(0, tv.SelectedCells.Count);
+ Assert.AreEqual(1, eventCount);
+
+ await UnitTestApp.Current.MainWindow.UnloadTestContentAsync(tv);
+ }
}
diff --git a/tests/TableViewSelectionUnitTests.cs b/tests/TableViewSelectionUnitTests.cs
index 2b1009c0..47bc062f 100644
--- a/tests/TableViewSelectionUnitTests.cs
+++ b/tests/TableViewSelectionUnitTests.cs
@@ -4,6 +4,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;
using System.Threading.Tasks;
+using Windows.Foundation;
namespace WinUI.TableView.Tests;
@@ -15,10 +16,10 @@ public async Task CellWithRow_CellClickSelectsCellAndOwningRow()
{
var tableView = await CreateTableViewAsync(TableViewSelectionUnit.CellWithRow);
- tableView.MakeSelection(new TableViewCellSlot(1, 0), false, false);
+ tableView.SelectCellRange(TableViewCellSlotRange.FromSlots(new TableViewCellSlot(1, 0), new TableViewCellSlot(1, 0)));
await Task.Yield(); // Allow selection to propagate
-
+
Assert.IsTrue(tableView.SelectedCells.Contains(new TableViewCellSlot(1, 0)));
Assert.AreEqual(1, tableView.SelectedItems.Count);
Assert.AreSame(tableView.Items[1], tableView.SelectedItem);
@@ -29,7 +30,7 @@ public async Task CellAndRow_RowHeaderClickSelectsOnlyRow()
{
var tableView = await CreateTableViewAsync(TableViewSelectionUnit.CellWithRow);
- tableView.MakeSelection(new TableViewCellSlot(1, -1), false, false);
+ tableView.SelectRange(new ItemIndexRange(1, 1));
Assert.AreEqual(0, tableView.SelectedCells.Count);
Assert.AreEqual(1, tableView.SelectedItems.Count);
@@ -41,11 +42,11 @@ public async Task CellSelectionUnitStillSelectsOnlyCells()
{
var tableView = await CreateTableViewAsync(TableViewSelectionUnit.Cell);
- tableView.MakeSelection(new TableViewCellSlot(0, 0), false, false);
+ tableView.SelectCellRange(TableViewCellSlotRange.FromSlots(new TableViewCellSlot(0, 0), new TableViewCellSlot(0, 0)));
await Task.Yield(); // Allow selection to propagate
- tableView.MakeSelection(new TableViewCellSlot(1, 1), false, true);
+ tableView.SelectCellRange(TableViewCellSlotRange.FromSlots(new TableViewCellSlot(1, 1), new TableViewCellSlot(1, 1)));
await Task.Yield(); // Allow selection to propagate
@@ -60,10 +61,10 @@ public async Task CellOrRowSelectionUnitStillUsesCellAndRowSemantics()
{
var tableView = await CreateTableViewAsync(TableViewSelectionUnit.CellOrRow);
- tableView.MakeSelection(new TableViewCellSlot(1, 0), false, false);
+ tableView.SelectCellRange(TableViewCellSlotRange.FromSlots(new TableViewCellSlot(1, 0), new TableViewCellSlot(1, 0)));
Assert.AreEqual(0, tableView.SelectedItems.Count);
- tableView.MakeSelection(new TableViewCellSlot(0, -1), false, false);
+ tableView.SelectRange(new ItemIndexRange(0, 1));
Assert.AreEqual(1, tableView.SelectedItems.Count);
Assert.AreEqual(0, tableView.SelectedCells.Count);
}
@@ -73,11 +74,11 @@ public async Task CellWithRow_MultiSelectionAddsCellAndRowSelections()
{
var tableView = await CreateTableViewAsync(TableViewSelectionUnit.CellWithRow, ListViewSelectionMode.Multiple);
- tableView.MakeSelection(new TableViewCellSlot(0, 0), false, false);
+ tableView.SelectCellRange(TableViewCellSlotRange.FromSlots(new TableViewCellSlot(0, 0), new TableViewCellSlot(0, 0)));
await Task.Yield(); // Allow selection to propagate
- tableView.MakeSelection(new TableViewCellSlot(1, 1), false, true);
+ tableView.SelectCellRange(TableViewCellSlotRange.FromSlots(new TableViewCellSlot(1, 1), new TableViewCellSlot(1, 1)));
await Task.Delay(200); // Allow selection to propagate