diff --git a/samples/WinUI.TableView.SampleApp/MainPage.xaml.cs b/samples/WinUI.TableView.SampleApp/MainPage.xaml.cs index 5d0b5e8..f8debb5 100644 --- a/samples/WinUI.TableView.SampleApp/MainPage.xaml.cs +++ b/samples/WinUI.TableView.SampleApp/MainPage.xaml.cs @@ -115,6 +115,7 @@ private void OnNavigationSelectionChanged(NavigationView sender, NavigationViewS "Data Export" => typeof(ExportPage), "Large Dataset" => typeof(LargeDataPage), "Conditional Cell Styling" => typeof(ConditionalStylingPage), + "Column Sizing" => typeof(ColumnSizingPage), _ => typeof(BlankPage) }; diff --git a/samples/WinUI.TableView.SampleApp/Pages/ColumnSizingPage.xaml b/samples/WinUI.TableView.SampleApp/Pages/ColumnSizingPage.xaml new file mode 100644 index 0000000..48ee037 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Pages/ColumnSizingPage.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + +<tv:TableView ItemsSource="{Binding Items}" + ColumnAutoWidthMode="$(ColumnAutoWidthMode)"/> + + + + + + + + diff --git a/samples/WinUI.TableView.SampleApp/Pages/ColumnSizingPage.xaml.cs b/samples/WinUI.TableView.SampleApp/Pages/ColumnSizingPage.xaml.cs new file mode 100644 index 0000000..8c60f26 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Pages/ColumnSizingPage.xaml.cs @@ -0,0 +1,20 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace WinUI.TableView.SampleApp.Pages; + +public sealed partial class ColumnSizingPage : Page +{ + public ColumnSizingPage() + { + InitializeComponent(); + + } + + private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) + { + if (DataContext is not ExampleViewModel viewModel) return; + + viewModel.Items = [.. ExampleViewModel.ItemsList.Take(20)]; + } +} \ No newline at end of file diff --git a/src/Columns/TableViewColumn.cs b/src/Columns/TableViewColumn.cs index 572c71d..25a7342 100644 --- a/src/Columns/TableViewColumn.cs +++ b/src/Columns/TableViewColumn.cs @@ -237,6 +237,15 @@ public double ActualWidth set => SetValue(ActualWidthProperty, value); } + /// + /// Gets or sets the ColumnAutoWidthMode of the column. + /// + public TableViewColumnAutoWidthMode? ColumnAutoWidthMode + { + get => (TableViewColumnAutoWidthMode?)GetValue(ColumnAutoWidthModeProperty); + set => SetValue(ColumnAutoWidthModeProperty, value); + } + /// /// Gets or sets a value indicating whether the column can be resized. /// @@ -508,6 +517,17 @@ private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChan } } + /// + /// Handles changes to the ColumnAutoWidthMode property. + /// + private static void OnColumnAutoWidthModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is TableViewColumn column) + { + column.TableView?.RefreshColumnsAutoWidth([column]); + } + } + /// /// Handles changes to the CanFilter property. /// @@ -579,6 +599,11 @@ public string? SortMemberPath /// public static readonly DependencyProperty ActualWidthProperty = DependencyProperty.Register(nameof(ActualWidth), typeof(double), typeof(TableViewColumn), new PropertyMetadata(0d, OnPropertyChanged)); + /// + /// Identifies the ColumnAutoWidthMode dependency property. + /// + public static readonly DependencyProperty ColumnAutoWidthModeProperty = DependencyProperty.Register(nameof(ColumnAutoWidthMode), typeof(TableViewColumnAutoWidthMode?), typeof(TableViewColumn), new PropertyMetadata(null, OnColumnAutoWidthModeChanged)); + /// /// Identifies the CanResize dependency property. /// diff --git a/src/TableView.Properties.cs b/src/TableView.Properties.cs index 8b6fc07..761241f 100644 --- a/src/TableView.Properties.cs +++ b/src/TableView.Properties.cs @@ -221,6 +221,11 @@ public partial class TableView /// public static readonly DependencyProperty RowHeaderTemplateSelectorProperty = DependencyProperty.Register(nameof(RowHeaderTemplateSelector), typeof(DataTemplateSelector), typeof(TableView), new PropertyMetadata(null, OnRowHeaderTemplateChanged)); + /// + /// Identifies the ColumnAutoWidthMode dependency property. + /// + public static readonly DependencyProperty ColumnAutoWidthModeProperty = DependencyProperty.Register(nameof(ColumnAutoWidthMode), typeof(TableViewColumnAutoWidthMode), typeof(TableView), new PropertyMetadata(TableViewColumnAutoWidthMode.Both, OnColumnAutoWidthModeChanged)); + /// /// Identifies the FrozenColumnCount dependency property. /// @@ -792,6 +797,15 @@ public DataTemplateSelector? RowHeaderTemplateSelector set => SetValue(RowHeaderTemplateSelectorProperty, value); } + /// + /// Gets or sets the ColumnAutoWidthMode for all columns. + /// + public TableViewColumnAutoWidthMode ColumnAutoWidthMode + { + get => (TableViewColumnAutoWidthMode)GetValue(ColumnAutoWidthModeProperty); + set => SetValue(ColumnAutoWidthModeProperty, value); + } + /// /// Gets or sets the number of columns that stays in view on horizontal scroll. /// @@ -983,6 +997,17 @@ private static void OnCanFilterColumnsChanged(DependencyObject d, DependencyProp } } + /// + /// Handles changes to the ColumnAutoWidthMode property. + /// + private static void OnColumnAutoWidthModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is TableView tableView) + { + tableView.RefreshColumnsAutoWidth(); + } + } + /// /// Handles changes to the MinColumnWidth property. /// diff --git a/src/TableView.cs b/src/TableView.cs index 206d74e..1b50492 100644 --- a/src/TableView.cs +++ b/src/TableView.cs @@ -1867,6 +1867,38 @@ internal void EnsureAlternateRowColors() }); } + /// + /// Resets the auto-calculated widths of the specified columns and recalculates them. + /// + /// The columns to refresh. When null, all columns are refreshed. + internal void RefreshColumnsAutoWidth(IEnumerable? columns = null) + { + var targetColumns = (columns ?? Columns).ToHashSet(); + if (targetColumns.Count == 0) + { + return; + } + + foreach (var column in targetColumns) + { + column.DesiredWidth = 0d; + column.HeaderControl?.InvalidateMeasure(); + } + + foreach (var row in _rows) + { + foreach (var cell in row.Cells) + { + if (cell.Column is { } cellColumn && targetColumns.Contains(cellColumn)) + { + cell.InvalidateMeasure(); + } + } + } + + DispatcherQueue.TryEnqueue(() => _headerRow?.CalculateHeaderWidths()); + } + /// /// Ensures the column headers style is applied. /// diff --git a/src/TableViewCell.cs b/src/TableViewCell.cs index bed74cc..0a427b0 100644 --- a/src/TableViewCell.cs +++ b/src/TableViewCell.cs @@ -117,7 +117,7 @@ void OnContentLoaded(object sender, RoutedEventArgs e) /// protected override Size MeasureOverride(Size availableSize) { - if (Column is not null && Row is not null && _contentPresenter is not null && Content is FrameworkElement element) + if (TableView is not null && Column is not null && Row is not null && _contentPresenter is not null && Content is FrameworkElement element) { if (Column is TableViewTemplateColumn) { @@ -138,16 +138,20 @@ protected override Size MeasureOverride(Size availableSize) element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - var desiredWidth = element.DesiredSize.Width; - desiredWidth += Padding.Left; - desiredWidth += Padding.Right; - desiredWidth += BorderThickness.Left; - desiredWidth += BorderThickness.Right; - desiredWidth += _selectionBorder?.BorderThickness.Right ?? 0; - desiredWidth += _selectionBorder?.BorderThickness.Left ?? 0; - desiredWidth += _v_gridLine?.ActualWidth ?? 0d; - - Column.DesiredWidth = Math.Max(Column.DesiredWidth, desiredWidth); + var autoSizeMode = Column.ColumnAutoWidthMode ?? TableView.ColumnAutoWidthMode; + if (autoSizeMode is TableViewColumnAutoWidthMode.Cells or TableViewColumnAutoWidthMode.Both) + { + var desiredWidth = element.DesiredSize.Width; + desiredWidth += Padding.Left; + desiredWidth += Padding.Right; + desiredWidth += BorderThickness.Left; + desiredWidth += BorderThickness.Right; + desiredWidth += _selectionBorder?.BorderThickness.Right ?? 0; + desiredWidth += _selectionBorder?.BorderThickness.Left ?? 0; + desiredWidth += _v_gridLine?.ActualWidth ?? 0d; + + Column.DesiredWidth = Math.Max(Column.DesiredWidth, desiredWidth); + } #region TEMP_FIX_FOR_ISSUE https://github.com/microsoft/microsoft-ui-xaml/issues/9860 var contentWidth = Column.ActualWidth; diff --git a/src/TableViewColumnAutoWidthMode.cs b/src/TableViewColumnAutoWidthMode.cs new file mode 100644 index 0000000..662c6bf --- /dev/null +++ b/src/TableViewColumnAutoWidthMode.cs @@ -0,0 +1,22 @@ +namespace WinUI.TableView; + +/// +/// Specifies the behavior for automatic column width. +/// +public enum TableViewColumnAutoWidthMode +{ + /// + /// Column width is adjusted to both header and maximum cell width. + /// + Both, + + /// + /// Column width is adjusted to maximum cell width. + /// + Cells, + + /// + /// Column width is adjusted to header width. + /// + Header +} diff --git a/src/TableViewColumnHeader.cs b/src/TableViewColumnHeader.cs index 0302952..996657d 100644 --- a/src/TableViewColumnHeader.cs +++ b/src/TableViewColumnHeader.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Windows.Foundation; using Windows.System; using Windows.UI.Core; using WinUI.TableView.Collections; @@ -63,7 +64,10 @@ public TableViewColumnHeader() /// private void OnWidthChanged(DependencyObject sender, DependencyProperty dp) { - Column?.ActualWidth = Width; + if (!double.IsNaN(Width)) + { + Column?.ActualWidth = Width; + } } /// @@ -348,19 +352,11 @@ protected override void OnDoubleTapped(DoubleTappedRoutedEventArgs e) if (position.X <= 8 && _headerRow?.GetPreviousHeader(this) is { Column: { } } header) { - var width = Math.Clamp( - header.Column.DesiredWidth, - header.Column.MinWidth ?? _tableView.MinColumnWidth, - header.Column.MaxWidth ?? _tableView.MaxColumnWidth); - header.Column.Width = new GridLength(width, GridUnitType.Pixel); + header.Column.Width = GridLength.Auto; } else if (Column is not null) { - var width = Math.Clamp( - Column.DesiredWidth, - Column.MinWidth ?? _tableView.MinColumnWidth, - Column.MaxWidth ?? _tableView.MaxColumnWidth); - Column.Width = new GridLength(width, GridUnitType.Pixel); + Column.Width = GridLength.Auto; } } @@ -483,6 +479,22 @@ protected override void OnPointerReleased(PointerRoutedEventArgs e) _reorderStarted = false; } + /// + protected override Size MeasureOverride(Size availableSize) + { + if (Column is not null && _tableView is not null) + { + var autoWidthMode = Column.ColumnAutoWidthMode ?? _tableView.ColumnAutoWidthMode; + if (autoWidthMode is TableViewColumnAutoWidthMode.Header or TableViewColumnAutoWidthMode.Both) + { + var desiredHeaderSize = base.MeasureOverride(new Size(double.PositiveInfinity, double.PositiveInfinity)); + Column.DesiredWidth = Math.Max(Column.DesiredWidth, desiredHeaderSize.Width); + } + } + + return base.MeasureOverride(availableSize); + } + /// /// Ensures grid lines are applied. /// diff --git a/src/TableViewHeaderRow.cs b/src/TableViewHeaderRow.cs index 6aed1b7..d648b5a 100644 --- a/src/TableViewHeaderRow.cs +++ b/src/TableViewHeaderRow.cs @@ -282,19 +282,10 @@ internal void CalculateHeaderWidths() var autoColumns = allColumns.Where(x => x.Width.IsAuto).ToList(); var absoluteColumns = allColumns.Where(x => x.Width.IsAbsolute).ToList(); - var height = ActualHeight; var availableWidth = TableView.ActualWidth - 32; var starUnitWeight = starColumns.Select(x => x.Width.Value).Sum(); - var fixedWidth = autoColumns.Select(x => - { - if (x.HeaderControl is { } header) - { - header.Measure(new Size(double.PositiveInfinity, height)); - return Math.Max(x.DesiredWidth, header.DesiredSize.Width); - } - return x.DesiredWidth; - }).Sum(); + var fixedWidth = autoColumns.Select(GetColumnDesiredWidth).Sum(); fixedWidth += absoluteColumns.Select(x => x.ActualWidth).Sum(); availableWidth -= fixedWidth; @@ -339,7 +330,7 @@ internal void CalculateHeaderWidths() var width = column.Width.IsStar ? starUnitWidth * column.Width.Value : column.Width.IsAbsolute ? column.Width.Value - : Math.Max(header.DesiredSize.Width, column.DesiredWidth); + : GetColumnDesiredWidth(column); var minWidth = column.MinWidth ?? TableView.MinColumnWidth; var maxWidth = column.MaxWidth ?? TableView.MaxColumnWidth; @@ -347,7 +338,6 @@ internal void CalculateHeaderWidths() width = width < minWidth ? minWidth : width; width = width > maxWidth ? maxWidth : width; header.Width = width; - header.MaxWidth = width; DispatcherQueue.TryEnqueue(() => header.Measure( @@ -362,6 +352,24 @@ internal void CalculateHeaderWidths() } } + /// + /// Gets the desired width of a column based on its header and cells. + /// + private double GetColumnDesiredWidth(TableViewColumn column) + { + var autoWidthMode = column.ColumnAutoWidthMode ?? TableView?.ColumnAutoWidthMode; + var width = column.DesiredWidth; + + if (column.HeaderControl is { } header && autoWidthMode is not TableViewColumnAutoWidthMode.Cells) + { + header.Width = double.NaN; + header.Measure(new Size(double.PositiveInfinity, ActualHeight)); + width = Math.Max(width, header.DesiredSize.Width); + } + + return width; + } + /// /// Handles the selection changed event for the TableView. ///