Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion src/TableView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ private sealed class GroupHeaderRowItem
private bool _shouldThrowSelectionModeChangedException;
private bool _isUpdatingBaseItemsSource;
private bool _ensureColumns = true;
private TableViewRow? _editingHighlightRow;
private int _editingHighlightRowIndex = -1;
private readonly List<TableViewRow> _rows = [];
private readonly CollectionView _collectionView = [];
internal Canvas? _dragRectangleCanvas;
Expand Down Expand Up @@ -196,17 +198,44 @@ protected override void PrepareContainerForItemOverride(DependencyObject element
{
base.PrepareContainerForItemOverride(element, item);

// Reset editing highlight state on recycled containers to prevent
// stale _hasEditingHighlight from blocking EnsureAlternateColors.
if (element is TableViewRow { } recycledRow)
{
recycledRow.ApplyEditingHighlight(false);

if (_editingHighlightRow == recycledRow)
{
_editingHighlightRow = null;
}
}

DispatcherQueue.TryEnqueue(() =>
{
if (element is TableViewRow row)
{
row.EnsureCellsStyle(default, item);
row.ApplyCellsSelectionState();

// Reset current cell border on all cells in recycled containers
// to clear stale "Current" visual state from previous use.
foreach (var cell in row.Cells)
{
cell.ApplyCurrentCellState();
}

if (CurrentCellSlot.HasValue)
{
row.ApplyCurrentCellState(CurrentCellSlot.Value);
}

// Apply editing highlight when the editing row scrolls into view
var rowIndex = Items.IndexOf(item);
if (_editingHighlightRowIndex >= 0 && rowIndex == _editingHighlightRowIndex)
{
_editingHighlightRow = row;
row.ApplyEditingHighlight(true);
}
}
});
}
Expand Down Expand Up @@ -302,7 +331,7 @@ private async Task HandleNavigations(KeyRoutedEventArgs e, bool shiftKey, bool c

do
{
newSlot = GetNextSlot(newSlot, shiftKey, e.Key is VirtualKey.Enter);
newSlot = GetNextSlot(newSlot, shiftKey, e.Key is VirtualKey.Enter || (e.Key is VirtualKey.Tab && SelectionUnit is TableViewSelectionUnit.Row));

} while (isEditing && Columns[newSlot.Column].IsReadOnly);

Expand All @@ -314,6 +343,21 @@ private async Task HandleNavigations(KeyRoutedEventArgs e, bool shiftKey, bool c
{
SetIsEditing(false);
}
else if (SelectionUnit is TableViewSelectionUnit.Row or TableViewSelectionUnit.CellOrRow && newSlot.Row != currentCell.Slot.Row)
{
// Editing moved to a different row — move the highlight
_editingHighlightRow?.ApplyEditingHighlight(false);
_editingHighlightRowIndex = newSlot.Row;
if (ContainerFromIndex(newSlot.Row) is TableViewRow newRow)
{
_editingHighlightRow = newRow;
newRow.ApplyEditingHighlight(true);
}
else
{
_editingHighlightRow = null;
}
}
}

MakeSelection(newSlot, false);
Expand Down Expand Up @@ -2445,6 +2489,25 @@ internal void SetIsEditing(bool value)

IsEditing = value;
UpdateCornerButtonState();

if (value && SelectionUnit is TableViewSelectionUnit.Row or TableViewSelectionUnit.CellOrRow)
{
if (CurrentCellSlot.HasValue)
{
_editingHighlightRowIndex = CurrentCellSlot.Value.Row;
if (ContainerFromIndex(CurrentCellSlot.Value.Row) is TableViewRow row)
{
_editingHighlightRow = row;
row.ApplyEditingHighlight(true);
}
}
}
else if (!value)
{
_editingHighlightRow?.ApplyEditingHighlight(false);
_editingHighlightRow = null;
_editingHighlightRowIndex = -1;
}
}

/// <summary>
Expand Down
14 changes: 12 additions & 2 deletions src/TableViewCell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ protected override void OnPointerEntered(PointerRoutedEventArgs e)
if ((TableView?.SelectionMode is not ListViewSelectionMode.None
&& TableView?.SelectionUnit is not TableViewSelectionUnit.Row)
|| !TableView.IsReadOnly
|| (TableView?.SelectionUnit is TableViewSelectionUnit.Row && !IsReadOnly))
|| (TableView?.SelectionUnit is TableViewSelectionUnit.Row or TableViewSelectionUnit.CellOrRow && !IsReadOnly))
{
VisualStates.GoToState(this, false, VisualStates.StatePointerOver);
}
Expand All @@ -342,7 +342,7 @@ protected override void OnPointerExited(PointerRoutedEventArgs e)
if ((TableView?.SelectionMode is not ListViewSelectionMode.None
&& TableView?.SelectionUnit is not TableViewSelectionUnit.Row)
|| !TableView.IsReadOnly
|| (TableView?.SelectionUnit is TableViewSelectionUnit.Row && !IsReadOnly))
|| (TableView?.SelectionUnit is TableViewSelectionUnit.Row or TableViewSelectionUnit.CellOrRow && !IsReadOnly))
{
VisualStates.GoToState(this, false, VisualStates.StateNormal);
}
Expand Down Expand Up @@ -374,6 +374,16 @@ protected override async void OnTapped(TappedRoutedEventArgs e)
MakeSelection();
e.Handled = true;
}
else if (TableView?.SelectionUnit is TableViewSelectionUnit.CellOrRow
&& !IsReadOnly
&& TableView is not null
&& !TableView.IsEditing
&& Column?.UseSingleElement is not true)
{
// Second tap on an already-selected cell in CellOrRow mode — start editing
// (like File Explorer's tap-pause-tap to rename).
e.Handled = await BeginCellEditing(e);
}
}

/// <inheritdoc/>
Expand Down
64 changes: 62 additions & 2 deletions src/TableViewRow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public partial class TableViewRow : ListViewItem
private ListViewItemPresenter? _itemPresenter;
private Border? _selectionBackground;
private bool _ensureCells = true;
private bool _hasEditingHighlight;
private bool _isBeginningEdit;
private Brush? _cellPresenterBackground;
private Brush? _cellPresenterForeground;

Expand Down Expand Up @@ -251,7 +253,7 @@ protected override void OnPointerCaptureLost(PointerRoutedEventArgs e)
}

/// <inheritdoc/>
protected override void OnTapped(TappedRoutedEventArgs e)
protected override async void OnTapped(TappedRoutedEventArgs e)
{
if (TableView?.IsGroupHeaderItem(Content) is true)
{
Expand All @@ -266,6 +268,23 @@ protected override void OnTapped(TappedRoutedEventArgs e)
TableView.CurrentRowIndex = Index;
TableView.LastSelectionUnit = TableViewSelectionUnit.Row;
}

// When SelectionUnit is Row and the row is already selected, forward the
// tap to the target cell so editing can be initiated with a second tap
// (like File Explorer's tap-pause-tap to rename).
if (TableView?.SelectionUnit is TableViewSelectionUnit.Row
&& IsSelected
&& e.OriginalSource is DependencyObject source
&& source.FindAscendant<TableViewCell>() is { IsReadOnly: false } cell
&& !TableView.IsEditing
&& !_isBeginningEdit
&& cell.Column?.UseSingleElement is not true)
{
_isBeginningEdit = true;
TableView.MakeSelection(cell.Slot, false);
e.Handled = await cell.BeginCellEditing(e);
_isBeginningEdit = false;
}
}

/// <summary>
Expand Down Expand Up @@ -311,10 +330,13 @@ protected override async void OnDoubleTapped(DoubleTappedRoutedEventArgs e)
&& e.OriginalSource is DependencyObject source
&& source.FindAscendant<TableViewCell>() is { IsReadOnly: false } cell
&& !TableView.IsEditing
&& !_isBeginningEdit
&& cell.Column?.UseSingleElement is not true)
{
_isBeginningEdit = true;
TableView.MakeSelection(cell.Slot, false);
e.Handled = await cell.BeginCellEditing(e);
_isBeginningEdit = false;
return;
}

Expand Down Expand Up @@ -735,7 +757,7 @@ private async void EnsureSelectionIndicatorPosition(double detailsHeight, Border
/// </summary>
internal void EnsureAlternateColors()
{
if (TableView is null || RowPresenter is null) return;
if (TableView is null || RowPresenter is null || _hasEditingHighlight) return;

RowPresenter.Background =
Index % 2 == 1 && TableView.AlternateRowBackground is not null ? TableView.AlternateRowBackground : _cellPresenterBackground;
Expand All @@ -754,6 +776,44 @@ internal void UpdateSelectCheckMarkOpacity()
}
}

/// <summary>
/// Highlights or unhighlights the row to indicate that a cell is being edited.
/// </summary>
internal void ApplyEditingHighlight(bool isEditing)
{
_hasEditingHighlight = isEditing;

if (isEditing)
{
#if WINDOWS
if (RowPresenter is not null && _itemPresenter?.PointerOverBackground is { } pointerOverBrush)
{
RowPresenter.Background = pointerOverBrush;
}
#else
if (_selectionBackground is not null)
{
_selectionBackground.Opacity = 1;
}
#endif
}
else
{
#if WINDOWS
if (RowPresenter is not null)
{
RowPresenter.Background = _cellPresenterBackground;
}
#else
if (_selectionBackground is not null)
{
_selectionBackground.Opacity = IsSelected ? 1 : 0;
}
#endif
EnsureAlternateColors();
}
}

/// <summary>
/// Gets the height of the horizontal gridlines.
/// </summary>
Expand Down
Loading