From bf6208028fabf11413a87969d3acb3c8431b904f Mon Sep 17 00:00:00 2001 From: Waheed Ahmad Date: Tue, 2 Jun 2026 18:31:20 +0500 Subject: [PATCH 1/6] Refactor to use C# 14 'field' keyword and cleanups Refactored property backing fields to use C# 14's field keyword, reducing boilerplate and improving clarity. Updated null checks and event handler logic with null-conditional operators. Made several properties nullable where appropriate. Exposed TableView.HeaderRow as a property and updated all references. Increased language version to C# 14 in the project file. Streamlined logic, improved code style, and fixed minor bugs. --- src/ColumnFilterHandler.cs | 5 +- src/Columns/TableViewBoundColumn.cs | 10 +-- src/Columns/TableViewColumn.cs | 54 +++++++------- src/Columns/TableViewComboBoxColumn.cs | 25 ++----- .../TableViewFilterItemsControl.xaml.cs | 22 +++--- src/ItemsSource/CollectionView.Properties.cs | 34 ++++----- src/ItemsSource/CollectionView.cs | 26 +++---- src/TableView.Properties.cs | 31 ++++---- src/TableView.cs | 42 +++++------ src/TableViewCell.cs | 7 +- src/TableViewColumnHeader.cs | 64 +++++----------- src/TableViewFilterItem.cs | 6 +- src/TableViewHeaderRow.OptionComamnds.cs | 11 +-- src/TableViewHeaderRow.cs | 74 +++---------------- src/TableViewRow.cs | 10 +-- src/TableViewRowPresenter.cs | 33 +++------ src/WinUI.TableView.csproj | 2 +- 17 files changed, 167 insertions(+), 289 deletions(-) diff --git a/src/ColumnFilterHandler.cs b/src/ColumnFilterHandler.cs index 8a1b67f3..abfa32a9 100644 --- a/src/ColumnFilterHandler.cs +++ b/src/ColumnFilterHandler.cs @@ -144,10 +144,7 @@ public virtual void ClearFilter(TableViewColumn? column) foreach (var col in _tableView.Columns) { - if (col is not null) - { - col.IsFiltered = false; - } + col?.IsFiltered = false; } } } diff --git a/src/Columns/TableViewBoundColumn.cs b/src/Columns/TableViewBoundColumn.cs index 274a7997..14a3020b 100644 --- a/src/Columns/TableViewBoundColumn.cs +++ b/src/Columns/TableViewBoundColumn.cs @@ -8,7 +8,7 @@ namespace WinUI.TableView; /// public abstract class TableViewBoundColumn : TableViewColumn { - private Binding _binding = new(); + // private Binding _binding;//= new(); /// /// Gets the property path for the binding. @@ -20,10 +20,10 @@ public abstract class TableViewBoundColumn : TableViewColumn /// public virtual Binding Binding { - get => _binding; + get; set { - if (_binding != value) + if (field != value) { if (value is not null) { @@ -35,10 +35,10 @@ public virtual Binding Binding } } - _binding = value!; + field = value!; } } - } + } = new(); /// /// Gets or sets the optional data binding used to perform operations on cell content, for example sorting, filtering and exporting. diff --git a/src/Columns/TableViewColumn.cs b/src/Columns/TableViewColumn.cs index b145f637..494aa2b6 100644 --- a/src/Columns/TableViewColumn.cs +++ b/src/Columns/TableViewColumn.cs @@ -14,14 +14,8 @@ namespace WinUI.TableView; [StyleTypedProperty(Property = nameof(CellStyle), StyleTargetType = typeof(TableViewCell))] public abstract partial class TableViewColumn : DependencyObject { - private TableViewColumnHeader? _headerControl; - private double _desiredWidth; - private SD? _sortDirection; - 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. @@ -244,11 +238,14 @@ public Style CellStyle /// public TableViewColumnHeader? HeaderControl { - get => _headerControl; + get; internal set { - _headerControl = value; - EnsureHeaderStyle(); + if (field != value) + { + field = value; + EnsureHeaderStyle(); + } } } @@ -322,12 +319,12 @@ public IList ConditionalCellStyles /// internal double DesiredWidth { - get => _desiredWidth; + get; set { - if (_desiredWidth != value) + if (field != value) { - _desiredWidth = value; + field = value; OwningCollection?.HandleColumnPropertyChanged(this, nameof(DesiredWidth)); } } @@ -348,11 +345,14 @@ internal double DesiredWidth /// public SD? SortDirection { - get => _sortDirection; + get; set { - _sortDirection = value; - OnSortDirectionChanged(); + if (field != value) + { + field = value; + OnSortDirectionChanged(); + } } } @@ -361,11 +361,14 @@ public SD? SortDirection /// public bool IsFiltered { - get => _isFiltered; + get; set { - _isFiltered = value; - OnIsFilteredChanged(); + if (field != value) + { + field = value; + OnIsFilteredChanged(); + } } } @@ -374,12 +377,12 @@ public bool IsFiltered /// public bool IsFrozen { - get => _isFrozen; + get; internal set { - if (_isFrozen != value) + if (field != value) { - _isFrozen = value; + field = value; OwningCollection?.HandleColumnPropertyChanged(this, nameof(IsFrozen)); } } @@ -401,8 +404,8 @@ internal set /// public Binding? ClipboardContentBinding { - get => _clipboardContentBinding ?? OperationContentBinding; - set => _clipboardContentBinding = value; + get => field ?? OperationContentBinding; + set; } /// @@ -436,10 +439,7 @@ private void OnIsFilteredChanged() /// internal void EnsureHeaderStyle() { - if (_headerControl is not null) - { - _headerControl.Style = HeaderStyle ?? TableView?.ColumnHeaderStyle; - } + HeaderControl?.Style = HeaderStyle ?? TableView?.ColumnHeaderStyle; } /// diff --git a/src/Columns/TableViewComboBoxColumn.cs b/src/Columns/TableViewComboBoxColumn.cs index dcd4472e..18e073dc 100644 --- a/src/Columns/TableViewComboBoxColumn.cs +++ b/src/Columns/TableViewComboBoxColumn.cs @@ -15,9 +15,6 @@ namespace WinUI.TableView; #endif public partial class TableViewComboBoxColumn : TableViewBoundColumn { - private Binding? _textBinding; - private Binding? _selectedValueBinding; - /// /// Generates a TextBlock element for the cell. /// @@ -135,32 +132,26 @@ public bool IsEditable /// /// Gets or sets the binding for the text property of the ComboBox. /// - public virtual Binding TextBinding + public virtual Binding? TextBinding { - get => _textBinding!; + get; set { - _textBinding = value; - if (_textBinding is not null) - { - _textBinding.Mode = BindingMode.TwoWay; - } + field = value; + field?.Mode = BindingMode.TwoWay; } } /// /// Gets or sets the binding for the selected value property of the ComboBox. /// - public virtual Binding SelectedValueBinding + public virtual Binding? SelectedValueBinding { - get => _selectedValueBinding!; + get; set { - _selectedValueBinding = value; - if (_selectedValueBinding is not null) - { - _selectedValueBinding.Mode = BindingMode.TwoWay; - } + field = value; + field?.Mode = BindingMode.TwoWay; } } diff --git a/src/Controls/TableViewFilterItemsControl.xaml.cs b/src/Controls/TableViewFilterItemsControl.xaml.cs index 834af92a..d70936a4 100644 --- a/src/Controls/TableViewFilterItemsControl.xaml.cs +++ b/src/Controls/TableViewFilterItemsControl.xaml.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Windows.System; @@ -16,7 +17,6 @@ namespace WinUI.TableView.Controls; public partial class TableViewFilterItemsControl : UserControl { private bool _canSetState = true; - private ICollection? _filterItems; /// /// Initializes a new instance of the class. @@ -93,8 +93,8 @@ internal void SetSelectAllCheckBoxState() } - selectAllCheckBox.IsChecked = _filterItems?.All(x => x.IsSelected) ?? false ? true - : _filterItems?.All(x => !x.IsSelected) ?? false ? false + selectAllCheckBox.IsChecked = FilterItems?.All(x => x.IsSelected) ?? false ? true + : FilterItems?.All(x => !x.IsSelected) ?? false ? false : null; } @@ -119,9 +119,9 @@ internal void SetFilterItemsState(bool isSelected) /// private void AttachPropertyChangedHandlers() { - if (_filterItems?.Count > 0) + if (FilterItems?.Count > 0) { - foreach (var item in _filterItems) + foreach (var item in FilterItems) { item.PropertyChanged += OnFilterItemPropertyChanged; } @@ -133,9 +133,9 @@ private void AttachPropertyChangedHandlers() /// private void DetachPropertyChangedHandlers() { - if (_filterItems?.Count > 0) + if (FilterItems?.Count > 0) { - foreach (var item in _filterItems) + foreach (var item in FilterItems) { item.PropertyChanged -= OnFilterItemPropertyChanged; } @@ -160,14 +160,14 @@ private void OnFilterItemPropertyChanged(object? sender, PropertyChangedEventArg /// internal ICollection? FilterItems { - get => _filterItems; + get; set { - if (_filterItems == value) return; + if (field == value) return; DetachPropertyChangedHandlers(); - _filterItems = value; - filterItemsList.ItemsSource = _filterItems; + field = value; + filterItemsList.ItemsSource = field; AttachPropertyChangedHandlers(); SetSelectAllCheckBoxState(); } diff --git a/src/ItemsSource/CollectionView.Properties.cs b/src/ItemsSource/CollectionView.Properties.cs index 6c022501..ff75986f 100644 --- a/src/ItemsSource/CollectionView.Properties.cs +++ b/src/ItemsSource/CollectionView.Properties.cs @@ -13,25 +13,25 @@ partial class CollectionView /// public IEnumerable Source { - get => _source; + get; set { - if (_source == value) return; + if (field == value) return; - DetachCollectionChangedHandlers(_source); - DetachPropertyChangedHandlers(_source); + DetachCollectionChangedHandlers(field); + DetachPropertyChangedHandlers(field); - _source = value; + field = value; - AttachCollectionChangedHandlers(_source); - AttachPropertyChangedHandlers(_source); + AttachCollectionChangedHandlers(field); + AttachPropertyChangedHandlers(field); - CreateItemsCopy(_source); + CreateItemsCopy(field); HandleSourceChanged(); OnPropertyChanged(); } - } + } = new List(); /// /// Gets a value indicating whether this CollectionView can filter its items. @@ -81,7 +81,7 @@ public object? CurrentItem /// /// Gets a value indicating whether there are more items to load. /// - public bool HasMoreItems => (_source as ISupportIncrementalLoading)?.HasMoreItems ?? false; + public bool HasMoreItems => (Source as ISupportIncrementalLoading)?.HasMoreItems ?? false; /// /// Gets a value indicating whether the current item is after the last item in the view. @@ -101,23 +101,23 @@ public object? CurrentItem /// /// Gets a value indicating whether the collection is read-only. /// - public bool IsReadOnly => _source == null || _source.IsReadOnly(); + public bool IsReadOnly => Source == null || Source.IsReadOnly(); /// /// Gets or sets a value indicating whether live shaping is enabled. /// public bool AllowLiveShaping { - get => _allowLiveShaping; + get; set { - if (_allowLiveShaping == value) return; + if (field == value) return; - _allowLiveShaping = value; - if (_allowLiveShaping) - AttachPropertyChangedHandlers(_source); + field = value; + if (field) + AttachPropertyChangedHandlers(Source); else - DetachPropertyChangedHandlers(_source); + DetachPropertyChangedHandlers(Source); } } } diff --git a/src/ItemsSource/CollectionView.cs b/src/ItemsSource/CollectionView.cs index 18e42342..96106468 100644 --- a/src/ItemsSource/CollectionView.cs +++ b/src/ItemsSource/CollectionView.cs @@ -17,9 +17,7 @@ namespace WinUI.TableView; /// internal partial class CollectionView : ICollectionView, ISupportIncrementalLoading, INotifyPropertyChanged, IComparer { - private IEnumerable _source = new List(); private object[] _itemsCopy = []; // In case the source is ICollection, keep a copy of the items to keep track of removed items. - private bool _allowLiveShaping; private readonly List _view = []; private readonly ObservableCollection _filterDescriptions = []; private readonly ObservableCollection _sortDescriptions = []; @@ -171,12 +169,12 @@ private void OnSourceVectorChanged(IObservableVector sender, IVectorChan } DetachPropertyChangedHandlers(_itemsCopy); - AttachPropertyChangedHandlers(_source); + AttachPropertyChangedHandlers(Source); break; } - CreateItemsCopy(_source); + CreateItemsCopy(Source); } /// @@ -237,7 +235,7 @@ private void OnSourceCollectionChanged(object? arg1, NotifyCollectionChangedEven } DetachPropertyChangedHandlers(e.OldItems); - AttachPropertyChangedHandlers(_source); + AttachPropertyChangedHandlers(Source); break; } @@ -266,7 +264,7 @@ private void OnItemPropertyChanged(object? item, PropertyChangedEventArgs e) } else if (viewIndex == -1 && filterResult) { - var index = _source.IndexOf(item); + var index = Source.IndexOf(item); HandleItemAdded(index, item); } } @@ -328,7 +326,7 @@ private void HandleSourceChanged() } else { - _view.AddRange(_source.OfType()); + _view.AddRange(Source.OfType()); } if (SortDescriptions.Count > 0) @@ -362,7 +360,7 @@ private void HandleFilterChanged() var viewHash = new HashSet(_view); var viewIndex = 0; var i = 0; - foreach (var item in _source) + foreach (var item in Source) { if (viewHash.Contains(item)) { @@ -418,7 +416,7 @@ private bool HandleItemAdded(int newStartingIndex, object? newItem, int? viewInd } else if (FilterDescriptions.Any()) { - if (_source == null) + if (Source == null) { HandleSourceChanged(); return false; @@ -506,7 +504,7 @@ public void Add(object item) { if (IsReadOnly) throw new NotSupportedException("Collection is read-only."); - _source.Add(item); + Source.Add(item); } /// @@ -516,7 +514,7 @@ public void Clear() { if (IsReadOnly) throw new NotSupportedException("Collection is read-only."); - _source.Clear(); + Source.Clear(); } /// @@ -558,7 +556,7 @@ public void Insert(int index, object item) { if (IsReadOnly) throw new NotSupportedException("Collection is read-only."); - _source.Insert(index, item); + Source.Insert(index, item); } /// @@ -626,7 +624,7 @@ public bool Remove(object? item) { if (IsReadOnly) throw new NotSupportedException("Collection is read-only."); - _source.Remove(item); + Source.Remove(item); return true; } @@ -661,7 +659,7 @@ IEnumerator IEnumerable.GetEnumerator() /// An asynchronous operation that returns the result of the load operation. public IAsyncOperation? LoadMoreItemsAsync(uint count) { - return (_source as ISupportIncrementalLoading)?.LoadMoreItemsAsync(count); + return (Source as ISupportIncrementalLoading)?.LoadMoreItemsAsync(count); } /// diff --git a/src/TableView.Properties.cs b/src/TableView.Properties.cs index e0f762a8..536b15d1 100644 --- a/src/TableView.Properties.cs +++ b/src/TableView.Properties.cs @@ -366,6 +366,11 @@ public bool ShowFilterItemsCount /// internal ConditionalWeakTable> DetailsPaneStates { get; } = []; + /// + /// Gets or sets the header row of the TableView. + /// + internal TableViewHeaderRow? HeaderRow { get; set; } + /// /// Gets or sets the filter handler for the TableView. /// @@ -719,9 +724,9 @@ public DataTemplate? RowHeaderTemplate /// /// Gets or sets the data template selector for the row header. /// - public DataTemplateSelector RowHeaderTemplateSelector + public DataTemplateSelector? RowHeaderTemplateSelector { - get => (DataTemplateSelector)GetValue(RowHeaderTemplateSelectorProperty); + get => (DataTemplateSelector?)GetValue(RowHeaderTemplateSelectorProperty); set => SetValue(RowHeaderTemplateSelectorProperty, value); } @@ -755,9 +760,9 @@ public DataTemplate? RowDetailsTemplate /// /// Gets or sets the data template selector for the row details. /// - public DataTemplateSelector RowDetailsTemplateSelector + public DataTemplateSelector? RowDetailsTemplateSelector { - get => (DataTemplateSelector)GetValue(RowDetailsTemplateSelectorProperty); + get => (DataTemplateSelector?)GetValue(RowDetailsTemplateSelectorProperty); set => SetValue(RowDetailsTemplateSelectorProperty, value); } @@ -834,7 +839,7 @@ private static void OnShowExportOptionsChanged(DependencyObject d, DependencyPro { if (d is TableView tableView) { - tableView._headerRow?.SetExportOptionsVisibility(); + tableView.HeaderRow?.SetExportOptionsVisibility(); } } @@ -890,9 +895,9 @@ private static void OnIsReadOnlyChanged(DependencyObject d, DependencyPropertyCh /// private static void OnCanFilterColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is TableView tableView && tableView._headerRow is not null) + if (d is TableView tableView && tableView.HeaderRow is not null) { - foreach (var header in tableView._headerRow.Headers) + foreach (var header in tableView.HeaderRow.Headers) { header.SetFilterButtonVisibility(); } @@ -904,9 +909,9 @@ private static void OnCanFilterColumnsChanged(DependencyObject d, DependencyProp /// private static void OnMinColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is TableView table && table._headerRow is not null) + if (d is TableView table && table.HeaderRow is not null) { - table._headerRow.CalculateHeaderWidths(); + table.HeaderRow.CalculateHeaderWidths(); } } @@ -915,9 +920,9 @@ private static void OnMinColumnWidthChanged(DependencyObject d, DependencyProper /// private static void OnMaxColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is TableView table && table._headerRow is not null) + if (d is TableView table && table.HeaderRow is not null) { - table._headerRow.CalculateHeaderWidths(); + table.HeaderRow.CalculateHeaderWidths(); } } @@ -1000,9 +1005,9 @@ private static async void OnCurrentCellSlotChanged(DependencyObject d, Dependenc /// private static void OnHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is TableView { _headerRow: { } } tableView) + if (d is TableView { HeaderRow: { } } tableView) { - tableView._headerRow.InvalidateArrange(); + tableView.HeaderRow.InvalidateArrange(); foreach (var row in tableView._rows) { diff --git a/src/TableView.cs b/src/TableView.cs index a640e402..3168bff7 100644 --- a/src/TableView.cs +++ b/src/TableView.cs @@ -31,7 +31,6 @@ namespace WinUI.TableView; [StyleTypedProperty(Property = nameof(CellStyle), StyleTargetType = typeof(TableViewCell))] public partial class TableView : ListView { - private TableViewHeaderRow? _headerRow; private ScrollViewer? _scrollViewer; private RowDefinition? _headerRowDefinition; private bool _shouldThrowSelectionModeChangedException; @@ -335,10 +334,10 @@ protected async override void OnApplyTemplate() { base.OnApplyTemplate(); - _headerRow = GetTemplateChild("HeaderRow") as TableViewHeaderRow; + HeaderRow = GetTemplateChild("HeaderRow") as TableViewHeaderRow; _scrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer; _headerRowDefinition = GetTemplateChild("HeaderRowDefinition") as RowDefinition; - if (_scrollViewer is not null) _scrollViewer.Loaded += OnScrollViewerLoaded; + _scrollViewer?.Loaded += OnScrollViewerLoaded; if (IsLoaded) { @@ -359,15 +358,9 @@ private void OnScrollViewerLoaded(object sender, RoutedEventArgs e) var xScrollBar = _scrollViewer?.FindDescendant(sb => sb.Name is "HorizontalScrollBar2"); var yScrollBar = _scrollViewer?.FindDescendant(sb => sb.Name is "VerticalScrollBar"); - if (scrollPresenter is not null) - { - scrollPresenter.PointerWheelChanged += OnScrollContentPresenterPointerWheelChanged; - } + scrollPresenter?.PointerWheelChanged += OnScrollContentPresenterPointerWheelChanged; - if (yScrollBar is not null) - { - yScrollBar.ValueChanged += (_, _) => SetValue(VerticalOffsetProperty, yScrollBar.Value); - } + yScrollBar?.ValueChanged += (_, _) => SetValue(VerticalOffsetProperty, yScrollBar.Value); xScrollBar?.SetBinding(RangeBase.ValueProperty, new Binding { @@ -507,8 +500,8 @@ internal void CopyToClipboardInternal(bool includeHeaders) { // Skip TableView copy logic when a cell editor already handles Ctrl+C. // TextBox, PasswordBox, and RichEditBox all implement their own copy behavior. - var focused = FocusManager.GetFocusedElement() as FrameworkElement; - if (focused is TextBox || focused is PasswordBox || focused is RichEditBox) + var focused = FocusManager.GetFocusedElement(XamlRoot!) as FrameworkElement; + if (focused is TextBox or PasswordBox or RichEditBox) { return; } @@ -520,7 +513,7 @@ internal void CopyToClipboardInternal(bool includeHeaders) { return; } - + var content = GetSelectedClipboardContent(includeHeaders); if (string.IsNullOrWhiteSpace(content)) @@ -533,7 +526,7 @@ internal void CopyToClipboardInternal(bool includeHeaders) { var package = new DataPackage(); package.SetText(content); - + Clipboard.SetContent(package); } catch (Exception ex) @@ -898,7 +891,11 @@ internal async void ExportAllToCSV() /// /// Gets a storage file for saving the CSV. /// - private async Task GetStorageFile() + private +#if !WINDOWS + static +#endif + async Task GetStorageFile() { var savePicker = new FileSavePicker(); savePicker.FileTypeChoices.Add("CSV (Comma delimited)", [".csv"]); @@ -938,10 +935,7 @@ public void ClearAllSorting() foreach (var column in Columns.Where(c => c.SortDirection is not null)) { - if (column is not null) - { - column.SortDirection = null; - } + column?.SortDirection = null; } } @@ -1468,7 +1462,7 @@ private void UpdateBaseSelectionMode() base.SelectionMode = SelectionUnit is TableViewSelectionUnit.Cell ? ListViewSelectionMode.None : SelectionMode; UpdateHorizontalScrollBarMargin(); - _headerRow?.SetHeadersVisibility(); + HeaderRow?.SetHeadersVisibility(); foreach (var row in _rows) { @@ -1485,7 +1479,7 @@ private void UpdateBaseSelectionMode() /// private void EnsureGridLines() { - _headerRow?.EnsureGridLines(); + HeaderRow?.EnsureGridLines(); foreach (var row in _rows) { @@ -1603,7 +1597,7 @@ internal bool ShowCellContext(TableViewCell cell, Point position) /// internal void UpdateCornerButtonState() { - _headerRow?.SetCornerButtonState(); + HeaderRow?.SetCornerButtonState(); DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () => { @@ -1639,7 +1633,7 @@ private void SetHeadersVisibility() _headerRowDefinition.Height = areColumnHeadersVisible ? GridLength.Auto : new(0); } - _headerRow?.SetHeadersVisibility(); + HeaderRow?.SetHeadersVisibility(); foreach (var row in _rows) { diff --git a/src/TableViewCell.cs b/src/TableViewCell.cs index a94899b7..47a1f952 100644 --- a/src/TableViewCell.cs +++ b/src/TableViewCell.cs @@ -28,7 +28,6 @@ namespace WinUI.TableView; #endif public partial class TableViewCell : ContentControl { - private TableViewColumn? _column; private ScrollViewer? _scrollViewer; private ContentPresenter? _contentPresenter; private Border? _selectionBorder; @@ -614,12 +613,12 @@ internal void EnsureStyle(object? item) /// public TableViewColumn? Column { - get => _column; + get; internal set { - if (_column != value) + if (field != value) { - _column = value; + field = value; OnColumnChanged(); } } diff --git a/src/TableViewColumnHeader.cs b/src/TableViewColumnHeader.cs index 345190aa..d7d3fafd 100644 --- a/src/TableViewColumnHeader.cs +++ b/src/TableViewColumnHeader.cs @@ -65,10 +65,7 @@ public TableViewColumnHeader() /// private void OnWidthChanged(DependencyObject sender, DependencyProperty dp) { - if (Column is not null) - { - Column.ActualWidth = Width; - } + Column?.ActualWidth = Width; } /// @@ -181,6 +178,8 @@ private void ApplyFilter() var firstItem = selectedValues.FirstOrDefault(x => x is not null); var firstItemType = firstItem?.GetType(); +#pragma warning disable IDE0306 // Simplify collection initialization +#pragma warning disable IDE0028 // Simplify collection initialization return firstItemType switch { Type t when t == typeof(int) => new ObjectBackedTypedSet(selectedValues), @@ -191,6 +190,8 @@ private void ApplyFilter() _ => [.. selectedValues], }; +#pragma warning restore IDE0028 // Simplify collection initialization +#pragma warning restore IDE0306 // Simplify collection initialization } /// @@ -230,29 +231,14 @@ protected override void OnApplyTemplate() { base.OnApplyTemplate(); - if (_optionsFlyout is not null) - { - _optionsFlyout.Opening -= OnOptionsFlyoutOpening; - _optionsFlyout.Closed -= OnOptionsFlyoutClosed; - } - - if (_optionsButton is not null) - { - _optionsButton.Tapped -= OnOptionsButtonTaped; - } - - if (_filterItemsMenuItem is not null) - { - _filterItemsMenuItem.PreviewKeyUp -= OnFilterItemsMenuItemPreviewKeyUp; - } + _optionsFlyout?.Opening -= OnOptionsFlyoutOpening; + _optionsFlyout?.Closed -= OnOptionsFlyoutClosed; + _optionsButton?.Tapped -= OnOptionsButtonTaped; + _filterItemsMenuItem?.PreviewKeyUp -= OnFilterItemsMenuItemPreviewKeyUp; - if (_filterItemsControl is not null) - { - _filterItemsControl.FilterItems = null; - _filterItemsControl.TableView = null; - _filterItemsControl.ColumnHeader = null; - _filterItemsControl = null; - } + _filterItemsControl?.FilterItems = null; + _filterItemsControl?.TableView = null; + _filterItemsControl?.ColumnHeader = null; _tableView = this.FindAscendant(); _headerRow = this.FindAscendant(); @@ -271,19 +257,13 @@ protected override void OnApplyTemplate() _optionsFlyout.Closed += OnOptionsFlyoutClosed; _optionsButton.Tapped += OnOptionsButtonTaped; - if (_filterItemsMenuItem is not null) - { - _filterItemsMenuItem.ApplyTemplate(); - _filterItemsControl = _filterItemsMenuItem.FindDescendant(); + _filterItemsMenuItem?.ApplyTemplate(); + _filterItemsControl = _filterItemsMenuItem?.FindDescendant(); - if (_filterItemsControl is not null) - { - _filterItemsControl.TableView = _tableView; - _filterItemsControl.ColumnHeader = this; - } + _filterItemsControl?.TableView = _tableView; + _filterItemsControl?.ColumnHeader = this; - _filterItemsMenuItem.PreviewKeyUp += OnFilterItemsMenuItemPreviewKeyUp; - } + _filterItemsMenuItem?.PreviewKeyUp += OnFilterItemsMenuItemPreviewKeyUp; SetOptionCommands(); SetFilterButtonVisibility(); @@ -361,19 +341,13 @@ internal void OnIsFilteredChanged() /// internal void SetFilterButtonVisibility() { - if (_optionsButton is not null) - { - _optionsButton.Visibility = CanFilter ? Visibility.Visible : Visibility.Collapsed; - } + _optionsButton?.Visibility = CanFilter ? Visibility.Visible : Visibility.Collapsed; - if (_contentPresenter is not null) - { - _contentPresenter.Margin = CanFilter ? new Thickness( + _contentPresenter?.Margin = CanFilter ? new Thickness( Padding.Left, Padding.Top, Padding.Right + 8, 0) : Padding; - } } /// diff --git a/src/TableViewFilterItem.cs b/src/TableViewFilterItem.cs index 76749ede..4e3fa955 100644 --- a/src/TableViewFilterItem.cs +++ b/src/TableViewFilterItem.cs @@ -10,8 +10,6 @@ public partial class TableViewFilterItem : INotifyPropertyChanged /// public event PropertyChangedEventHandler? PropertyChanged; - private bool _isSelected; - /// /// Initializes a new instance of the FilterItem class. /// @@ -32,10 +30,10 @@ public TableViewFilterItem(bool isSelected, object? value, int count = 1, bool s /// public bool IsSelected { - get => _isSelected; + get; set { - _isSelected = value; + field = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected))); } } diff --git a/src/TableViewHeaderRow.OptionComamnds.cs b/src/TableViewHeaderRow.OptionComamnds.cs index 6d84a5ae..d8b5f8b7 100644 --- a/src/TableViewHeaderRow.OptionComamnds.cs +++ b/src/TableViewHeaderRow.OptionComamnds.cs @@ -25,15 +25,8 @@ internal void SetExportOptionsVisibility() { var visibility = TableView?.ShowExportOptions is true ? Visibility.Visible : Visibility.Collapsed; - if (_exportAllMenuItem is not null) - { - _exportAllMenuItem.Visibility = visibility; - } - - if (_exportSelectedMenuItem is not null) - { - _exportSelectedMenuItem.Visibility = visibility; - } + _exportAllMenuItem?.Visibility = visibility; + _exportSelectedMenuItem?.Visibility = visibility; } /// diff --git a/src/TableViewHeaderRow.cs b/src/TableViewHeaderRow.cs index 86183243..2c302ba0 100644 --- a/src/TableViewHeaderRow.cs +++ b/src/TableViewHeaderRow.cs @@ -42,7 +42,6 @@ public partial class TableViewHeaderRow : Control private TranslateTransform? _columnDropIndicatorTransform; private Image? _dragHeaderImage; private bool _calculatingHeaderWidths; - private DispatcherTimer? _timer; private int _dropColumnIndex; private bool _isValidDropTarget; private readonly Dictionary _callbackTokens = []; @@ -60,16 +59,9 @@ protected override void OnApplyTemplate() { base.OnApplyTemplate(); - if (_selectAllButton is not null) - { - _selectAllButton.Tapped -= OnSelectAllButtonClicked; - } - - if (_selectAllCheckBox is not null) - { - _selectAllCheckBox.Checked -= OnSelectAllCheckBoxChecked; - _selectAllCheckBox.Unchecked -= OnSelectAllCheckBoxUnchecked; - } + _selectAllButton?.Tapped -= OnSelectAllButtonClicked; + _selectAllCheckBox?.Checked -= OnSelectAllCheckBoxChecked; + _selectAllCheckBox?.Unchecked -= OnSelectAllCheckBoxUnchecked; _cornerButtonPanel = GetTemplateChild("CornerButtonPanel") as Panel; _optionsButton = GetTemplateChild("optionsButton") as Button; @@ -88,16 +80,9 @@ protected override void OnApplyTemplate() return; } - if (_selectAllButton is not null) - { - _selectAllButton.Tapped += OnSelectAllButtonClicked; - } - - if (_selectAllCheckBox is not null) - { - _selectAllCheckBox.Checked += OnSelectAllCheckBoxChecked; - _selectAllCheckBox.Unchecked += OnSelectAllCheckBoxUnchecked; - } + _selectAllButton?.Tapped += OnSelectAllButtonClicked; + _selectAllCheckBox?.Checked += OnSelectAllCheckBoxChecked; + _selectAllCheckBox?.Unchecked += OnSelectAllCheckBoxUnchecked; if (TableView.Columns is not null) { @@ -216,35 +201,6 @@ e.PropertyName is nameof(TableViewColumn.IsFrozen)) && CalculateHeaderWidths(); } } - else if (e.PropertyName is nameof(TableViewColumn.DesiredWidth)) - { - if (_timer is null) - { - _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) }; - _timer.Tick += OnTimerTick; - } - - _timer.Stop(); - _timer.Start(); - } - } - - /// - /// Handles the timer tick event. - /// - private void OnTimerTick(object? sender, object e) - { - if (!_calculatingHeaderWidths) - { - CalculateHeaderWidths(); - } - - if (_timer is not null) - { - _timer.Stop(); - _timer.Tick -= OnTimerTick; - _timer = null; - } } /// @@ -595,25 +551,13 @@ internal void ShowColumnDropIndicator(double position, RenderTargetBitmap header x -= _columnDropIndicator.ActualWidth / 2; _dropColumnIndex += midPoint > position ? -1 : 0; _columnDropIndicator.Visibility = Visibility.Visible; - - if (_columnDropIndicatorTransform is not null) - { - _columnDropIndicatorTransform.X = x; - } - - if (_dragHeaderImage is not null) - { - _dragHeaderImage.Source = headerVisuals; - } + _columnDropIndicatorTransform?.X = x; + _dragHeaderImage?.Source = headerVisuals; } else { _isValidDropTarget = false; - - if (_columnDropIndicator is not null) - { - _columnDropIndicator.Visibility = Visibility.Collapsed; - } + _columnDropIndicator?.Visibility = Visibility.Collapsed; } } diff --git a/src/TableViewRow.cs b/src/TableViewRow.cs index d403a149..3f8a4278 100644 --- a/src/TableViewRow.cs +++ b/src/TableViewRow.cs @@ -32,8 +32,6 @@ public partial class TableViewRow : ListViewItem private Thickness _focusVisualMargin = new(1); private readonly Thickness _selectionBackgroundMargin = new(4, 2, 4, 2); private readonly Thickness _selectionIndicatorMargin = new(4, 0, 0, 0); - - private TableView? _tableView; private ListViewItemPresenter? _itemPresenter; private Border? _selectionBackground; private bool _ensureCells = true; @@ -147,7 +145,7 @@ protected override void OnContentChanged(object oldContent, object newContent) } RowPresenter?.InvalidateMeasure(); // The cells presenter does not measure every time. - _tableView?.EnsureAlternateRowColors(); + TableView?.EnsureAlternateRowColors(); } /// @@ -627,13 +625,13 @@ private double GetHorizontalGridlineHeight() /// public TableView? TableView { - get => _tableView; + get; internal set { - if (_tableView != value) + if (field != value) { OnTableViewChanging(); - _tableView = value; + field = value; OnTableViewChanged(); } } diff --git a/src/TableViewRowPresenter.cs b/src/TableViewRowPresenter.cs index 3ea196ca..59b443a0 100644 --- a/src/TableViewRowPresenter.cs +++ b/src/TableViewRowPresenter.cs @@ -48,20 +48,14 @@ protected override void OnApplyTemplate() { base.OnApplyTemplate(); - if (_detailsToggleButton is not null) - { - _detailsToggleButton.Tapped -= OnDetailsToggleButtonTapped; - } + _detailsToggleButton?.Tapped -= OnDetailsToggleButtonTapped; - if (_detailsPanel is not null) - { - _detailsPanel.SizeChanged -= OnDetailsPanelSizeChanged; + _detailsPanel?.SizeChanged -= OnDetailsPanelSizeChanged; - if (_detailsPanelVisibilityCallbackToken is long token) - { - _detailsPanel.UnregisterPropertyChangedCallback(VisibilityProperty, token); - _detailsPanelVisibilityCallbackToken = null; - } + if (_detailsPanelVisibilityCallbackToken is long token) + { + _detailsPanel?.UnregisterPropertyChangedCallback(VisibilityProperty, token); + _detailsPanelVisibilityCallbackToken = null; } _rowHeader = GetTemplateChild("RowHeader") as TableViewRowHeader; @@ -77,17 +71,10 @@ protected override void OnApplyTemplate() _itemPresenter = this.FindAscendant(); TableViewRow = this.FindAscendant(); TableView = TableViewRow?.TableView; + _rowHeader?.TableView = TableView; + _rowHeader?.TableViewRow = TableViewRow; - if (_rowHeader is not null) - { - _rowHeader.TableView = TableView; - _rowHeader.TableViewRow = TableViewRow; - } - - if (_detailsToggleButton is not null) - { - _detailsToggleButton.Tapped += OnDetailsToggleButtonTapped; - } + _detailsToggleButton?.Tapped += OnDetailsToggleButtonTapped; if (_detailsPanel is not null) { @@ -266,7 +253,7 @@ internal void ApplyDetailsPaneState(object? item) if (TableView?.RowDetailsVisibilityMode is TableViewRowDetailsVisibilityMode.VisibleWhenExpanded && _detailsToggleButton is not null && TableView is not null && item is not null) { - var isChecked = TableView.DetailsPaneStates.TryGetValue(item, out var value) ? value.Value : false; + var isChecked = TableView.DetailsPaneStates.TryGetValue(item, out var value) && value.Value; _detailsToggleButton!.IsChecked = isChecked; ToggleDetailsPane(item, isChecked); } diff --git a/src/WinUI.TableView.csproj b/src/WinUI.TableView.csproj index bcdf89da..9937dcb8 100644 --- a/src/WinUI.TableView.csproj +++ b/src/WinUI.TableView.csproj @@ -5,7 +5,7 @@ net9.0; net10.0; - 12 + 14 enable true From a0fac136b08afeba97d217d13ac0a250a9752e93 Mon Sep 17 00:00:00 2001 From: Waheed Ahmad Date: Tue, 2 Jun 2026 19:52:13 +0500 Subject: [PATCH 2/6] code cleanup --- src/Columns/TableViewBoundColumn.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Columns/TableViewBoundColumn.cs b/src/Columns/TableViewBoundColumn.cs index 14a3020b..f6bf5a7d 100644 --- a/src/Columns/TableViewBoundColumn.cs +++ b/src/Columns/TableViewBoundColumn.cs @@ -8,8 +8,6 @@ namespace WinUI.TableView; /// public abstract class TableViewBoundColumn : TableViewColumn { - // private Binding _binding;//= new(); - /// /// Gets the property path for the binding. /// From 2101397c2b1a1c54759c82b958590a47ca5e0276 Mon Sep 17 00:00:00 2001 From: Waheed Ahmad Date: Tue, 2 Jun 2026 19:54:33 +0500 Subject: [PATCH 3/6] make header row field again --- src/TableView.Properties.cs | 23 +++++++++-------------- src/TableView.cs | 11 ++++++----- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/TableView.Properties.cs b/src/TableView.Properties.cs index 536b15d1..f782956a 100644 --- a/src/TableView.Properties.cs +++ b/src/TableView.Properties.cs @@ -366,11 +366,6 @@ public bool ShowFilterItemsCount /// internal ConditionalWeakTable> DetailsPaneStates { get; } = []; - /// - /// Gets or sets the header row of the TableView. - /// - internal TableViewHeaderRow? HeaderRow { get; set; } - /// /// Gets or sets the filter handler for the TableView. /// @@ -839,7 +834,7 @@ private static void OnShowExportOptionsChanged(DependencyObject d, DependencyPro { if (d is TableView tableView) { - tableView.HeaderRow?.SetExportOptionsVisibility(); + tableView._headerRow?.SetExportOptionsVisibility(); } } @@ -895,9 +890,9 @@ private static void OnIsReadOnlyChanged(DependencyObject d, DependencyPropertyCh /// private static void OnCanFilterColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is TableView tableView && tableView.HeaderRow is not null) + if (d is TableView tableView && tableView._headerRow is not null) { - foreach (var header in tableView.HeaderRow.Headers) + foreach (var header in tableView._headerRow.Headers) { header.SetFilterButtonVisibility(); } @@ -909,9 +904,9 @@ private static void OnCanFilterColumnsChanged(DependencyObject d, DependencyProp /// private static void OnMinColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is TableView table && table.HeaderRow is not null) + if (d is TableView table && table._headerRow is not null) { - table.HeaderRow.CalculateHeaderWidths(); + table._headerRow.CalculateHeaderWidths(); } } @@ -920,9 +915,9 @@ private static void OnMinColumnWidthChanged(DependencyObject d, DependencyProper /// private static void OnMaxColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is TableView table && table.HeaderRow is not null) + if (d is TableView table && table._headerRow is not null) { - table.HeaderRow.CalculateHeaderWidths(); + table._headerRow.CalculateHeaderWidths(); } } @@ -1005,9 +1000,9 @@ private static async void OnCurrentCellSlotChanged(DependencyObject d, Dependenc /// private static void OnHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is TableView { HeaderRow: { } } tableView) + if (d is TableView { _headerRow: { } } tableView) { - tableView.HeaderRow.InvalidateArrange(); + tableView._headerRow.InvalidateArrange(); foreach (var row in tableView._rows) { diff --git a/src/TableView.cs b/src/TableView.cs index 3168bff7..77156a97 100644 --- a/src/TableView.cs +++ b/src/TableView.cs @@ -31,6 +31,7 @@ namespace WinUI.TableView; [StyleTypedProperty(Property = nameof(CellStyle), StyleTargetType = typeof(TableViewCell))] public partial class TableView : ListView { + private TableViewHeaderRow? _headerRow; private ScrollViewer? _scrollViewer; private RowDefinition? _headerRowDefinition; private bool _shouldThrowSelectionModeChangedException; @@ -334,7 +335,7 @@ protected async override void OnApplyTemplate() { base.OnApplyTemplate(); - HeaderRow = GetTemplateChild("HeaderRow") as TableViewHeaderRow; + _headerRow = GetTemplateChild("HeaderRow") as TableViewHeaderRow; _scrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer; _headerRowDefinition = GetTemplateChild("HeaderRowDefinition") as RowDefinition; _scrollViewer?.Loaded += OnScrollViewerLoaded; @@ -1462,7 +1463,7 @@ private void UpdateBaseSelectionMode() base.SelectionMode = SelectionUnit is TableViewSelectionUnit.Cell ? ListViewSelectionMode.None : SelectionMode; UpdateHorizontalScrollBarMargin(); - HeaderRow?.SetHeadersVisibility(); + _headerRow?.SetHeadersVisibility(); foreach (var row in _rows) { @@ -1479,7 +1480,7 @@ private void UpdateBaseSelectionMode() /// private void EnsureGridLines() { - HeaderRow?.EnsureGridLines(); + _headerRow?.EnsureGridLines(); foreach (var row in _rows) { @@ -1597,7 +1598,7 @@ internal bool ShowCellContext(TableViewCell cell, Point position) /// internal void UpdateCornerButtonState() { - HeaderRow?.SetCornerButtonState(); + _headerRow?.SetCornerButtonState(); DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () => { @@ -1633,7 +1634,7 @@ private void SetHeadersVisibility() _headerRowDefinition.Height = areColumnHeadersVisible ? GridLength.Auto : new(0); } - HeaderRow?.SetHeadersVisibility(); + _headerRow?.SetHeadersVisibility(); foreach (var row in _rows) { From fd9b2c22c448da54c1c361f85cf9b32b4a7ae10c Mon Sep 17 00:00:00 2001 From: Waheed Ahmad Date: Tue, 2 Jun 2026 23:43:34 +0500 Subject: [PATCH 4/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/TableViewColumnHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TableViewColumnHeader.cs b/src/TableViewColumnHeader.cs index d7d3fafd..e61b0efe 100644 --- a/src/TableViewColumnHeader.cs +++ b/src/TableViewColumnHeader.cs @@ -239,7 +239,7 @@ protected override void OnApplyTemplate() _filterItemsControl?.FilterItems = null; _filterItemsControl?.TableView = null; _filterItemsControl?.ColumnHeader = null; - + _filterItemsControl = null; _tableView = this.FindAscendant(); _headerRow = this.FindAscendant(); _optionsButton = GetTemplateChild("OptionsButton") as Button; From 013d5833919bc1cd13ba686c29713e91631b37af Mon Sep 17 00:00:00 2001 From: Waheed Ahmad Date: Tue, 2 Jun 2026 23:44:09 +0500 Subject: [PATCH 5/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/Controls/TableViewFilterItemsControl.xaml.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Controls/TableViewFilterItemsControl.xaml.cs b/src/Controls/TableViewFilterItemsControl.xaml.cs index d70936a4..50b2c42c 100644 --- a/src/Controls/TableViewFilterItemsControl.xaml.cs +++ b/src/Controls/TableViewFilterItemsControl.xaml.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Windows.System; From 8fa6d1450748258464a08c991f28a0e4cfeda23a Mon Sep 17 00:00:00 2001 From: Waheed Ahmad Date: Wed, 3 Jun 2026 13:29:57 +0500 Subject: [PATCH 6/6] addedunit tests for all column types --- tests/TableViewBoundColumnTests.cs | 42 +++++++++ tests/TableViewCheckBoxColumnTests.cs | 52 +++++++++++ tests/TableViewColumnTestHelpers.cs | 80 ++++++++++++++++ tests/TableViewColumnTests.cs | 109 ++++++++++++++++++++++ tests/TableViewComboBoxColumnTests.cs | 108 +++++++++++++++++++++ tests/TableViewDateColumnTests.cs | 79 ++++++++++++++++ tests/TableViewHyperlinkColumnTests.cs | 73 +++++++++++++++ tests/TableViewNumberColumnTests.cs | 52 +++++++++++ tests/TableViewTemplateColumnTests.cs | 66 +++++++++++++ tests/TableViewTextColumnTests.cs | 60 ++++++++++++ tests/TableViewTimeColumnTests.cs | 68 ++++++++++++++ tests/TableViewToggleSwitchColumnTests.cs | 54 +++++++++++ 12 files changed, 843 insertions(+) create mode 100644 tests/TableViewBoundColumnTests.cs create mode 100644 tests/TableViewCheckBoxColumnTests.cs create mode 100644 tests/TableViewColumnTestHelpers.cs create mode 100644 tests/TableViewColumnTests.cs create mode 100644 tests/TableViewComboBoxColumnTests.cs create mode 100644 tests/TableViewDateColumnTests.cs create mode 100644 tests/TableViewHyperlinkColumnTests.cs create mode 100644 tests/TableViewNumberColumnTests.cs create mode 100644 tests/TableViewTemplateColumnTests.cs create mode 100644 tests/TableViewTextColumnTests.cs create mode 100644 tests/TableViewTimeColumnTests.cs create mode 100644 tests/TableViewToggleSwitchColumnTests.cs diff --git a/tests/TableViewBoundColumnTests.cs b/tests/TableViewBoundColumnTests.cs new file mode 100644 index 00000000..b3dd8d03 --- /dev/null +++ b/tests/TableViewBoundColumnTests.cs @@ -0,0 +1,42 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; + +namespace WinUI.TableView.Tests; + +[TestClass] +public class TableViewBoundColumnTests +{ + [UITestMethod] + public void TableViewBoundColumn_BindingAndOperationBinding_UseExpectedDefaults() + { + var binding = new Binding + { + Path = new PropertyPath(nameof(ColumnTestItem.Name)), + Mode = BindingMode.OneWay, + UpdateSourceTrigger = UpdateSourceTrigger.Default + }; + + var column = new TableViewTextColumn + { + Binding = binding + }; + + Assert.AreEqual(BindingMode.TwoWay, column.Binding.Mode); + Assert.AreEqual(UpdateSourceTrigger.Explicit, column.Binding.UpdateSourceTrigger); + Assert.AreSame(column.Binding, column.OperationContentBinding); + } + + [UITestMethod] + public void TableViewBoundColumn_ClipboardContentBinding_FallsBackToBinding() + { + var column = new TableViewTextColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.Name)) } + }; + + Assert.AreSame(column.Binding, column.ClipboardContentBinding); + Assert.AreEqual(nameof(ColumnTestItem.Name), column.ClipboardContentBinding!.Path!.Path); + } +} diff --git a/tests/TableViewCheckBoxColumnTests.cs b/tests/TableViewCheckBoxColumnTests.cs new file mode 100644 index 00000000..1b660a80 --- /dev/null +++ b/tests/TableViewCheckBoxColumnTests.cs @@ -0,0 +1,52 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using System; + +namespace WinUI.TableView.Tests; + +[TestClass] +public class TableViewCheckBoxColumnTests +{ + [UITestMethod] + public void TableViewCheckBoxColumn_UsesSingleElementAndTracksReadOnlyState() + { + var tableView = new TableView { IsReadOnly = false }; + var column = new TableViewCheckBoxColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.IsEnabled)) } + }; + column.SetOwningTableView(tableView); + + var checkBox = (CheckBox)column.GenerateElement(new TableViewCell(), new ColumnTestItem()); + var cell = new TableViewCell { Content = checkBox }; + + Assert.IsTrue(column.UseSingleElement); + Assert.IsTrue(checkBox.IsHitTestVisible); + + column.IsReadOnly = true; + column.UpdateElementState(cell, null); + + Assert.IsFalse(checkBox.IsHitTestVisible); + checkBox.IsChecked = true; + Assert.AreEqual(true, column.PrepareCellForEdit(cell, new RoutedEventArgs())); + Assert.ThrowsExactly(() => column.GenerateEditingElement(new TableViewCell(), null)); + } + + [UITestMethod] + public void TableViewCheckBoxColumn_RespectsTableViewReadOnlyState() + { + var tableView = new TableView { IsReadOnly = true }; + var column = new TableViewCheckBoxColumn(); + column.SetOwningTableView(tableView); + + var checkBox = (CheckBox)column.GenerateElement(new TableViewCell(), null); + + Assert.IsFalse(checkBox.IsHitTestVisible); + Assert.AreEqual(new Thickness(12, 0, 12, 0), checkBox.Margin); + Assert.AreEqual(20d, checkBox.MinWidth); + Assert.AreEqual(20d, checkBox.MaxWidth); + } +} diff --git a/tests/TableViewColumnTestHelpers.cs b/tests/TableViewColumnTestHelpers.cs new file mode 100644 index 00000000..4e687232 --- /dev/null +++ b/tests/TableViewColumnTestHelpers.cs @@ -0,0 +1,80 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Markup; +using System; + +namespace WinUI.TableView.Tests; + +internal static class TableViewColumnTestHelpers +{ + public static DataTemplate CreateTemplate(string text) + { + return (DataTemplate)XamlReader.Load( + $""" + + + + """); + } +} + +internal sealed class ColumnTestColumn : TableViewColumn +{ + public override FrameworkElement GenerateElement(TableViewCell cell, object? dataItem) => new TextBlock(); + + public override FrameworkElement GenerateEditingElement(TableViewCell cell, object? dataItem) => new TextBox(); +} + +internal sealed class ConstantTemplateSelector(DataTemplate template) : DataTemplateSelector +{ + protected override DataTemplate SelectTemplateCore(object item) => template; +} + +internal sealed class PrefixConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, string language) => $"{parameter}{value}"; + + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotSupportedException(); +} + +internal sealed class ColumnTestItem +{ + public string? Name { get; set; } + + public string? ClipboardText { get; set; } + + public ColumnTestNestedItem? Nested { get; set; } + + public bool IsEnabled { get; set; } + + public ColumnTestOptionItem? SelectedOption { get; set; } + + public int SelectedOptionId { get; set; } + + public string? SelectedOptionText { get; set; } + + public DateOnly DueDate { get; set; } + + public TimeOnly AppointmentTime { get; set; } + + public double Amount { get; set; } + + public Uri? Link { get; set; } + + public object? UnknownDateLikeValue { get; set; } + + public object? UnknownTimeLikeValue { get; set; } +} + +internal sealed class ColumnTestNestedItem +{ + public string? Value { get; set; } +} + +internal sealed class ColumnTestOptionItem +{ + public int Id { get; set; } + + public string? Name { get; set; } +} diff --git a/tests/TableViewColumnTests.cs b/tests/TableViewColumnTests.cs new file mode 100644 index 00000000..3933ade7 --- /dev/null +++ b/tests/TableViewColumnTests.cs @@ -0,0 +1,109 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; + +namespace WinUI.TableView.Tests; + +[TestClass] +public class TableViewColumnTests +{ + [UITestMethod] + public void TableViewColumn_Constructor_InitializesConditionalCellStyles() + { + var column = new ColumnTestColumn(); + + Assert.IsNotNull(column.ConditionalCellStyles); + Assert.AreEqual(0, column.ConditionalCellStyles.Count); + } + + [UITestMethod] + public void TableViewColumn_ContentAccessors_UseConfiguredBindings() + { + var column = new ColumnTestColumn + { + OperationContentBinding = new Binding + { + Path = new PropertyPath($"{nameof(ColumnTestItem.Nested)}.{nameof(ColumnTestNestedItem.Value)}"), + Converter = new PrefixConverter(), + ConverterParameter = "display:" + }, + ClipboardContentBinding = new Binding + { + Path = new PropertyPath(nameof(ColumnTestItem.ClipboardText)), + Converter = new PrefixConverter(), + ConverterParameter = "clipboard:" + } + }; + + var item = new ColumnTestItem + { + Nested = new ColumnTestNestedItem { Value = "row value" }, + ClipboardText = "copy value" + }; + + Assert.AreEqual("display:row value", column.GetCellContent(item)); + Assert.AreEqual("clipboard:copy value", column.GetClipboardContent(item)); + Assert.IsNull(column.GetCellContent(null)); + Assert.IsNull(column.GetClipboardContent(null)); + } + + [UITestMethod] + public void TableViewColumn_ClipboardContentBinding_FallsBackToOperationContentBinding() + { + var operationBinding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.Name)) }; + var column = new ColumnTestColumn + { + OperationContentBinding = operationBinding + }; + + var item = new ColumnTestItem { Name = "fallback" }; + + Assert.AreSame(operationBinding, column.ClipboardContentBinding); + Assert.AreEqual("fallback", column.GetClipboardContent(item)); + } + + [UITestMethod] + public void TableViewColumn_GetCellContent_ReturnsInput_WhenNoBindingIsConfigured() + { + var column = new ColumnTestColumn(); + var item = new ColumnTestItem { Name = "raw" }; + + Assert.AreSame(item, column.GetCellContent(item)); + Assert.AreSame(item, column.GetClipboardContent(item)); + } + + [UITestMethod] + public void TableViewColumn_EnsureHeaderStyle_UsesTableViewStyleAndColumnOverride() + { + var tableViewStyle = new Style { TargetType = typeof(TableViewColumnHeader) }; + var columnStyle = new Style { TargetType = typeof(TableViewColumnHeader) }; + var tableView = new TableView { ColumnHeaderStyle = tableViewStyle }; + var headerControl = new TableViewColumnHeader(); + var column = new ColumnTestColumn(); + + column.SetOwningTableView(tableView); + column.HeaderControl = headerControl; + column.EnsureHeaderStyle(); + + Assert.AreSame(tableViewStyle, headerControl.Style); + + column.HeaderStyle = columnStyle; + + Assert.AreSame(columnStyle, headerControl.Style); + } + + [UITestMethod] + public void TableViewColumn_DefaultVirtualMembers_AreNoOps() + { + var column = new ColumnTestColumn(); + var cell = new TableViewCell(); + + column.RefreshElement(cell, null); + column.UpdateElementState(cell, null); + column.EndCellEditing(cell, null, TableViewEditAction.Cancel, null); + + Assert.IsNull(column.PrepareCellForEdit(cell, new RoutedEventArgs())); + } +} diff --git a/tests/TableViewComboBoxColumnTests.cs b/tests/TableViewComboBoxColumnTests.cs new file mode 100644 index 00000000..9533e741 --- /dev/null +++ b/tests/TableViewComboBoxColumnTests.cs @@ -0,0 +1,108 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace WinUI.TableView.Tests; + +[TestClass] +public class TableViewComboBoxColumnTests +{ + [UITestMethod] + public async Task TableViewComboBoxColumn_GeneratesDisplayAndEditingElements() + { + var options = new List + { + new() { Id = 1, Name = "One" }, + new() { Id = 2, Name = "Two" } + }; + + var item = new ColumnTestItem + { + SelectedOption = options[1], + SelectedOptionId = options[1].Id, + SelectedOptionText = options[1].Name + }; + + var column = new TableViewComboBoxColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.SelectedOption)) }, + DisplayMemberPath = nameof(ColumnTestOptionItem.Name), + SelectedValuePath = nameof(ColumnTestOptionItem.Id), + ItemsSource = options, + IsEditable = true, + TextBinding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.SelectedOptionText)), Mode = BindingMode.OneWay }, + SelectedValueBinding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.SelectedOptionId)), Mode = BindingMode.OneWay } + }; + + Assert.AreEqual(BindingMode.TwoWay, column.TextBinding!.Mode); + Assert.AreEqual(BindingMode.TwoWay, column.SelectedValueBinding!.Mode); + + var element = (TextBlock)column.GenerateElement(new TableViewCell(), item); + var editingElement = (ComboBox)column.GenerateEditingElement(new TableViewCell(), item); + + Assert.IsNotNull(element.GetBindingExpression(FrameworkElement.DataContextProperty)); + Assert.IsNotNull(element.GetBindingExpression(TextBlock.TextProperty)); + Assert.AreEqual(new Thickness(12, 0, 12, 0), element.Margin); + + try + { + editingElement.DataContext = item; + await UnitTestApp.Current.MainWindow.LoadTestContentAsync(editingElement); + Assert.AreSame(options, editingElement.ItemsSource); + Assert.AreEqual(nameof(ColumnTestOptionItem.Name), editingElement.DisplayMemberPath); + Assert.AreEqual(nameof(ColumnTestOptionItem.Id), editingElement.SelectedValuePath); + Assert.IsTrue(editingElement.IsEditable); + Assert.AreSame(options[1], editingElement.SelectedItem); + } + finally + { + await UnitTestApp.Current.MainWindow.UnloadTestContentAsync(editingElement); + } + + var cell = new TableViewCell { Content = editingElement }; + Assert.AreSame(options[1], column.PrepareCellForEdit(cell, new RoutedEventArgs())); + } + + [UITestMethod] + public async Task TableViewComboBoxColumn_GeneratesDisplayElementWithoutDisplayMemberPath() + { + var item = new ColumnTestItem { SelectedOptionText = "Direct text" }; + var column = new TableViewComboBoxColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.SelectedOptionText)) } + }; + + var element = (TextBlock)column.GenerateElement(new TableViewCell(), item); + + try + { + element.DataContext = item; + await UnitTestApp.Current.MainWindow.LoadTestContentAsync(element); + Assert.AreEqual("Direct text", element.Text); + } + finally + { + await UnitTestApp.Current.MainWindow.UnloadTestContentAsync(element); + } + } + + [UITestMethod] + public void TableViewComboBoxColumn_GenerateEditingElement_DoesNotBindOptionalProperties_WhenUnset() + { + var column = new TableViewComboBoxColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.SelectedOptionText)) } + }; + + var editingElement = (ComboBox)column.GenerateEditingElement(new TableViewCell(), new ColumnTestItem()); + + Assert.IsNull(editingElement.GetBindingExpression(ComboBox.TextProperty)); + Assert.IsNull(editingElement.GetBindingExpression(Selector.SelectedValueProperty)); + Assert.AreEqual(HorizontalAlignment.Stretch, editingElement.HorizontalAlignment); + } +} diff --git a/tests/TableViewDateColumnTests.cs b/tests/TableViewDateColumnTests.cs new file mode 100644 index 00000000..e4a83ae1 --- /dev/null +++ b/tests/TableViewDateColumnTests.cs @@ -0,0 +1,79 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using System; +using WinUI.TableView.Controls; +using WinUI.TableView.Helpers; + +namespace WinUI.TableView.Tests; + +[TestClass] +public class TableViewDateColumnTests +{ + [UITestMethod] + public void TableViewDateColumn_GeneratesEditingElementWithConfiguredProperties() + { + var minDate = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero); + var maxDate = new DateTimeOffset(2024, 12, 31, 0, 0, 0, TimeSpan.Zero); + var item = new ColumnTestItem { DueDate = new DateOnly(2024, 6, 15) }; + var column = new TableViewDateColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.DueDate)) }, + MinDate = minDate, + MaxDate = maxDate, + DateFormat = "month day", + IsTodayHighlighted = false, + IsGroupLabelVisible = false, + IsOutOfScopeEnabled = false, + DayOfWeekFormat = "{dayofweek.abbreviated(2)}" + }; + + var element = (TableViewDatePicker)column.GenerateEditingElement(new TableViewCell(), item); + var cell = new TableViewCell { Content = element }; + element.SelectedDate = item.DueDate; + + Assert.AreEqual(minDate, element.MinDate); + Assert.AreEqual(maxDate, element.MaxDate); + Assert.AreEqual("month day", element.DateFormat); + Assert.IsFalse(element.IsTodayHighlighted); + Assert.IsFalse(element.IsGroupLabelVisible); + Assert.IsFalse(element.IsOutOfScopeEnabled); + Assert.AreEqual("{dayofweek.abbreviated(2)}", element.DayOfWeekFormat); + Assert.AreEqual(TableViewLocalizedStrings.DatePickerPlaceholder, element.PlaceholderText); + Assert.AreEqual(typeof(DateOnly), element.SourceType); + Assert.AreEqual(item.DueDate, column.PrepareCellForEdit(cell, new RoutedEventArgs())); + } + + [UITestMethod] + public void TableViewDateColumn_GeneratesDisplayElement_WithFormatBindings() + { + var column = new TableViewDateColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.DueDate)) }, + DateFormat = "shortdate" + }; + + var element = (TextBlock)column.GenerateElement(new TableViewCell(), new ColumnTestItem()); + + Assert.IsNotNull(element.GetBindingExpression(DateTimeFormatHelper.ValueProperty)); + Assert.IsNotNull(element.GetBindingExpression(DateTimeFormatHelper.FormatProperty)); + Assert.AreEqual(new Thickness(12, 0, 12, 0), element.Margin); + } + + [UITestMethod] + public void TableViewDateColumn_UsesCustomPlaceholderAndDateTimeOffsetFallback() + { + var column = new TableViewDateColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.UnknownDateLikeValue)) }, + PlaceHolderText = "Choose a date" + }; + + var element = (TableViewDatePicker)column.GenerateEditingElement(new TableViewCell(), new ColumnTestItem()); + + Assert.AreEqual("Choose a date", element.PlaceholderText); + Assert.AreEqual(typeof(DateTimeOffset), element.SourceType); + } +} diff --git a/tests/TableViewHyperlinkColumnTests.cs b/tests/TableViewHyperlinkColumnTests.cs new file mode 100644 index 00000000..21e27527 --- /dev/null +++ b/tests/TableViewHyperlinkColumnTests.cs @@ -0,0 +1,73 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using System; +using System.Threading.Tasks; + +namespace WinUI.TableView.Tests; + +[TestClass] +public class TableViewHyperlinkColumnTests +{ + [UITestMethod] + public async Task TableViewHyperlinkColumn_GeneratesBoundHyperlink() + { + var item = new ColumnTestItem + { + Link = new Uri("https://example.com/docs"), + Name = "Docs" + }; + + var column = new TableViewHyperlinkColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.Link)) }, + ContentBinding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.Name)) } + }; + + var element = (HyperlinkButton)column.GenerateElement(new TableViewCell(), item); + + try + { + element.DataContext = item; + await UnitTestApp.Current.MainWindow.LoadTestContentAsync(element); + Assert.AreEqual(item.Link, element.NavigateUri); + Assert.AreEqual("Docs", element.Content); + } + finally + { + await UnitTestApp.Current.MainWindow.UnloadTestContentAsync(element); + } + } + + [UITestMethod] + public async Task TableViewHyperlinkColumn_FallsBackToBindingForContent() + { + var item = new ColumnTestItem + { + Link = new Uri("https://example.com/fallback") + }; + + var column = new TableViewHyperlinkColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.Link)) } + }; + + var element = (HyperlinkButton)column.GenerateElement(new TableViewCell(), item); + + try + { + element.DataContext = item; + await UnitTestApp.Current.MainWindow.LoadTestContentAsync(element); + Assert.AreEqual(item.Link, element.NavigateUri); + Assert.AreEqual(item.Link, element.Content); + Assert.AreEqual(HorizontalAlignment.Stretch, element.HorizontalAlignment); + Assert.AreEqual(HorizontalAlignment.Left, element.HorizontalContentAlignment); + } + finally + { + await UnitTestApp.Current.MainWindow.UnloadTestContentAsync(element); + } + } +} diff --git a/tests/TableViewNumberColumnTests.cs b/tests/TableViewNumberColumnTests.cs new file mode 100644 index 00000000..dfd2afec --- /dev/null +++ b/tests/TableViewNumberColumnTests.cs @@ -0,0 +1,52 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using System.Threading.Tasks; + +namespace WinUI.TableView.Tests; + +[TestClass] +public class TableViewNumberColumnTests +{ + [UITestMethod] + public void TableViewNumberColumn_GeneratesNumericElements() + { + var column = new TableViewNumberColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.Amount)) } + }; + + var element = (TextBlock)column.GenerateElement(new TableViewCell(), null); + var editingElement = (NumberBox)column.GenerateEditingElement(new TableViewCell(), null); + editingElement.Value = 42.5; + + Assert.AreEqual(TextAlignment.Right, element.TextAlignment); + Assert.AreEqual(new Thickness(12, 0, 12, 0), element.Margin); + Assert.AreEqual(42.5, column.PrepareCellForEdit(new TableViewCell { Content = editingElement }, new RoutedEventArgs())); + } + + [UITestMethod] + public async Task TableViewNumberColumn_GeneratesBoundEditingElement() + { + var item = new ColumnTestItem { Amount = 11.25 }; + var column = new TableViewNumberColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.Amount)) } + }; + + var editingElement = (NumberBox)column.GenerateEditingElement(new TableViewCell(), item); + + try + { + editingElement.DataContext = item; + await UnitTestApp.Current.MainWindow.LoadTestContentAsync(editingElement); + Assert.AreEqual(11.25, editingElement.Value); + } + finally + { + await UnitTestApp.Current.MainWindow.UnloadTestContentAsync(editingElement); + } + } +} diff --git a/tests/TableViewTemplateColumnTests.cs b/tests/TableViewTemplateColumnTests.cs new file mode 100644 index 00000000..933fee38 --- /dev/null +++ b/tests/TableViewTemplateColumnTests.cs @@ -0,0 +1,66 @@ +using Microsoft.UI.Xaml.Controls; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; + +namespace WinUI.TableView.Tests; + +[TestClass] +public class TableViewTemplateColumnTests +{ + [UITestMethod] + public void TableViewTemplateColumn_UsesTemplatesForDisplayEditingAndRefresh() + { + var cellTemplate = TableViewColumnTestHelpers.CreateTemplate("display"); + var editingTemplate = TableViewColumnTestHelpers.CreateTemplate("editing"); + var column = new TableViewTemplateColumn + { + CellTemplate = cellTemplate, + EditingTemplate = editingTemplate + }; + + var displayElement = (ContentControl)column.GenerateElement(new TableViewCell(), new ColumnTestItem()); + var editingElement = (ContentControl)column.GenerateEditingElement(new TableViewCell(), new ColumnTestItem()); + var cell = new TableViewCell(); + + column.RefreshElement(cell, new ColumnTestItem()); + + Assert.IsFalse(column.CanSort); + Assert.IsFalse(column.CanFilter); + Assert.AreSame(cellTemplate, displayElement.ContentTemplate); + Assert.AreSame(editingTemplate, editingElement.ContentTemplate); + Assert.IsInstanceOfType(cell.Content); + Assert.AreSame(cellTemplate, ((ContentControl)cell.Content).ContentTemplate); + } + + [UITestMethod] + public void TableViewTemplateColumn_GenerateEditingElement_FallsBackToDisplayTemplate_WhenEditingTemplateIsMissing() + { + var cellTemplate = TableViewColumnTestHelpers.CreateTemplate("display"); + var column = new TableViewTemplateColumn + { + CellTemplate = cellTemplate + }; + + var editingElement = (ContentControl)column.GenerateEditingElement(new TableViewCell(), new ColumnTestItem()); + + Assert.AreSame(cellTemplate, editingElement.ContentTemplate); + } + + [UITestMethod] + public void TableViewTemplateColumn_UsesTemplateSelectors() + { + var displayTemplate = TableViewColumnTestHelpers.CreateTemplate("selected-display"); + var editingTemplate = TableViewColumnTestHelpers.CreateTemplate("selected-edit"); + var column = new TableViewTemplateColumn + { + CellTemplateSelector = new ConstantTemplateSelector(displayTemplate), + EditingTemplateSelector = new ConstantTemplateSelector(editingTemplate) + }; + + var displayElement = (ContentControl)column.GenerateElement(new TableViewCell(), new ColumnTestItem()); + var editingElement = (ContentControl)column.GenerateEditingElement(new TableViewCell(), new ColumnTestItem()); + + Assert.AreSame(displayTemplate, displayElement.ContentTemplate); + Assert.AreSame(editingTemplate, editingElement.ContentTemplate); + } +} diff --git a/tests/TableViewTextColumnTests.cs b/tests/TableViewTextColumnTests.cs new file mode 100644 index 00000000..d2ecca0f --- /dev/null +++ b/tests/TableViewTextColumnTests.cs @@ -0,0 +1,60 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using System.Threading.Tasks; + +namespace WinUI.TableView.Tests; + +[TestClass] +public class TableViewTextColumnTests +{ + [UITestMethod] + public async Task TableViewTextColumn_GeneratesBoundTextElements() + { + var item = new ColumnTestItem { Name = "Alice" }; + var column = new TableViewTextColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.Name)) } + }; + + var element = (TextBlock)column.GenerateElement(new TableViewCell(), item); + var editingElement = (TextBox)column.GenerateEditingElement(new TableViewCell(), item); + + try + { + element.DataContext = item; + await UnitTestApp.Current.MainWindow.LoadTestContentAsync(element); + Assert.AreEqual("Alice", element.Text); + Assert.AreEqual(new Thickness(12, 0, 12, 0), element.Margin); + } + finally + { + await UnitTestApp.Current.MainWindow.UnloadTestContentAsync(element); + } + + try + { + editingElement.DataContext = item; + await UnitTestApp.Current.MainWindow.LoadTestContentAsync(editingElement); + Assert.AreEqual("Alice", editingElement.Text); + } + finally + { + await UnitTestApp.Current.MainWindow.UnloadTestContentAsync(editingElement); + } + + var cell = new TableViewCell { Content = editingElement }; + Assert.AreEqual("Alice", column.PrepareCellForEdit(cell, new RoutedEventArgs())); + } + + [UITestMethod] + public void TableViewTextColumn_PrepareCellForEdit_ReturnsNull_WhenContentIsNotTextBox() + { + var column = new TableViewTextColumn(); + var cell = new TableViewCell { Content = new TextBlock { Text = "not editing" } }; + + Assert.IsNull(column.PrepareCellForEdit(cell, new RoutedEventArgs())); + } +} diff --git a/tests/TableViewTimeColumnTests.cs b/tests/TableViewTimeColumnTests.cs new file mode 100644 index 00000000..1a6f4798 --- /dev/null +++ b/tests/TableViewTimeColumnTests.cs @@ -0,0 +1,68 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using System; +using Windows.Globalization.DateTimeFormatting; +using WinUI.TableView.Controls; +using WinUI.TableView.Helpers; + +namespace WinUI.TableView.Tests; + +[TestClass] +public class TableViewTimeColumnTests +{ + [UITestMethod] + public void TableViewTimeColumn_GeneratesEditingElementWithConfiguredProperties() + { + var item = new ColumnTestItem { AppointmentTime = new TimeOnly(9, 30) }; + var column = new TableViewTimeColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.AppointmentTime)) }, + ClockIdentifier = "24HourClock", + MinuteIncrement = 15 + }; + + var element = (TableViewTimePicker)column.GenerateEditingElement(new TableViewCell(), item); + var cell = new TableViewCell { Content = element }; + element.SelectedTime = item.AppointmentTime; + + Assert.AreEqual("24HourClock", element.ClockIdentifier); + Assert.AreEqual(15, element.MinuteIncrement); + Assert.AreEqual(TableViewLocalizedStrings.TimePickerPlaceholder, element.PlaceholderText); + Assert.AreEqual(typeof(TimeOnly), element.SourceType); + Assert.AreEqual(item.AppointmentTime, column.PrepareCellForEdit(cell, new RoutedEventArgs())); + Assert.AreEqual(DateTimeFormatter.LongTime.Clock, new TableViewTimeColumn().ClockIdentifier); + } + + [UITestMethod] + public void TableViewTimeColumn_GeneratesDisplayElement_WithFormatBindings() + { + var column = new TableViewTimeColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.AppointmentTime)) } + }; + + var element = (TextBlock)column.GenerateElement(new TableViewCell(), new ColumnTestItem()); + + Assert.IsNotNull(element.GetBindingExpression(DateTimeFormatHelper.ValueProperty)); + Assert.IsNotNull(element.GetBindingExpression(DateTimeFormatHelper.FormatProperty)); + Assert.AreEqual(new Thickness(12, 0, 12, 0), element.Margin); + } + + [UITestMethod] + public void TableViewTimeColumn_UsesCustomPlaceholderAndTimeSpanFallback() + { + var column = new TableViewTimeColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.UnknownTimeLikeValue)) }, + PlaceholderText = "Pick a time" + }; + + var element = (TableViewTimePicker)column.GenerateEditingElement(new TableViewCell(), new ColumnTestItem()); + + Assert.AreEqual("Pick a time", element.PlaceholderText); + Assert.AreEqual(typeof(TimeSpan), element.SourceType); + } +} diff --git a/tests/TableViewToggleSwitchColumnTests.cs b/tests/TableViewToggleSwitchColumnTests.cs new file mode 100644 index 00000000..876a4fc7 --- /dev/null +++ b/tests/TableViewToggleSwitchColumnTests.cs @@ -0,0 +1,54 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using System; + +namespace WinUI.TableView.Tests; + +[TestClass] +public class TableViewToggleSwitchColumnTests +{ + [UITestMethod] + public void TableViewToggleSwitchColumn_UsesSingleElementAndTracksReadOnlyState() + { + var tableView = new TableView { IsReadOnly = false }; + var column = new TableViewToggleSwitchColumn + { + Binding = new Binding { Path = new PropertyPath(nameof(ColumnTestItem.IsEnabled)) }, + OnContent = "On", + OffContent = "Off" + }; + column.SetOwningTableView(tableView); + + var toggleSwitch = (ToggleSwitch)column.GenerateElement(new TableViewCell(), new ColumnTestItem()); + var cell = new TableViewCell { Content = toggleSwitch }; + + Assert.IsTrue(column.UseSingleElement); + Assert.AreEqual("On", toggleSwitch.OnContent); + Assert.AreEqual("Off", toggleSwitch.OffContent); + Assert.IsTrue(toggleSwitch.IsHitTestVisible); + + tableView.IsReadOnly = true; + column.UpdateElementState(cell, null); + + Assert.IsFalse(toggleSwitch.IsHitTestVisible); + toggleSwitch.IsOn = true; + Assert.AreEqual(true, column.PrepareCellForEdit(cell, new RoutedEventArgs())); + Assert.ThrowsExactly(() => column.GenerateEditingElement(new TableViewCell(), null)); + } + + [UITestMethod] + public void TableViewToggleSwitchColumn_RespectsTableViewReadOnlyState() + { + var tableView = new TableView { IsReadOnly = true }; + var column = new TableViewToggleSwitchColumn(); + column.SetOwningTableView(tableView); + + var toggleSwitch = (ToggleSwitch)column.GenerateElement(new TableViewCell(), null); + + Assert.IsFalse(toggleSwitch.IsHitTestVisible); + Assert.AreEqual(new Thickness(12, 0, 12, 0), toggleSwitch.Margin); + } +}