From f8c40c6585303785389cfe3d4c2a5b2358a98844 Mon Sep 17 00:00:00 2001 From: k-hara Date: Tue, 17 Feb 2026 22:24:59 +0900 Subject: [PATCH 01/12] Add XML comments --- components/DataTable/src/DataTable/DataColumn.cs | 7 +++++++ components/DataTable/src/DataTable/DataRow.cs | 10 +++++++++- components/DataTable/src/DataTable/DataTable.cs | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/components/DataTable/src/DataTable/DataColumn.cs b/components/DataTable/src/DataTable/DataColumn.cs index 6f969b15e..303a200f3 100644 --- a/components/DataTable/src/DataTable/DataColumn.cs +++ b/components/DataTable/src/DataTable/DataColumn.cs @@ -4,6 +4,9 @@ namespace CommunityToolkit.WinUI.Controls; +/// +/// Column of . +/// [TemplatePart(Name = nameof(PART_ColumnSizer), Type = typeof(ContentSizer))] public partial class DataColumn : ContentControl { @@ -62,11 +65,15 @@ private static void DesiredWidth_PropertyChanged(DependencyObject d, DependencyP } } + /// + /// Constructor. + /// public DataColumn() { this.DefaultStyleKey = typeof(DataColumn); } + /// protected override void OnApplyTemplate() { if (PART_ColumnSizer != null) diff --git a/components/DataTable/src/DataTable/DataRow.cs b/components/DataTable/src/DataTable/DataRow.cs index 6546150ae..9e38176f0 100644 --- a/components/DataTable/src/DataTable/DataRow.cs +++ b/components/DataTable/src/DataTable/DataRow.cs @@ -6,6 +6,9 @@ namespace CommunityToolkit.WinUI.Controls; +/// +/// Row item of . +/// public partial class DataRow : Panel { // TODO: Create our own helper class here for the Header as well vs. straight-Grid. @@ -16,6 +19,9 @@ public partial class DataRow : Panel private bool _isTreeView; private double _treePadding; + /// + /// Constructor. + /// public DataRow() { Unloaded += this.DataRow_Unloaded; @@ -70,6 +76,7 @@ private void DataRow_Unloaded(object sender, RoutedEventArgs e) return panel; } + /// protected override Size MeasureOverride(Size availableSize) { // We should probably only have to do this once ever? @@ -168,6 +175,7 @@ protected override Size MeasureOverride(Size availableSize) return new(_parentPanel?.DesiredSize.Width ?? availableSize.Width, maxHeight); } + /// protected override Size ArrangeOverride(Size finalSize) { int column = 0; @@ -186,7 +194,7 @@ protected override Size ArrangeOverride(Size finalSize) if (_parentPanel is Grid grid && column < grid.ColumnDefinitions.Count) { - width = grid.ColumnDefinitions[column++].ActualWidth; + width = grid.ColumnDefinitions[column++].ActualWidth; } // TODO: Need to check Column visibility here as well... else if (_parentPanel is DataTable table && diff --git a/components/DataTable/src/DataTable/DataTable.cs b/components/DataTable/src/DataTable/DataTable.cs index 7b78b2beb..cb362c761 100644 --- a/components/DataTable/src/DataTable/DataTable.cs +++ b/components/DataTable/src/DataTable/DataTable.cs @@ -44,6 +44,7 @@ public double ColumnSpacing public static readonly DependencyProperty ColumnSpacingProperty = DependencyProperty.Register(nameof(ColumnSpacing), typeof(double), typeof(DataTable), new PropertyMetadata(0d)); + /// protected override Size MeasureOverride(Size availableSize) { double fixedWidth = 0; @@ -107,6 +108,7 @@ protected override Size MeasureOverride(Size availableSize) return new Size(availableSize.Width, maxHeight); } + /// protected override Size ArrangeOverride(Size finalSize) { double fixedWidth = 0; From 0a579ab69a7be5941e1aae2fd453075c1c849ccf Mon Sep 17 00:00:00 2001 From: k-hara Date: Tue, 17 Feb 2026 22:25:20 +0900 Subject: [PATCH 02/12] Wrap some code and comment lines --- .../DataTable/src/DataTable/DataColumn.cs | 21 ++++++++++++------- components/DataTable/src/DataTable/DataRow.cs | 19 ++++++++++------- .../DataTable/src/DataTable/DataTable.cs | 6 +++--- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/components/DataTable/src/DataTable/DataColumn.cs b/components/DataTable/src/DataTable/DataColumn.cs index 303a200f3..5ff6ae06b 100644 --- a/components/DataTable/src/DataTable/DataColumn.cs +++ b/components/DataTable/src/DataTable/DataColumn.cs @@ -17,12 +17,14 @@ public partial class DataColumn : ContentControl private WeakReference? _parent; /// - /// Gets or sets the width of the largest child contained within the visible s of the . + /// Gets or sets the width of the largest child contained within the visible s + /// of the . /// internal double MaxChildDesiredWidth { get; set; } /// - /// Gets or sets the internal copy of the property to be used in calculations, this gets manipulated in Auto-Size mode. + /// Gets or sets the internal copy of the property to be used in calculations, + /// this gets manipulated in Auto-Size mode. /// internal GridLength CurrentWidth { get; private set; } @@ -39,10 +41,13 @@ public bool CanResize /// Identifies the property. /// public static readonly DependencyProperty CanResizeProperty = - DependencyProperty.Register("CanResize", typeof(bool), typeof(DataColumn), new PropertyMetadata(false)); + DependencyProperty.Register( + nameof(CanResize), typeof(bool), typeof(DataColumn), + new PropertyMetadata(false)); /// - /// Gets or sets the desired width of the column upon initialization. Defaults to a of 1 . + /// Gets or sets the desired width of the column upon initialization. Defaults to a + /// of 1 . /// public GridLength DesiredWidth { @@ -54,7 +59,9 @@ public GridLength DesiredWidth /// Identifies the property. /// public static readonly DependencyProperty DesiredWidthProperty = - DependencyProperty.Register(nameof(DesiredWidth), typeof(GridLength), typeof(DataColumn), new PropertyMetadata(GridLength.Auto, DesiredWidth_PropertyChanged)); + DependencyProperty.Register( + nameof(DesiredWidth), typeof(GridLength), typeof(DataColumn), + new PropertyMetadata(GridLength.Auto, DesiredWidth_PropertyChanged)); private static void DesiredWidth_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { @@ -118,8 +125,8 @@ private void ColumnResizedByUserSizer() CurrentWidth = new(this.ActualWidth); // Notify the rest of the table to update - if (_parent?.TryGetTarget(out DataTable? parent) == true - && parent != null) + if (_parent?.TryGetTarget(out DataTable? parent) == true && + parent != null) { parent.ColumnResized(); } diff --git a/components/DataTable/src/DataTable/DataRow.cs b/components/DataTable/src/DataTable/DataRow.cs index 9e38176f0..ffcb915b6 100644 --- a/components/DataTable/src/DataTable/DataRow.cs +++ b/components/DataTable/src/DataTable/DataRow.cs @@ -42,7 +42,8 @@ private void DataRow_Unloaded(object sender, RoutedEventArgs e) // Positive: don't have to restart climbing the Visual Tree if we don't find ItemsPresenter... ////var parent = this.FindAscendant(static (element) => element is ItemsPresenter or Grid); - // TODO: Investigate what a scenario with an ItemsRepeater would look like (with a StackLayout, but using DataRow as the item's panel inside) + // TODO: Investigate what a scenario with an ItemsRepeater would look like (with a StackLayout, but + // using DataRow as the item's panel inside) Panel? panel = null; // 1a. Get parent ItemsPresenter to find header @@ -93,8 +94,8 @@ protected override Size MeasureOverride(Size availableSize) return new Size(availableSize.Width, Children[0].DesiredSize.Height); } // Handle DataTable Parent - else if (_parentTable != null - && _parentTable.Children.Count == Children.Count) + else if (_parentTable != null && + _parentTable.Children.Count == Children.Count) { // TODO: Need to check visibility // Measure all children since we need to determine the row's height at minimum @@ -115,7 +116,8 @@ protected override Size MeasureOverride(Size availableSize) if (parentContainer != null) { _treePadding = parentContainer.Padding.Left; - // We assume our 'DataRow' is in the last child slot of the Grid, need to know how large the other columns are. + // We assume our 'DataRow' is in the last child slot of the Grid, need to know + // how large the other columns are. for (int j = 0; j < parentContainer.Children.Count - 1; j++) { // TODO: We may need to get the actual size here later in Arrange? @@ -148,9 +150,9 @@ protected override Size MeasureOverride(Size availableSize) } } // Fallback for Grid Hybrid scenario... - else if (_parentPanel is Grid grid - && _parentPanel.Children.Count == Children.Count - && grid.ColumnDefinitions.Count == Children.Count) + else if (_parentPanel is Grid grid && + _parentPanel.Children.Count == Children.Count && + grid.ColumnDefinitions.Count == Children.Count) { // TODO: Need to check visibility // Measure all children since we need to determine the row's height at minimum @@ -204,7 +206,8 @@ protected override Size ArrangeOverride(Size finalSize) width = (table.Children[column++] as DataColumn)?.ActualWidth ?? 0; } - // Note: For Auto, since we measured our children and bubbled that up to the DataTable layout, then the DataColumn size we grab above should account for the largest of our children. + // Note: For Auto, since we measured our children and bubbled that up to the DataTable layout, + // then the DataColumn size we grab above should account for the largest of our children. if (i == 0) { child.Arrange(new Rect(x, 0, width, finalSize.Height)); diff --git a/components/DataTable/src/DataTable/DataTable.cs b/components/DataTable/src/DataTable/DataTable.cs index cb362c761..72c2cd3d1 100644 --- a/components/DataTable/src/DataTable/DataTable.cs +++ b/components/DataTable/src/DataTable/DataTable.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Runtime.CompilerServices; - namespace CommunityToolkit.WinUI.Controls; /// @@ -42,7 +40,9 @@ public double ColumnSpacing /// Gets the . /// public static readonly DependencyProperty ColumnSpacingProperty = - DependencyProperty.Register(nameof(ColumnSpacing), typeof(double), typeof(DataTable), new PropertyMetadata(0d)); + DependencyProperty.Register( + nameof(ColumnSpacing), typeof(double), typeof(DataTable), + new PropertyMetadata(0d)); /// protected override Size MeasureOverride(Size availableSize) From bdd83b66beec2b298bb62d6f439cf06fe20c59f8 Mon Sep 17 00:00:00 2001 From: k-hara Date: Tue, 17 Feb 2026 23:22:51 +0900 Subject: [PATCH 03/12] Remove support for case where the DataTable is descendant of a Grid or DataTable The removed logic would have been useless or more harmful, because of the reasons: 1) DataTable control is designed to represent 'header' of a table, not for containing the list of DataRows. 2) Grid control is often used to lay out other controls rather than constructing a table, so there was possibilities that completely unrelated Grids would have been caught as a parent. --- components/DataTable/src/DataTable/DataRow.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/DataTable/src/DataTable/DataRow.cs b/components/DataTable/src/DataTable/DataRow.cs index ffcb915b6..f35c4c379 100644 --- a/components/DataTable/src/DataTable/DataRow.cs +++ b/components/DataTable/src/DataTable/DataRow.cs @@ -64,9 +64,6 @@ private void DataRow_Unloaded(object sender, RoutedEventArgs e) _isTreeView = itemsPresenter.FindAscendant() is TreeView; } - // 1b. If we can't find the ItemsPresenter, then we reach up outside to find the next thing we could use as a parent - panel ??= this.FindAscendant(static (element) => element is Grid or DataTable); - // Cache actual datatable reference if (panel is DataTable table) { From 1e5c15b7ad5034740d8e24ab96c986ac5ef3319c Mon Sep 17 00:00:00 2001 From: k-hara Date: Thu, 19 Feb 2026 16:56:24 +0900 Subject: [PATCH 04/12] Remove support for the 'Hybrid-case' that paring Grid and DataRows That case doesn't work well for the Width="Auto" column. I'd like to make DataTable control simple rather than leave the pitfall. --- components/DataTable/samples/DataTable.md | 8 --- .../samples/DataTableHybridSample.xaml | 50 ---------------- .../samples/DataTableHybridSample.xaml.cs | 48 --------------- components/DataTable/src/DataTable/DataRow.cs | 59 ++++--------------- 4 files changed, 12 insertions(+), 153 deletions(-) delete mode 100644 components/DataTable/samples/DataTableHybridSample.xaml delete mode 100644 components/DataTable/samples/DataTableHybridSample.xaml.cs diff --git a/components/DataTable/samples/DataTable.md b/components/DataTable/samples/DataTable.md index c9cdf1a98..38ddc422c 100644 --- a/components/DataTable/samples/DataTable.md +++ b/components/DataTable/samples/DataTable.md @@ -36,14 +36,6 @@ can be made to look like a table of data: There are limitations here with having fixed column sizes that can be difficult to align. Their definitions are also duplicated, and every item is recreating this layout and duplicating it within the Visual Tree. -## DataRow Hybrid Setup - -As a first step, moving to **DataTable** is easy, just replace the `Grid` in your `ItemsTemplate` with the `DataRow` panel -and remove the Column attributes from your controls. `DataRow` automatically will lay each subsequent control in the next column -for you automatically: - -> [!Sample DataTableHybridSample] - ## DataTable Setup The `DataTable` setup provides an easier way to define and manage your columns within your header for this coordinated effort diff --git a/components/DataTable/samples/DataTableHybridSample.xaml b/components/DataTable/samples/DataTableHybridSample.xaml deleted file mode 100644 index b6eaf6023..000000000 --- a/components/DataTable/samples/DataTableHybridSample.xaml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/components/DataTable/samples/DataTableHybridSample.xaml.cs b/components/DataTable/samples/DataTableHybridSample.xaml.cs deleted file mode 100644 index b47a44fed..000000000 --- a/components/DataTable/samples/DataTableHybridSample.xaml.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using CommunityToolkit.WinUI.Controls; - -namespace DataTableExperiment.Samples; - -[ToolkitSample(id: nameof(DataTableHybridSample), "Hybrid DataTable Example", description: $"A sample for showing how to create and use a {nameof(DataRow)} control alongside an existing traditional setup with Grid.")] -public sealed partial class DataTableHybridSample : Page -{ - public ObservableCollection InventoryItems { get; set; } = new() - { - new() - { - Id = 1002, - Name = "Hydra", - Description = "Multiple Launch Rocket System-2 Hydra", - Quantity = 1, - }, - new() - { - Id = 3456, - Name = "MA40 AR", - Description = "Regular assault rifle - updated version of MA5B or MA37 AR", - Quantity = 4, - }, - new() - { - Id = 5698, - Name = "Needler", - Description = "Alien weapon well-known for its iconic design with pink crystals", - Quantity = 2, - }, - new() - { - Id = 7043, - Name = "Ravager", - Description = "An incendiary plasma launcher", - Quantity = 1, - }, - }; - - public DataTableHybridSample() - { - this.InitializeComponent(); - } -} diff --git a/components/DataTable/src/DataTable/DataRow.cs b/components/DataTable/src/DataTable/DataRow.cs index f35c4c379..b4e3aa721 100644 --- a/components/DataTable/src/DataTable/DataRow.cs +++ b/components/DataTable/src/DataTable/DataRow.cs @@ -13,7 +13,6 @@ public partial class DataRow : Panel { // TODO: Create our own helper class here for the Header as well vs. straight-Grid. // TODO: WeakReference? - private Panel? _parentPanel; private DataTable? _parentTable; private bool _isTreeView; @@ -32,10 +31,9 @@ private void DataRow_Unloaded(object sender, RoutedEventArgs e) // Remove our references on unloaded _parentTable?.Rows.Remove(this); _parentTable = null; - _parentPanel = null; } - private Panel? InitializeParentHeaderConnection() + private DataTable? InitializeParentHeaderConnection() { // TODO: Think about this expression instead... // Drawback: Can't have Grid between table and header @@ -49,15 +47,9 @@ private void DataRow_Unloaded(object sender, RoutedEventArgs e) // 1a. Get parent ItemsPresenter to find header if (this.FindAscendant() is ItemsPresenter itemsPresenter) { - // 2. Quickly check if the header is just what we're looking for. - if (itemsPresenter.Header is Grid or DataTable) + if (itemsPresenter.Header is DependencyObject header) { - panel = itemsPresenter.Header as Panel; - } - else - { - // 3. Otherwise, try and find the inner thing we want. - panel = itemsPresenter.FindDescendant(static (element) => element is Grid or DataTable); + panel = header.FindDescendantOrSelf(); } // Check if we're in a TreeView @@ -71,28 +63,27 @@ private void DataRow_Unloaded(object sender, RoutedEventArgs e) _parentTable.Rows.Add(this); // Add us to the row list. } - return panel; + return _parentTable; } /// protected override Size MeasureOverride(Size availableSize) { // We should probably only have to do this once ever? - _parentPanel ??= InitializeParentHeaderConnection(); + _parentTable ??= InitializeParentHeaderConnection(); double maxHeight = 0; if (Children.Count > 0) { // If we don't have a grid, just measure first child to get row height and take available space - if (_parentPanel is null) + if (_parentTable is null) { Children[0].Measure(availableSize); return new Size(availableSize.Width, Children[0].DesiredSize.Height); } // Handle DataTable Parent - else if (_parentTable != null && - _parentTable.Children.Count == Children.Count) + else if (_parentTable.Children.Count == Children.Count) { // TODO: Need to check visibility // Measure all children since we need to determine the row's height at minimum @@ -146,32 +137,12 @@ protected override Size MeasureOverride(Size availableSize) maxHeight = Math.Max(maxHeight, Children[i].DesiredSize.Height); } } - // Fallback for Grid Hybrid scenario... - else if (_parentPanel is Grid grid && - _parentPanel.Children.Count == Children.Count && - grid.ColumnDefinitions.Count == Children.Count) - { - // TODO: Need to check visibility - // Measure all children since we need to determine the row's height at minimum - for (int i = 0; i < Children.Count; i++) - { - if (grid.ColumnDefinitions[i].Width.GridUnitType == GridUnitType.Pixel) - { - Children[i].Measure(new(grid.ColumnDefinitions[i].Width.Value, availableSize.Height)); - } - else - { - Children[i].Measure(availableSize); - } - maxHeight = Math.Max(maxHeight, Children[i].DesiredSize.Height); - } - } // TODO: What do we want to do if there's unequal children in the DataTable vs. DataRow? } // Otherwise, return our parent's size as the desired size. - return new(_parentPanel?.DesiredSize.Width ?? availableSize.Width, maxHeight); + return new(_parentTable?.DesiredSize.Width ?? availableSize.Width, maxHeight); } /// @@ -181,26 +152,20 @@ protected override Size ArrangeOverride(Size finalSize) double x = 0; // Try and grab Column Spacing from DataTable, if not a parent Grid, if not 0. - double spacing = _parentTable?.ColumnSpacing ?? (_parentPanel as Grid)?.ColumnSpacing ?? 0; + double spacing = _parentTable?.ColumnSpacing ?? 0; double width = 0; - if (_parentPanel != null) + if (_parentTable != null) { int i = 0; foreach (UIElement child in Children.Where(static e => e.Visibility == Visibility.Visible)) { - if (_parentPanel is Grid grid && - column < grid.ColumnDefinitions.Count) - { - width = grid.ColumnDefinitions[column++].ActualWidth; - } // TODO: Need to check Column visibility here as well... - else if (_parentPanel is DataTable table && - column < table.Children.Count) + if (column < _parentTable.Children.Count) { // TODO: This is messy... - width = (table.Children[column++] as DataColumn)?.ActualWidth ?? 0; + width = (_parentTable.Children[column++] as DataColumn)?.ActualWidth ?? 0; } // Note: For Auto, since we measured our children and bubbled that up to the DataTable layout, From ab9d62f697b8a424a7971af9832ee284e3dc4d02 Mon Sep 17 00:00:00 2001 From: k-hara Date: Thu, 19 Feb 2026 17:11:40 +0900 Subject: [PATCH 05/12] Reduce DataColumn.xaml indent --- .../DataTable/src/DataTable/DataColumn.xaml | 78 +++++++++---------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/components/DataTable/src/DataTable/DataColumn.xaml b/components/DataTable/src/DataTable/DataColumn.xaml index f88fedfe8..1eb69e9d5 100644 --- a/components/DataTable/src/DataTable/DataColumn.xaml +++ b/components/DataTable/src/DataTable/DataColumn.xaml @@ -12,45 +12,43 @@ From a7b6b5bdad249fedf5c9b200b1e22cb31dbffb30 Mon Sep 17 00:00:00 2001 From: k-hara Date: Tue, 17 Feb 2026 23:37:23 +0900 Subject: [PATCH 06/12] Prevent to focus DataColumn itself by default From the design concept, DataColumn control simply provides a space for the header content, so it should not interact with keyboard operations by default. If something requested, the DataTable user could be place any controls like a Button there. --- components/DataTable/src/DataTable/DataColumn.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/components/DataTable/src/DataTable/DataColumn.xaml b/components/DataTable/src/DataTable/DataColumn.xaml index 1eb69e9d5..dbe58fb44 100644 --- a/components/DataTable/src/DataTable/DataColumn.xaml +++ b/components/DataTable/src/DataTable/DataColumn.xaml @@ -17,6 +17,7 @@ + From c92d5d1d8824180a8cbe3042cedde4e24cf4b243 Mon Sep 17 00:00:00 2001 From: k-hara Date: Wed, 18 Feb 2026 00:06:26 +0900 Subject: [PATCH 07/12] DataRow lay out children like a horizontal StackPanel if DataTable is not present Each rows lay out their columns independent, because there's no main controller to remember the column widths. --- components/DataTable/src/DataTable/DataRow.cs | 59 ++++++++++++++----- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/components/DataTable/src/DataTable/DataRow.cs b/components/DataTable/src/DataTable/DataRow.cs index b4e3aa721..80a07daae 100644 --- a/components/DataTable/src/DataTable/DataRow.cs +++ b/components/DataTable/src/DataTable/DataRow.cs @@ -74,16 +74,29 @@ protected override Size MeasureOverride(Size availableSize) double maxHeight = 0; - if (Children.Count > 0) + // If we don't have a DataTable, just layout children like a horizontal StackPanel. + if (_parentTable is null) { - // If we don't have a grid, just measure first child to get row height and take available space - if (_parentTable is null) + double totalWidth = 0; + + for (int i = 0; i < Children.Count; i++) { - Children[0].Measure(availableSize); - return new Size(availableSize.Width, Children[0].DesiredSize.Height); + var child = Children[i]; + if (child?.Visibility != Visibility.Visible) + continue; + + child.Measure(availableSize); + + totalWidth += child.DesiredSize.Width; + maxHeight = Math.Max(maxHeight, child.DesiredSize.Height); } - // Handle DataTable Parent - else if (_parentTable.Children.Count == Children.Count) + + return new Size(totalWidth, maxHeight); + } + // Handle DataTable Parent + else + { + if (_parentTable.Children.Count == Children.Count) { // TODO: Need to check visibility // Measure all children since we need to determine the row's height at minimum @@ -148,16 +161,34 @@ protected override Size MeasureOverride(Size availableSize) /// protected override Size ArrangeOverride(Size finalSize) { - int column = 0; - double x = 0; + // If we don't have DataTable, just layout children like a horizontal StackPanel. + if (_parentTable is null) + { + double x = 0; - // Try and grab Column Spacing from DataTable, if not a parent Grid, if not 0. - double spacing = _parentTable?.ColumnSpacing ?? 0; + for (int i = 0; i < Children.Count; i++) + { + var child = Children[i]; + if (child?.Visibility != Visibility.Visible) + continue; + + double width = child.DesiredSize.Width; - double width = 0; + child.Arrange(new Rect(x, 0, width, finalSize.Height)); + + x += width; + } - if (_parentTable != null) + return new Size(x, finalSize.Height); + } + // Handle DataTable Parent + else { + int column = 0; + double x = 0; + double spacing = _parentTable.ColumnSpacing; + double width = 0; + int i = 0; foreach (UIElement child in Children.Where(static e => e.Visibility == Visibility.Visible)) { @@ -186,7 +217,5 @@ protected override Size ArrangeOverride(Size finalSize) return new Size(x - spacing, finalSize.Height); } - - return finalSize; } } From 98d77cb15a2c5df642d8c6ee1b455c1ea147cd10 Mon Sep 17 00:00:00 2001 From: k-hara Date: Thu, 19 Feb 2026 17:01:55 +0900 Subject: [PATCH 08/12] Add internal methods in DataColumn --- components/DataTable/src/DataTable/DataColumn.cs | 14 +++++++++----- components/DataTable/src/DataTable/DataTable.cs | 16 ++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/components/DataTable/src/DataTable/DataColumn.cs b/components/DataTable/src/DataTable/DataColumn.cs index 5ff6ae06b..9ba925665 100644 --- a/components/DataTable/src/DataTable/DataColumn.cs +++ b/components/DataTable/src/DataTable/DataColumn.cs @@ -16,6 +16,8 @@ public partial class DataColumn : ContentControl private WeakReference? _parent; + internal DataTable? DataTable => _parent?.TryGetTarget(out DataTable? parent) == true ? parent : null; + /// /// Gets or sets the width of the largest child contained within the visible s /// of the . @@ -28,6 +30,12 @@ public partial class DataColumn : ContentControl /// internal GridLength CurrentWidth { get; private set; } + internal bool IsAbsolute => CurrentWidth.IsAbsolute; + + internal bool IsAuto => CurrentWidth.IsAuto; + + internal bool IsStar => CurrentWidth.IsStar; + /// /// Gets or sets whether the column can be resized by the user. /// @@ -125,10 +133,6 @@ private void ColumnResizedByUserSizer() CurrentWidth = new(this.ActualWidth); // Notify the rest of the table to update - if (_parent?.TryGetTarget(out DataTable? parent) == true && - parent != null) - { - parent.ColumnResized(); - } + DataTable?.ColumnResized(); } } diff --git a/components/DataTable/src/DataTable/DataTable.cs b/components/DataTable/src/DataTable/DataTable.cs index 72c2cd3d1..105cccb29 100644 --- a/components/DataTable/src/DataTable/DataTable.cs +++ b/components/DataTable/src/DataTable/DataTable.cs @@ -58,11 +58,11 @@ protected override Size MeasureOverride(Size availableSize) // We only need to measure elements that are visible foreach (DataColumn column in elements) { - if (column.CurrentWidth.IsStar) + if (column.IsStar) { proportionalUnits += column.DesiredWidth.Value; } - else if (column.CurrentWidth.IsAbsolute) + else if (column.IsAbsolute) { fixedWidth += column.DesiredWidth.Value; } @@ -76,11 +76,11 @@ protected override Size MeasureOverride(Size availableSize) foreach (DataColumn column in elements) { - if (column.CurrentWidth.IsStar) + if (column.IsStar) { column.Measure(new Size(proportionalAmount * column.CurrentWidth.Value, availableSize.Height)); } - else if (column.CurrentWidth.IsAbsolute) + else if (column.IsAbsolute) { column.Measure(new Size(column.CurrentWidth.Value, availableSize.Height)); } @@ -120,11 +120,11 @@ protected override Size ArrangeOverride(Size finalSize) // We only need to measure elements that are visible foreach (DataColumn column in elements) { - if (column.CurrentWidth.IsStar) + if (column.IsStar) { proportionalUnits += column.CurrentWidth.Value; } - else if (column.CurrentWidth.IsAbsolute) + else if (column.IsAbsolute) { fixedWidth += column.CurrentWidth.Value; } @@ -143,12 +143,12 @@ protected override Size ArrangeOverride(Size finalSize) foreach (DataColumn column in elements) { - if (column.CurrentWidth.IsStar) + if (column.IsStar) { width = proportionalAmount * column.CurrentWidth.Value; column.Arrange(new Rect(x, 0, width, finalSize.Height)); } - else if (column.CurrentWidth.IsAbsolute) + else if (column.IsAbsolute) { width = column.CurrentWidth.Value; column.Arrange(new Rect(x, 0, width, finalSize.Height)); From e7ac3f1ee39ca00c782b5f452952b31cf9f73493 Mon Sep 17 00:00:00 2001 From: k-hara Date: Thu, 19 Feb 2026 17:43:28 +0900 Subject: [PATCH 09/12] Consider column visibility, and unequal counts case of columns and DataRow.Children --- components/DataTable/src/DataTable/DataRow.cs | 127 +++++++++--------- 1 file changed, 67 insertions(+), 60 deletions(-) diff --git a/components/DataTable/src/DataTable/DataRow.cs b/components/DataTable/src/DataTable/DataRow.cs index 80a07daae..118cb3160 100644 --- a/components/DataTable/src/DataTable/DataRow.cs +++ b/components/DataTable/src/DataTable/DataRow.cs @@ -96,66 +96,67 @@ protected override Size MeasureOverride(Size availableSize) // Handle DataTable Parent else { - if (_parentTable.Children.Count == Children.Count) + int maxChildCount = Math.Min(_parentTable.Children.Count, Children.Count); + + // Measure all children which have corresponding visible DataColumns. + for (int i = 0; i < maxChildCount; i++) { - // TODO: Need to check visibility - // Measure all children since we need to determine the row's height at minimum - for (int i = 0; i < Children.Count; i++) + var child = Children[i]; + var column = _parentTable.Children[i] as DataColumn; + if (column?.Visibility != Visibility.Visible) + continue; + + if (column.IsAuto) { - if (_parentTable.Children[i] is DataColumn { CurrentWidth.GridUnitType: GridUnitType.Auto } col) - { - Children[i].Measure(availableSize); + child.Measure(availableSize); - // For TreeView in the first column, we want the header to expand to encompass - // the maximum indentation of the tree. - double padding = 0; - //// TODO: We only want/need to do this once? We may want to do if we're not an Auto column too...? - if (i == 0 && _isTreeView) + // For TreeView in the first column, we want the header to expand to encompass + // the maximum indentation of the tree. + double padding = 0; + //// TODO: We only want/need to do this once? We may want to do if we're not an Auto column too...? + if (i == 0 && _isTreeView) + { + // Get our containing grid from TreeViewItem, start with our indented padding + var parentContainer = this.FindAscendant("MultiSelectGrid") as Grid; + if (parentContainer != null) { - // Get our containing grid from TreeViewItem, start with our indented padding - var parentContainer = this.FindAscendant("MultiSelectGrid") as Grid; - if (parentContainer != null) + _treePadding = parentContainer.Padding.Left; + // We assume our 'DataRow' is in the last child slot of the Grid, need to know + // how large the other columns are. + for (int j = 0; j < parentContainer.Children.Count - 1; j++) { - _treePadding = parentContainer.Padding.Left; - // We assume our 'DataRow' is in the last child slot of the Grid, need to know - // how large the other columns are. - for (int j = 0; j < parentContainer.Children.Count - 1; j++) - { - // TODO: We may need to get the actual size here later in Arrange? - _treePadding += parentContainer.Children[j].DesiredSize.Width; - } + // TODO: We may need to get the actual size here later in Arrange? + _treePadding += parentContainer.Children[j].DesiredSize.Width; } - padding = _treePadding; - } - - // TODO: Do we want this to ever shrink back? - var prev = col.MaxChildDesiredWidth; - col.MaxChildDesiredWidth = Math.Max(col.MaxChildDesiredWidth, Children[i].DesiredSize.Width + padding); - if (col.MaxChildDesiredWidth != prev) - { - // If our measure has changed, then we have to invalidate the arrange of the DataTable - _parentTable.ColumnResized(); } - - } - else if (_parentTable.Children[i] is DataColumn { CurrentWidth.GridUnitType: GridUnitType.Pixel } pixel) - { - Children[i].Measure(new(pixel.DesiredWidth.Value, availableSize.Height)); + padding = _treePadding; } - else + + // TODO: Do we want this to ever shrink back? + var prev = column.MaxChildDesiredWidth; + column.MaxChildDesiredWidth = Math.Max(column.MaxChildDesiredWidth, child.DesiredSize.Width + padding); + if (column.MaxChildDesiredWidth != prev) { - Children[i].Measure(availableSize); + // If our measure has changed, then we have to invalidate the arrange of the DataTable + _parentTable.ColumnResized(); } - maxHeight = Math.Max(maxHeight, Children[i].DesiredSize.Height); } + else if (column.IsAbsolute) + { + child.Measure(new(column.DesiredWidth.Value, availableSize.Height)); + } + else + { + child.Measure(availableSize); + } + + maxHeight = Math.Max(maxHeight, child.DesiredSize.Height); } - // TODO: What do we want to do if there's unequal children in the DataTable vs. DataRow? + // Return our parent's size as the desired size. + return new Size(_parentTable.DesiredSize.Width, maxHeight); } - - // Otherwise, return our parent's size as the desired size. - return new(_parentTable?.DesiredSize.Width ?? availableSize.Width, maxHeight); } /// @@ -184,20 +185,27 @@ protected override Size ArrangeOverride(Size finalSize) // Handle DataTable Parent else { - int column = 0; - double x = 0; - double spacing = _parentTable.ColumnSpacing; - double width = 0; + int maxChildCount = Math.Min(_parentTable.Children.Count, Children.Count); + + double columnSpacing = _parentTable.ColumnSpacing; + double x = double.NaN; - int i = 0; - foreach (UIElement child in Children.Where(static e => e.Visibility == Visibility.Visible)) + // Arrange all children which have corresponding visible DataColumns. + for (int i = 0; i < maxChildCount; i++) { - // TODO: Need to check Column visibility here as well... - if (column < _parentTable.Children.Count) - { - // TODO: This is messy... - width = (_parentTable.Children[column++] as DataColumn)?.ActualWidth ?? 0; - } + var column = _parentTable.Children[i] as DataColumn; + if (column?.Visibility != Visibility.Visible) + continue; + + if (double.IsNaN(x)) + x = 0; + else + x += columnSpacing; + + // TODO: This is messy... + double width = column.ActualWidth; + + var child = Children[i]; // Note: For Auto, since we measured our children and bubbled that up to the DataTable layout, // then the DataColumn size we grab above should account for the largest of our children. @@ -211,11 +219,10 @@ protected override Size ArrangeOverride(Size finalSize) child.Arrange(new Rect(x - _treePadding, 0, width, finalSize.Height)); } - x += width + spacing; - i++; + x += width; } - return new Size(x - spacing, finalSize.Height); + return new Size(x, finalSize.Height); } } } From 768491e9017c2cc85479c34cda03493da6b82e14 Mon Sep 17 00:00:00 2001 From: k-hara Date: Thu, 19 Feb 2026 18:37:40 +0900 Subject: [PATCH 10/12] Update the logic for the column widths calculation The internal column types are increased to 5: 1. IsAbsolute: have a width that is same with DesiredWidth, or a manually resized. 2. IsAuto: have a manually resized width. 3. IsStar: have a manually resized width. 4. IsAutoFit: have a calculated width that fits to each column content of visualized row. 5. IsStarProportion: have a calculated width, from the proportion of remained spaces. DataTable assigns the width space first to 1-3, then 4-5. But, to take the actual width of 4 would need the Measure of visualized rows, so DataTable and DataRows would have mutual call of InvalidateMeasure. When the layout system is being into stable, that loop will be break. --- ...ityToolkit.WinUI.Controls.DataTable.csproj | 2 +- .../DataTable/src/DataTable/DataColumn.cs | 46 ++-- components/DataTable/src/DataTable/DataRow.cs | 88 ++++---- .../DataTable/src/DataTable/DataTable.cs | 202 ++++++++++-------- 4 files changed, 188 insertions(+), 150 deletions(-) diff --git a/components/DataTable/src/CommunityToolkit.WinUI.Controls.DataTable.csproj b/components/DataTable/src/CommunityToolkit.WinUI.Controls.DataTable.csproj index a191e716f..4c6645576 100644 --- a/components/DataTable/src/CommunityToolkit.WinUI.Controls.DataTable.csproj +++ b/components/DataTable/src/CommunityToolkit.WinUI.Controls.DataTable.csproj @@ -12,7 +12,7 @@ - + $(PackageIdPrefix).$(PackageIdVariant).Controls.$(ToolkitComponentName) diff --git a/components/DataTable/src/DataTable/DataColumn.cs b/components/DataTable/src/DataTable/DataColumn.cs index 9ba925665..eab48c3be 100644 --- a/components/DataTable/src/DataTable/DataColumn.cs +++ b/components/DataTable/src/DataTable/DataColumn.cs @@ -10,8 +10,6 @@ namespace CommunityToolkit.WinUI.Controls; [TemplatePart(Name = nameof(PART_ColumnSizer), Type = typeof(ContentSizer))] public partial class DataColumn : ContentControl { - private static GridLength StarLength = new GridLength(1, GridUnitType.Star); - private ContentSizer? PART_ColumnSizer; private WeakReference? _parent; @@ -19,22 +17,27 @@ public partial class DataColumn : ContentControl internal DataTable? DataTable => _parent?.TryGetTarget(out DataTable? parent) == true ? parent : null; /// - /// Gets or sets the width of the largest child contained within the visible s - /// of the . + /// Gets or sets the internal calculated or manually set width of this column. + /// - Positive value: this column has a fixed or manually set width. + /// - Negative value: this column has a calculated width which is derived from DesiredWidth. + /// - NaN: this column should have a calculated width which isn't set yet. /// - internal double MaxChildDesiredWidth { get; set; } + internal double CurrentWidth { get; set; } = double.NaN; /// - /// Gets or sets the internal copy of the property to be used in calculations, - /// this gets manipulated in Auto-Size mode. + /// Gets the internal calculated or manually set width of this column, as a positive value. /// - internal GridLength CurrentWidth { get; private set; } + internal double ActualCurrentWidth => double.IsNaN(CurrentWidth) ? 0 : Math.Abs(CurrentWidth); + + internal bool IsAbsolute => DesiredWidth.IsAbsolute; + + internal bool IsAuto => DesiredWidth.IsAuto; - internal bool IsAbsolute => CurrentWidth.IsAbsolute; + internal bool IsAutoFit => DesiredWidth.IsAuto && !(CurrentWidth > 0); - internal bool IsAuto => CurrentWidth.IsAuto; + internal bool IsStar => DesiredWidth.IsStar; - internal bool IsStar => CurrentWidth.IsStar; + internal bool IsStarProportion => DesiredWidth.IsStar && !(CurrentWidth > 0); /// /// Gets or sets whether the column can be resized by the user. @@ -73,10 +76,16 @@ public GridLength DesiredWidth private static void DesiredWidth_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - // If the developer updates the size of the column, update our internal copy - if (d is DataColumn col) + // If the developer updates the size of the column, update our internal value. + if (d is DataColumn column) { - col.CurrentWidth = col.DesiredWidth; + if (column.DesiredWidth is { GridUnitType: GridUnitType.Pixel, Value: var value }) + { + column.CurrentWidth = value; + } + + // Request to measure for the IsAutoFit or IsStarProportion columns. + column.DataTable?.InvalidateMeasure(); } } @@ -130,9 +139,12 @@ private void PART_ColumnSizer_ManipulationCompleted(object sender, ManipulationC private void ColumnResizedByUserSizer() { // Update our internal representation to be our size now as a fixed value. - CurrentWidth = new(this.ActualWidth); + if (CurrentWidth != this.ActualWidth) + { + CurrentWidth = this.ActualWidth; - // Notify the rest of the table to update - DataTable?.ColumnResized(); + // Notify the rest of the table to update + DataTable?.ColumnResized(); + } } } diff --git a/components/DataTable/src/DataTable/DataRow.cs b/components/DataTable/src/DataTable/DataRow.cs index 118cb3160..e96dd67c4 100644 --- a/components/DataTable/src/DataTable/DataRow.cs +++ b/components/DataTable/src/DataTable/DataRow.cs @@ -16,7 +16,7 @@ public partial class DataRow : Panel private DataTable? _parentTable; private bool _isTreeView; - private double _treePadding; + internal double TreePadding { get; private set; } /// /// Constructor. @@ -69,6 +69,8 @@ private void DataRow_Unloaded(object sender, RoutedEventArgs e) /// protected override Size MeasureOverride(Size availableSize) { + //Debug.WriteLine($"DataRow.MeasureOverride"); + // We should probably only have to do this once ever? _parentTable ??= InitializeParentHeaderConnection(); @@ -106,55 +108,53 @@ protected override Size MeasureOverride(Size availableSize) if (column?.Visibility != Visibility.Visible) continue; - if (column.IsAuto) + // For TreeView in the first column, we want the header to expand to encompass + // the maximum indentation of the tree. + //// TODO: We only want/need to do this once? We may want to do if we're not an Auto column too...? + if (i == 0 && _isTreeView) { - child.Measure(availableSize); - - // For TreeView in the first column, we want the header to expand to encompass - // the maximum indentation of the tree. - double padding = 0; - //// TODO: We only want/need to do this once? We may want to do if we're not an Auto column too...? - if (i == 0 && _isTreeView) + // Get our containing grid from TreeViewItem, start with our indented padding + var parentContainer = this.FindAscendant("MultiSelectGrid") as Grid; + if (parentContainer != null) { - // Get our containing grid from TreeViewItem, start with our indented padding - var parentContainer = this.FindAscendant("MultiSelectGrid") as Grid; - if (parentContainer != null) + TreePadding = parentContainer.Padding.Left; + // We assume our 'DataRow' is in the last child slot of the Grid, need to know + // how large the other columns are. + for (int j = 0; j < parentContainer.Children.Count - 1; j++) { - _treePadding = parentContainer.Padding.Left; - // We assume our 'DataRow' is in the last child slot of the Grid, need to know - // how large the other columns are. - for (int j = 0; j < parentContainer.Children.Count - 1; j++) - { - // TODO: We may need to get the actual size here later in Arrange? - _treePadding += parentContainer.Children[j].DesiredSize.Width; - } + // TODO: We may need to get the actual size here later in Arrange? + TreePadding += parentContainer.Children[j].DesiredSize.Width; } - padding = _treePadding; } + } - // TODO: Do we want this to ever shrink back? - var prev = column.MaxChildDesiredWidth; - column.MaxChildDesiredWidth = Math.Max(column.MaxChildDesiredWidth, child.DesiredSize.Width + padding); - if (column.MaxChildDesiredWidth != prev) - { - // If our measure has changed, then we have to invalidate the arrange of the DataTable - _parentTable.ColumnResized(); - } + double width = column.ActualCurrentWidth; - } - else if (column.IsAbsolute) + if (column.IsAutoFit) { - child.Measure(new(column.DesiredWidth.Value, availableSize.Height)); + // We should get the *required* width from the child. + child.Measure(new Size(double.PositiveInfinity, availableSize.Height)); + + var childWidth = child.DesiredSize.Width; + if (i == 0) + childWidth += TreePadding; + + // If the adjusted column width is smaller than the current cell width, + // we should call DataTable.MeasureOverride() again to extend it. + if (!(width >= childWidth)) + { + _parentTable.InvalidateMeasure(); + } } else { - child.Measure(availableSize); + child.Measure(new Size(width, availableSize.Height)); } maxHeight = Math.Max(maxHeight, child.DesiredSize.Height); } - // Return our parent's size as the desired size. + // Returns the same width as the DataTable requests, regardless of the IsAutoFit column presence. return new Size(_parentTable.DesiredSize.Width, maxHeight); } } @@ -162,6 +162,8 @@ protected override Size MeasureOverride(Size availableSize) /// protected override Size ArrangeOverride(Size finalSize) { + //Debug.WriteLine($"DataRow.ArrangeOverride"); + // If we don't have DataTable, just layout children like a horizontal StackPanel. if (_parentTable is null) { @@ -202,22 +204,12 @@ protected override Size ArrangeOverride(Size finalSize) else x += columnSpacing; - // TODO: This is messy... - double width = column.ActualWidth; + double width = column.ActualCurrentWidth; + if (i == 0) + width = Math.Max(0, width - TreePadding); var child = Children[i]; - - // Note: For Auto, since we measured our children and bubbled that up to the DataTable layout, - // then the DataColumn size we grab above should account for the largest of our children. - if (i == 0) - { - child.Arrange(new Rect(x, 0, width, finalSize.Height)); - } - else - { - // If we're in a tree, remove the indentation from the layout of columns beyond the first. - child.Arrange(new Rect(x - _treePadding, 0, width, finalSize.Height)); - } + child?.Arrange(new Rect(x, 0, width, finalSize.Height)); x += width; } diff --git a/components/DataTable/src/DataTable/DataTable.cs b/components/DataTable/src/DataTable/DataTable.cs index 105cccb29..f4a8dded6 100644 --- a/components/DataTable/src/DataTable/DataTable.cs +++ b/components/DataTable/src/DataTable/DataTable.cs @@ -10,15 +10,12 @@ namespace CommunityToolkit.WinUI.Controls; /// public partial class DataTable : Panel { - // TODO: We should cache this result and update if column properties change - internal bool IsAnyColumnAuto => Children.Any(static e => e is DataColumn { CurrentWidth.GridUnitType: GridUnitType.Auto }); - // TODO: Check with Sergio if there's a better structure here, as I don't need a Dictionary like ConditionalWeakTable internal HashSet Rows { get; private set; } = new(); internal void ColumnResized() { - InvalidateArrange(); + InvalidateMeasure(); foreach (var row in Rows) { @@ -47,120 +44,157 @@ public double ColumnSpacing /// protected override Size MeasureOverride(Size availableSize) { - double fixedWidth = 0; - double proportionalUnits = 0; - double autoSized = 0; + //Debug.WriteLine($"DataTable.MeasureOverride"); + + int starRemains = 0; + double starAmounts = 0; + double columnSpacing = ColumnSpacing; + double totalWidth = double.NaN; double maxHeight = 0; - var elements = Children.Where(static e => e.Visibility == Visibility.Visible && e is DataColumn); + bool invokeRowsMeasures = false; - // We only need to measure elements that are visible - foreach (DataColumn column in elements) + for (int i = 0; i < Children.Count; i++) { - if (column.IsStar) + // We only need to measure children that are visible + var column = Children[i] as DataColumn; + if (column?.Visibility != Visibility.Visible) + continue; + + if (double.IsNaN(totalWidth)) + totalWidth = 0; + else + totalWidth += columnSpacing; + + double width = column.ActualCurrentWidth; + + if (column.IsStarProportion) { - proportionalUnits += column.DesiredWidth.Value; + ++starRemains; + starAmounts += column.DesiredWidth.Value; + continue; + } + else if (column.IsStar) + { + starAmounts += column.DesiredWidth.Value; + //Debug.WriteLine($" Column[{i}] ({column.DesiredWidth}) width is fixed to: {width}"); + + // If availableSize.Width is infinite, the column will also get infinite available width. + column.Measure(new Size(width, availableSize.Height)); } else if (column.IsAbsolute) { - fixedWidth += column.DesiredWidth.Value; + // column.CurrentWidth is already set in DesiredWidth_PropertyChanged. + //Debug.WriteLine($" Column[{i}] ({column.DesiredWidth}) width is fixed to: {width}"); + + column.Measure(new Size(width, availableSize.Height)); + } + else // (column.IsAuto) + { + if (column.IsAutoFit) + { + // Calculate the best width of the header content. + column.Measure(new Size(double.PositiveInfinity, availableSize.Height)); + + width = column.DesiredSize.Width; + foreach (var row in Rows) + { + if (i < row.Children.Count) + { + var child = row.Children[i]; + + var childWidth = child.DesiredSize.Width; + if (i == 0) + childWidth += row.TreePadding; + + width = Math.Max(width, childWidth); + } + } + //Debug.WriteLine($" Column[{i}] ({column.DesiredWidth}) width is adjusted to: {width}"); + + // The column width of the corresponding cell in each row + // is taken into account in the next layout pass. + invokeRowsMeasures = true; + + // Store the calculated column width as a negative value. + column.CurrentWidth = -width; + } + else + { + //Debug.WriteLine($" Column[{i}] ({column.DesiredWidth}) width is fixed to: {width}"); + + column.Measure(new Size(width, availableSize.Height)); + } } - } - // Add in spacing between columns to our fixed size allotment - fixedWidth += (elements.Count() - 1) * ColumnSpacing; + totalWidth += width; + maxHeight = Math.Max(maxHeight, column.DesiredSize.Height); + } - // TODO: Handle infinite width? - var proportionalAmount = (availableSize.Width - fixedWidth) / proportionalUnits; + if (double.IsNaN(totalWidth)) + return new Size(0, 0); - foreach (DataColumn column in elements) + double starUnit = Math.Max(0, availableSize.Width - totalWidth) / starAmounts; + for (int i = 0; starRemains != 0; i++) { - if (column.IsStar) - { - column.Measure(new Size(proportionalAmount * column.CurrentWidth.Value, availableSize.Height)); - } - else if (column.IsAbsolute) - { - column.Measure(new Size(column.CurrentWidth.Value, availableSize.Height)); - } - else + var column = Children[i] as DataColumn; + if (column?.Visibility != Visibility.Visible) + continue; + + if (column.IsStarProportion) { - // TODO: Technically this is using 'Auto' on the Header content - // What the developer probably intends is it to be adjusted based on the contents of the rows... - // To enable this scenario, we'll need to actually measure the contents of the rows for that column - // in DataRow and figure out the maximum size to report back and adjust here in some sort of hand-shake - // for the layout process... (i.e. get the data in the measure step, use it in the arrange step here, - // then invalidate the child arranges [don't re-measure and cause loop]...) + --starRemains; + + // If the column width needs to be calculated, get the proportion of the remained space. + double width = starUnit * column.DesiredWidth.Value; + //Debug.WriteLine($" Column[{i}] ({column.DesiredWidth}) width is adjusted to: {width}"); - // For now, we'll just use the header content as a guideline to see if things work. + // If availableSize.Width is infinite, the column will also get infinite available width. + column.Measure(new Size(width, availableSize.Height)); - // Avoid negative values when columns don't fit `availableSize`. Otherwise the `Size` constructor will throw. - column.Measure(new Size(Math.Max(availableSize.Width - fixedWidth - autoSized, 0), availableSize.Height)); + // Store the calculated column width as a negative value. + column.CurrentWidth = -width; - // Keep track of already 'allotted' space, use either the maximum child size (if we know it) or the header content - autoSized += Math.Max(column.DesiredSize.Width, column.MaxChildDesiredWidth); + totalWidth += width; + maxHeight = Math.Max(maxHeight, column.DesiredSize.Height); } + } - maxHeight = Math.Max(maxHeight, column.DesiredSize.Height); + if (invokeRowsMeasures) + { + foreach (var row in Rows) + row.InvalidateMeasure(); } - return new Size(availableSize.Width, maxHeight); + return new Size(totalWidth, maxHeight); } /// protected override Size ArrangeOverride(Size finalSize) { - double fixedWidth = 0; - double proportionalUnits = 0; - double autoSized = 0; + //Debug.WriteLine($"DataTable.ArrangeOverride"); - var elements = Children.Where(static e => e.Visibility == Visibility.Visible && e is DataColumn); + double columnSpacing = ColumnSpacing; + double x = double.NaN; - // We only need to measure elements that are visible - foreach (DataColumn column in elements) + for (int i = 0; i < Children.Count; i++) { - if (column.IsStar) - { - proportionalUnits += column.CurrentWidth.Value; - } - else if (column.IsAbsolute) - { - fixedWidth += column.CurrentWidth.Value; - } - else - { - autoSized += Math.Max(column.DesiredSize.Width, column.MaxChildDesiredWidth); - } - } + // We only need to measure children that are visible + var column = Children[i] as DataColumn; + if (column?.Visibility != Visibility.Visible) + continue; - // TODO: Handle infinite width? - // TODO: This can go out of bounds or something around here when pushing a resized column to the right... - var proportionalAmount = (finalSize.Width - fixedWidth - autoSized) / proportionalUnits; + if (double.IsNaN(x)) + x = 0; + else + x += columnSpacing; - double width = 0; - double x = 0; + double width = column.ActualCurrentWidth; - foreach (DataColumn column in elements) - { - if (column.IsStar) - { - width = proportionalAmount * column.CurrentWidth.Value; - column.Arrange(new Rect(x, 0, width, finalSize.Height)); - } - else if (column.IsAbsolute) - { - width = column.CurrentWidth.Value; - column.Arrange(new Rect(x, 0, width, finalSize.Height)); - } - else - { - // TODO: We use the comparison of sizes a lot, should we cache in the DataColumn itself? - width = Math.Max(column.DesiredSize.Width, column.MaxChildDesiredWidth); - column.Arrange(new Rect(x, 0, width, finalSize.Height)); - } + column.Arrange(new Rect(x, 0, width, finalSize.Height)); - x += width + ColumnSpacing; + x += width; } return finalSize; From 4156c0285f7d8b7f57cc70043c12b4c8deb1537f Mon Sep 17 00:00:00 2001 From: k-hara Date: Mon, 23 Feb 2026 16:04:46 +0900 Subject: [PATCH 11/12] If availableSize.Width is infinite, use DesiredSize.Width of star columns --- .../DataTable/src/DataTable/DataTable.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/components/DataTable/src/DataTable/DataTable.cs b/components/DataTable/src/DataTable/DataTable.cs index f4a8dded6..23136735e 100644 --- a/components/DataTable/src/DataTable/DataTable.cs +++ b/components/DataTable/src/DataTable/DataTable.cs @@ -146,12 +146,22 @@ protected override Size MeasureOverride(Size availableSize) { --starRemains; - // If the column width needs to be calculated, get the proportion of the remained space. - double width = starUnit * column.DesiredWidth.Value; - //Debug.WriteLine($" Column[{i}] ({column.DesiredWidth}) width is adjusted to: {width}"); + double width; + if (double.IsFinite(starUnit)) + { + // If the column width needs to be calculated, get the proportion of the remained space. + width = starUnit * column.DesiredWidth.Value; - // If availableSize.Width is infinite, the column will also get infinite available width. - column.Measure(new Size(width, availableSize.Height)); + column.Measure(new Size(width, availableSize.Height)); + } + else + { + // If availableSize.Width is infinite, use DesiredSize.Width of the column. + column.Measure(new Size(double.PositiveInfinity, availableSize.Height)); + + width = column.DesiredSize.Width; + } + //Debug.WriteLine($" Column[{i}] ({column.DesiredWidth}) width is adjusted to: {width}"); // Store the calculated column width as a negative value. column.CurrentWidth = -width; From 925e7575e8ece90beaf653d2193641c1f80477fa Mon Sep 17 00:00:00 2001 From: k-hara Date: Tue, 24 Feb 2026 09:01:58 +0900 Subject: [PATCH 12/12] Fix a compile error while targeting UWP platform --- components/DataTable/src/DataTable/DataTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/DataTable/src/DataTable/DataTable.cs b/components/DataTable/src/DataTable/DataTable.cs index 23136735e..7743718ef 100644 --- a/components/DataTable/src/DataTable/DataTable.cs +++ b/components/DataTable/src/DataTable/DataTable.cs @@ -147,7 +147,7 @@ protected override Size MeasureOverride(Size availableSize) --starRemains; double width; - if (double.IsFinite(starUnit)) + if (!double.IsInfinity(starUnit) && !double.IsNaN(starUnit)) { // If the column width needs to be calculated, get the proportion of the remained space. width = starUnit * column.DesiredWidth.Value;