Skip to content

feat: allow drag selection to start from rows and empty space - #417

Draft
w-ahmad wants to merge 11 commits into
mainfrom
feat/improve-drag-selection
Draft

feat: allow drag selection to start from rows and empty space#417
w-ahmad wants to merge 11 commits into
mainfrom
feat/improve-drag-selection

Conversation

@w-ahmad

@w-ahmad w-ahmad commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

Improves the drag-selection experience in TableView that was added in PR #344 by allowing selection gestures to begin from row containers and empty space — not just individual cells. This makes the control feel much more natural and consistent with other data-grid implementations.

Recording.2026-07-30.034511.mp4

Changes

New Types

Type Description
TableViewCellSlotRange A coordinate-based, rectangular range of cell slots (first/last row + column). Includes Equals, GetHashCode, equality operators, and factory helpers (FromCoordinates, FromSlots).
TableViewCellSlotRangeExtensions Extension methods for TableViewCellSlotRange: IsInRange, IsRowInRange, IsColumnInRange, IsValid, GetSlots, Contains, IntersectsWith, Subtract, Merge.
IndexRangeHelper Utility helper for index-range arithmetic used internally during selection.
TableViewTrace [Conditional("DEBUG")] trace helper that writes tagged messages to Debug.WriteLine.

Core Changes

  • TableView.cs – Major rework of drag-selection hit-testing so that pointer-down events on TableViewRow containers and blank list areas correctly initiate a selection rectangle, with full row/column range tracking.
  • TableViewCell.cs – Selection-related logic extracted out of the cell and centralised in TableView, reducing the cell to its display responsibilities.
  • TableViewRow.cs – Removed redundant pointer-event handling that is now owned by TableView.
  • TableViewCellSlot.cs – Minor adjustments to align with the new range types.
  • ItemIndexRangeExtensions.cs – Extended to support the new selection model.

Tests

  • DragSelectionRectangleTests.cs – New tests covering drag-selection rectangle geometry.
  • TableViewSelectionUnitTests.cs – Updated to cover selection scenarios that start outside individual cells.

Sample App

  • OverviewPage and ExampleModelColumnsHelper updated to demonstrate the improved drag-selection behaviour.

Checklist

  • New public types have full XML documentation
  • Nullable reference types respected throughout
  • Debug-only tracing via TableViewTrace (no production overhead)
  • Unit tests added / updated
  • Tested on WinUI 3
  • Tested on Uno Platform

w-ahmad and others added 2 commits July 29, 2026 20:33
CS1591 build error — publicly visible override was missing an
inheritdoc comment.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@w-ahmad
w-ahmad marked this pull request as ready for review July 29, 2026 22:25
@w-ahmad
w-ahmad requested a review from Copilot July 29, 2026 23:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances TableView’s drag-selection so selection gestures can start from row containers and empty list space (not only cells), while centralizing selection/drag logic into TableView and introducing coordinate-based range helpers to support rectangular cell selection.

Changes:

  • Introduces TableViewCellSlotRange (+ range extensions) and index-range helpers to model rectangular cell selections and perform range arithmetic.
  • Reworks pointer/drag-selection handling in TableView to support starting drags from rows and empty space, and refactors selection state updates.
  • Updates and adds tests to cover range-based selection APIs (SelectCellRange / DeselectCellRange) and drag-selection behaviors.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/TableViewSelectionUnitTests.cs Updates selection tests to use range-based APIs.
tests/DragSelectionRectangleTests.cs Adds tests for SelectCellRange / DeselectCellRange event behavior and selection results.
src/TableViewRow.cs Removes row-level pointer handling now owned by TableView.
src/TableViewCellSlotRange.cs Adds new public range type representing rectangular cell-slot selections.
src/TableViewCellSlot.cs Adds ToString() for improved diagnostics/logging.
src/TableViewCell.cs Removes selection/drag logic from cell and adjusts pointer/edit handling.
src/TableView.Properties.cs Migrates internal selection storage to HashSet<TableViewCellSlotRange>.
src/TableView.Events.cs Adds debug tracing for cell selection changed notifications.
src/TableView.cs Major drag-selection + hit-testing rework; adds range-based selection/deselection APIs and selection-state dispatching.
src/Helpers/TableViewTrace.cs Adds DEBUG-only tagged tracing helper.
src/Helpers/IndexRangeHelper.cs Adds helper to build contiguous ItemIndexRange sequences from indexes.
src/Extensions/TableViewCellSlotRangeExtensions.cs Adds range operations (contains/intersects/subtract/merge, slot enumeration, validity).
src/Extensions/ItemIndexRangeExtensions.cs Adds Contains/Subtract and refines IsValid signature for selection logic.

Comment thread src/TableViewCellSlotRange.cs
Comment thread src/TableView.cs Outdated
Comment thread src/TableView.cs
Comment thread src/TableView.cs
Comment thread src/TableView.cs
w-ahmad and others added 3 commits July 30, 2026 04:39
…logic

- Normalize FromCoordinates() to use Math.Min for firstRow/firstCol so
  ranges with start > end (e.g. reverse drag) compute correct bounds.
- Guard TableView_SelectionChanged against zero visible columns before
  creating a full-row cell slot range (avoids ArgumentOutOfRangeException).
- Fix SelectRowsInDragRect: remove invalid null-conditionals on the
  non-nullable ItemIndexRange parameter; use .Value on the nullable field.
- Fix DeselectCell to subtract only the 1x1 cell range from each
  containing selection range instead of removing the whole range.
- Fix EndDragSelection: clear _pointerCaptureElement after releasing
  captures and remove the dead _tableViewDragPointer cleanup block.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
ItemIndexRange is a class, not a struct, so .Value does not exist on a
nullable reference. Revert to calling Contains/Subtract directly on the
already-null-checked _lastDragSelectionRowRange.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (4)

src/TableView.cs:325

  • During row drag-selection, CurrentRowIndex (and SelectedIndex) are never updated when selection is driven by SelectRange/DeselectRange. Keyboard navigation uses CurrentRowIndex when LastSelectionUnit is Row, so after a drag-select the arrow keys can start navigating from a stale row index.
        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);

src/TableView.cs:1635

  • OnCellSelectionChanged sets _cellStateDispatchPending = true and then ignores the return value of DispatcherQueue.TryEnqueue. If enqueue fails (e.g., dispatcher shutting down), _cellStateDispatchPending stays true and pending row visual updates will never run again.
        if (!_cellStateDispatchPending)
        {
            _cellStateDispatchPending = true;
            DispatcherQueue.TryEnqueue(ApplyPendingCellStates);
        }

src/Extensions/ItemIndexRangeExtensions.cs:62

  • Subtract() returns an empty sequence when the ranges don’t overlap. For subtraction semantics, “no overlap” should normally return the original range unchanged; returning nothing can make callers accidentally drop the entire range.
        // No overlap.
        if (otherEnd < start || otherStart > end)
        {
            yield break;
        }

src/TableView.cs:235

  • Local variable names in OnAnyPointerPressed contain typos (isShiftkey, orignalSoruce). This makes the code harder to read and propagates the typo into subsequent checks.
        var ctrlKey = KeyboardHelper.IsCtrlKeyDown();
        var isShiftkey = KeyboardHelper.IsShiftKeyDown();
        var orignalSoruce = e.OriginalSource as FrameworkElement;

Comment thread src/TableView.cs
Comment thread src/TableView.cs Outdated
w-ahmad and others added 6 commits July 30, 2026 16:58
…er invalid item indexes

- Release pointer captures immediately if StartDragSelection returns
  early (SelectionMode not Multiple/Extended), preventing a stuck capture.
- Filter out Items.IndexOf() results of -1 in the Ctrl SelectionChanged
  path to avoid passing invalid row indexes to TableViewCellSlotRange.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@w-ahmad
w-ahmad marked this pull request as draft August 3, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants