diff --git a/src/Columns/TableViewBoundColumn.cs b/src/Columns/TableViewBoundColumn.cs index 39673298..274a7997 100644 --- a/src/Columns/TableViewBoundColumn.cs +++ b/src/Columns/TableViewBoundColumn.cs @@ -1,9 +1,5 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Data; -using System; -using System.Linq.Expressions; -using System.Reflection; -using WinUI.TableView.Extensions; namespace WinUI.TableView; @@ -13,31 +9,6 @@ namespace WinUI.TableView; public abstract class TableViewBoundColumn : TableViewColumn { private Binding _binding = new(); - private Func? _funcCompiledPropertyPath; - - /// - public override object? GetCellContent(object? dataItem) - { - if (dataItem is null) - return null; - - if (_funcCompiledPropertyPath is null && !string.IsNullOrWhiteSpace(PropertyPath)) - _funcCompiledPropertyPath = dataItem.GetFuncCompiledPropertyPath(PropertyPath!); - - if (_funcCompiledPropertyPath is not null) - dataItem = _funcCompiledPropertyPath(dataItem); - - if (Binding?.Converter is not null) - { - dataItem = Binding.Converter.Convert( - dataItem, - typeof(object), - Binding.ConverterParameter, - Binding.ConverterLanguage); - } - - return dataItem; - } /// /// Gets the property path for the binding. @@ -70,13 +41,13 @@ public virtual Binding Binding } /// - /// Gets or sets the data binding used to retrieve cell content when copying to the clipboard. - /// If no explicit clipboard binding is set, the column's is returned as a fallback. + /// Gets or sets the optional data binding used to perform operations on cell content, for example sorting, filtering and exporting. + /// If no explicit operation binding is set, the column's is returned as a fallback. /// - public override Binding? ClipboardContentBinding + public override Binding? OperationContentBinding { - get => base.ClipboardContentBinding ?? Binding; - set => base.ClipboardContentBinding = value; + get => base.OperationContentBinding ?? Binding; + set => base.OperationContentBinding = value; } /// diff --git a/src/Columns/TableViewColumn.cs b/src/Columns/TableViewColumn.cs index 48ae832a..b145f637 100644 --- a/src/Columns/TableViewColumn.cs +++ b/src/Columns/TableViewColumn.cs @@ -20,6 +20,8 @@ public abstract partial class TableViewColumn : DependencyObject private bool _isFiltered; private bool _isFrozen; private Func? _funcCompiledPropertyPath; + private Func? _funcCompiledClipboardPropertyPath; + private Binding? _clipboardContentBinding; /// /// Initializes a new instance of the class with default conditional cell styles. @@ -107,7 +109,25 @@ internal void SetOwningTableView(TableView tableView) /// The content of the cell. public virtual object? GetCellContent(object? dataItem) { - return default; + if (dataItem is null) + return null; + + if (_funcCompiledPropertyPath is null && !string.IsNullOrWhiteSpace(OperationContentBindingPropertyPath)) + _funcCompiledPropertyPath = dataItem.GetFuncCompiledPropertyPath(OperationContentBindingPropertyPath!); + + if (_funcCompiledPropertyPath is not null) + dataItem = _funcCompiledPropertyPath(dataItem); + + if (OperationContentBinding?.Converter is not null) + { + dataItem = OperationContentBinding.Converter.Convert( + dataItem, + typeof(object), + OperationContentBinding.ConverterParameter, + OperationContentBinding.ConverterLanguage); + } + + return dataItem; } /// @@ -120,11 +140,11 @@ internal void SetOwningTableView(TableView tableView) if (dataItem is null) return null; - if (_funcCompiledPropertyPath is null && !string.IsNullOrWhiteSpace(ClipboardContentBindingPropertyPath)) - _funcCompiledPropertyPath = dataItem.GetFuncCompiledPropertyPath(ClipboardContentBindingPropertyPath!); + if (_funcCompiledClipboardPropertyPath is null && !string.IsNullOrWhiteSpace(ClipboardContentBindingPropertyPath)) + _funcCompiledClipboardPropertyPath = dataItem.GetFuncCompiledPropertyPath(ClipboardContentBindingPropertyPath!); - if (_funcCompiledPropertyPath is not null) - dataItem = _funcCompiledPropertyPath(dataItem); + if (_funcCompiledClipboardPropertyPath is not null) + dataItem = _funcCompiledClipboardPropertyPath(dataItem); if (ClipboardContentBinding?.Converter is not null) { @@ -365,10 +385,25 @@ internal set } } + /// + /// Gets or sets the optional data binding used to perform operations on cell content, for example sorting, filtering and exporting. + /// + public virtual Binding? OperationContentBinding { get; set; } + + /// + /// Gets the property path for the . + /// + internal string? OperationContentBindingPropertyPath => OperationContentBinding?.Path?.Path; + /// /// Gets or sets the data binding used to retrieve cell content when copying to the clipboard. + /// If no explicit clipboard binding is set, the column's is returned as a fallback. /// - public virtual Binding? ClipboardContentBinding { get; set; } + public Binding? ClipboardContentBinding + { + get => _clipboardContentBinding ?? OperationContentBinding; + set => _clipboardContentBinding = value; + } /// /// Gets the property path for the . diff --git a/src/Columns/TableViewTemplateColumn.cs b/src/Columns/TableViewTemplateColumn.cs index da5d2dd1..044fc192 100644 --- a/src/Columns/TableViewTemplateColumn.cs +++ b/src/Columns/TableViewTemplateColumn.cs @@ -1,5 +1,6 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Markup; namespace WinUI.TableView; @@ -9,6 +10,7 @@ namespace WinUI.TableView; #if WINDOWS [WinRT.GeneratedBindableCustomProperty] #endif +[ContentProperty(Name = nameof(CellTemplate))] public partial class TableViewTemplateColumn : TableViewColumn { /// diff --git a/src/TableView.cs b/src/TableView.cs index e21a6d3e..dcfefa5f 100644 --- a/src/TableView.cs +++ b/src/TableView.cs @@ -133,7 +133,7 @@ protected override DependencyObject GetContainerForItemOverride() } /// - protected override async void OnKeyDown(KeyRoutedEventArgs e) + protected override void OnKeyDown(KeyRoutedEventArgs e) { var shiftKey = KeyboardHelper.IsShiftKeyDown(); var ctrlKey = KeyboardHelper.IsCtrlKeyDown(); @@ -144,19 +144,19 @@ protected override async void OnKeyDown(KeyRoutedEventArgs e) return; } - await HandleNavigations(e, shiftKey, ctrlKey); + HandleNavigations(e, shiftKey, ctrlKey); } /// /// Handles navigation keys. /// - private async Task HandleNavigations(KeyRoutedEventArgs e, bool shiftKey, bool ctrlKey) + private void HandleNavigations(KeyRoutedEventArgs e, bool shiftKey, bool ctrlKey) { var currentCell = CurrentCellSlot.HasValue ? GetCellFromSlot(CurrentCellSlot.Value) : default; if (e.Key is VirtualKey.F2 && currentCell is { IsReadOnly: false } && !IsEditing) { - e.Handled = await currentCell.BeginCellEditing(e); + e.Handled = currentCell.BeginCellEditing(e); } else if (e.Key is VirtualKey.Escape && currentCell is not null && IsEditing) { @@ -192,7 +192,7 @@ private async Task HandleNavigations(KeyRoutedEventArgs e, bool shiftKey, bool c { if (!EndCellEditing(TableViewEditAction.Commit, currentCell)) return; - if (CurrentCellSlot == newSlot || GetCellFromSlot(newSlot) is not { } nextCell || !await nextCell.BeginCellEditing(e)) + if (CurrentCellSlot == newSlot || GetCellFromSlot(newSlot) is not { } nextCell || !nextCell.BeginCellEditing(e)) { SetIsEditing(false); } @@ -230,6 +230,42 @@ private async Task HandleNavigations(KeyRoutedEventArgs e, bool shiftKey, bool c MakeSelection(newSlot, shiftKey); e.Handled = true; } + else if (e.Key is VirtualKey.Home or VirtualKey.End) + { + var row = ctrlKey ? (e.Key == VirtualKey.Home ? 0 : _collectionView.Count - 1) : CurrentCellSlot?.Row; + var column = e.Key == VirtualKey.Home ? 0 : Columns.VisibleColumns.Count - 1; + + var newSlot = new TableViewCellSlot(row ?? -1, column); + MakeSelection(newSlot, shiftKey); + e.Handled = true; + } + else if (e.Key is VirtualKey.PageDown or VirtualKey.PageUp) + { + var pageSize = CalculateAvailablePageSize(); + + var row = (LastSelectionUnit is TableViewSelectionUnit.Row ? CurrentRowIndex : CurrentCellSlot?.Row) ?? -1; + var column = CurrentCellSlot?.Column ?? -1; + + var numRows = CollectionView.Count; + var nextRow = e.Key == VirtualKey.PageDown + ? Math.Min(numRows - 1, row + pageSize) + : Math.Max(0, row - pageSize); + + var newSlot = new TableViewCellSlot(nextRow, column); + MakeSelection(newSlot, shiftKey); + e.Handled = true; + } + } + + /// + /// Calculates how many rows should be able to fit within the actual height of the table without scrolling. + /// + private int CalculateAvailablePageSize() + { + var rowHeight = RowHeight is not double.NaN ? RowHeight : RowMinHeight; + var headerHeight = HeaderRowHeight is not double.NaN ? HeaderRowHeight : HeaderRowMinHeight; + var availableHeight = ActualHeight - headerHeight; + return (int)Math.Floor(availableHeight / rowHeight); } internal bool EndCellEditing(TableViewEditAction editAction, TableViewCell cell) @@ -283,7 +319,7 @@ protected async override void OnApplyTemplate() _scrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer; _headerRowDefinition = GetTemplateChild("HeaderRowDefinition") as RowDefinition; if (_scrollViewer is not null) _scrollViewer.Loaded += OnScrollViewerLoaded; - + if (IsLoaded) { while (ItemsPanelRoot is null) await Task.Yield(); @@ -320,12 +356,12 @@ private void OnScrollViewerLoaded(object sender, RoutedEventArgs e) Source = this }); } - + /// /// Handles the Loaded event of the TableView control. /// private void OnLoaded(object sender, RoutedEventArgs e) - { + { EnsureAutoColumns(); } @@ -536,7 +572,7 @@ private string GetCellsContent(IEnumerable slots, bool includ for (var col = minColumn; col <= maxColumn; col++) { - if (Columns.VisibleColumns[col] is not TableViewBoundColumn column || + if (Columns.VisibleColumns[col] is not TableViewColumn column || !slots.Contains(new TableViewCellSlot(row, col))) { stringBuilder.Append(separator); diff --git a/src/TableViewCell.cs b/src/TableViewCell.cs index 758c3b14..af37ac0e 100644 --- a/src/TableViewCell.cs +++ b/src/TableViewCell.cs @@ -210,18 +210,14 @@ protected override void OnPointerExited(PointerRoutedEventArgs e) } /// - protected override async void OnTapped(TappedRoutedEventArgs e) + protected override void OnTapped(TappedRoutedEventArgs e) { base.OnTapped(e); - if ((TableView?.IsEditing ?? false) && - TableView.CurrentCellSlot != Slot && - TableView.CurrentCellSlot.HasValue && - TableView.GetCellFromSlot(TableView.CurrentCellSlot.Value) is { } currentCell) + if (!TryEndCurrentCellEdit()) { - e.Handled = !TableView.EndCellEditing(TableViewEditAction.Commit, currentCell); - - if (e.Handled) return; + e.Handled = true; + return; } if (TableView?.CurrentCellSlot != Slot || TableView?.LastSelectionUnit is TableViewSelectionUnit.Row) @@ -236,9 +232,15 @@ protected override void OnPointerPressed(PointerRoutedEventArgs e) { base.OnPointerPressed(e); + if (!TryEndCurrentCellEdit()) + { + e.Handled = true; + return; + } + if (!KeyboardHelper.IsShiftKeyDown() && TableView is not null) { - TableView.SelectionStartCellSlot = TableView.SelectionUnit is not TableViewSelectionUnit.Row || !IsReadOnly ? Slot : default; ; + TableView.SelectionStartCellSlot = TableView.SelectionUnit is not TableViewSelectionUnit.Row || !IsReadOnly ? Slot : default; TableView.SelectionStartRowIndex = Index; CapturePointer(e.Pointer); } @@ -278,6 +280,26 @@ protected override void OnManipulationDelta(ManipulationDeltaRoutedEventArgs e) } } + /// + /// Tries to end the current edit operation, if any. + /// + /// True if an edit operation was successfully ended, or there is no edit operation. + /// False if the current edit operation can not be ended. + private bool TryEndCurrentCellEdit() + { + if ((TableView?.IsEditing ?? false) && + TableView.CurrentCellSlot != Slot && + TableView.CurrentCellSlot.HasValue && + TableView.GetCellFromSlot(TableView.CurrentCellSlot.Value) is { } currentCell) + { + if (!TableView.EndCellEditing(TableViewEditAction.Commit, currentCell)) return false; + + TableView.SetIsEditing(false); + } + + return true; + } + /// /// Gets the height of the horizontal gridlines/>. /// @@ -309,7 +331,7 @@ private double GetHorizontalGridlineHeight() } /// - protected override async void OnDoubleTapped(DoubleTappedRoutedEventArgs e) + protected override void OnDoubleTapped(DoubleTappedRoutedEventArgs e) { var eventArgs = new TableViewCellDoubleTappedEventArgs(Slot, this, Row?.Content); TableView?.OnCellDoubleTapped(eventArgs); @@ -321,7 +343,7 @@ protected override async void OnDoubleTapped(DoubleTappedRoutedEventArgs e) if (!IsReadOnly && TableView is not null && !TableView.IsEditing && !Column?.UseSingleElement is true) { - e.Handled = await BeginCellEditing(e); + e.Handled = BeginCellEditing(e); } else { @@ -370,7 +392,7 @@ private void MakeSelection() /// The event data associated with the editing request. Cannot be null. /// A task that represents the asynchronous operation. The task result is if cell editing was /// successfully started; otherwise, if the operation was canceled. - internal async Task BeginCellEditing(RoutedEventArgs editingArgs) + internal bool BeginCellEditing(RoutedEventArgs editingArgs) { var args = new TableViewBeginningEditEventArgs(this, Row?.Content, Column!, editingArgs); TableView?.OnBeginningEdit(args);