Skip to content

Commit 9c9f461

Browse files
committed
Update the logic for the column widths calculation
For `IsAuto` and `IsStar` columns, additional attribute `IsFixed` is considered. - `IsAuto && IsFixed`: have a manually resized width. - `IsAuto && !IsFixed`: have a calculated width that fits to each column content of visualized row. - `IsStar && IsFixed`: have a manually resized width. - `IsStar && !IsFixed`: have a calculated width, from the proportion of remained spaces. If `IsAbsolute == true`, `IsFixed` is also `true` always. If `IsStar` has zero ratio `0*`, `IsFixed` is specially set to `true` and the column has zero width. `DataTable` assigns the width space first to the fixed columns, then unfixed ones. For the IsFixed == true columns, there's nothing to difficult. For the column that is IsAuto && !IsFixed, the column size is determined with the following steps: - [DataTable.MeasureOverride] - The column's `CurerntWidth` is set to best-fit width of its header content. - For each visualized `DataRow`s, `InvalidateMeasure()` is invoked. - [DataRow.MeasureOverride] - Calculates the best-fit width of the column content and increase the column's `CurrentWidth` to get the maximun width. - Then, call `DataTable.InvalidateMeasure()`. - [DataTable.MeasureOverride] - In the last, gets the best-fit width for the column. - The mutual `InvalidateMeasure()` call between `DataTable` and `DataRow`s are stopped by the layout system when all element sizes are stable. For the column that is IsStar && !IsFixed, the column size is determined with the following steps: - [DataTable.MeasureOverride] - The column's `CurerntWidth` is set to best-fit width of its header content. - For each visualized `DataRow`s, InvalidateArrange() is invoked. - [DataRow.ArrangeOverride] - Remaining space of finalSize.Width is supplied to the star proportion column.
1 parent 06622bc commit 9c9f461

4 files changed

Lines changed: 273 additions & 163 deletions

File tree

components/DataTable/src/CommunityToolkit.WinUI.Controls.DataTable.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<!-- Sets this up as a toolkit component's source project -->
1313
<Import Project="$(ToolingDirectory)\ToolkitComponent.SourceProject.props" />
1414

15-
<PropertyGroup>
15+
<PropertyGroup>
1616
<PackageId>$(PackageIdPrefix).$(PackageIdVariant).Controls.$(ToolkitComponentName)</PackageId>
1717
</PropertyGroup>
1818
</Project>

components/DataTable/src/DataTable/DataColumn.cs

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,33 @@ namespace CommunityToolkit.WinUI.Controls;
1010
[TemplatePart(Name = nameof(PART_ColumnSizer), Type = typeof(ContentSizer))]
1111
public partial class DataColumn : ContentControl
1212
{
13-
private static GridLength StarLength = new GridLength(1, GridUnitType.Star);
14-
1513
private ContentSizer? PART_ColumnSizer;
1614

1715
private WeakReference<DataTable>? _parent;
1816

1917
internal DataTable? DataTable => _parent?.TryGetTarget(out DataTable? parent) == true ? parent : null;
2018

2119
/// <summary>
22-
/// Gets or sets the width of the largest child contained within the visible <see cref="DataRow"/>s of the <see cref="DataTable"/>.
20+
/// Gets or sets the internal calculated or manually set width of this column.
21+
/// NaN means that the column size is no yet calculated.
2322
/// </summary>
24-
internal double MaxChildDesiredWidth { get; set; }
23+
internal double CurrentWidth { get; set; } = double.NaN;
2524

2625
/// <summary>
27-
/// Gets or sets the internal copy of the <see cref="DesiredWidth"/> property to be used in calculations, this gets manipulated in Auto-Size mode.
26+
/// Gets the internal calculated or manually set width of this column, as a positive value.
2827
/// </summary>
29-
internal GridLength CurrentWidth { get; private set; }
28+
internal double ActualCurrentWidth => double.IsNaN(CurrentWidth) ? 0 : CurrentWidth;
29+
30+
internal bool IsAbsolute => DesiredWidth.IsAbsolute;
3031

31-
internal bool IsAbsolute => CurrentWidth.IsAbsolute;
32+
internal bool IsAuto => DesiredWidth.IsAuto;
3233

33-
internal bool IsAuto => CurrentWidth.IsAuto;
34+
internal bool IsStar => DesiredWidth.IsStar;
3435

35-
internal bool IsStar => CurrentWidth.IsStar;
36+
/// <summary>
37+
/// Returns <see langword="true"/> if the column width is fixed with the manual adjustment.
38+
/// </summary>
39+
internal bool IsFixed { get; set; }
3640

3741
/// <summary>
3842
/// Gets or sets whether the column can be resized by the user.
@@ -66,10 +70,29 @@ public GridLength DesiredWidth
6670

6771
private static void DesiredWidth_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
6872
{
69-
// If the developer updates the size of the column, update our internal copy
70-
if (d is DataColumn col)
73+
// If the developer updates the size of the column, update our internal value.
74+
if (d is DataColumn column)
7175
{
72-
col.CurrentWidth = col.DesiredWidth;
76+
if (column.DesiredWidth is { GridUnitType: GridUnitType.Pixel, Value: var value })
77+
{
78+
column.IsFixed = true;
79+
column.CurrentWidth = value;
80+
}
81+
else if (column.DesiredWidth is { GridUnitType: GridUnitType.Star, Value: 0 })
82+
{
83+
// Handle DesiredWidth="0*" as fixed zero width column.
84+
column.IsFixed = true;
85+
column.CurrentWidth = 0;
86+
}
87+
else
88+
{
89+
// Reset the manual adjusted width.
90+
column.IsFixed = false;
91+
column.CurrentWidth = double.NaN;
92+
}
93+
94+
// Request to measure for the IsAutoFit or IsStarProportion columns.
95+
column.DataTable?.InvalidateMeasure();
7396
}
7497
}
7598

@@ -123,9 +146,12 @@ private void PART_ColumnSizer_ManipulationCompleted(object sender, ManipulationC
123146
private void ColumnResizedByUserSizer()
124147
{
125148
// Update our internal representation to be our size now as a fixed value.
126-
CurrentWidth = new(this.ActualWidth);
149+
if (CurrentWidth != this.ActualWidth)
150+
{
151+
CurrentWidth = this.ActualWidth;
127152

128-
// Notify the rest of the table to update
129-
DataTable?.ColumnResized();
153+
// Notify the rest of the table to update
154+
DataTable?.ColumnResized();
155+
}
130156
}
131157
}

components/DataTable/src/DataTable/DataRow.cs

Lines changed: 72 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public partial class DataRow : Panel
1616
private DataTable? _parentTable;
1717

1818
private bool _isTreeView;
19-
private double _treePadding;
19+
internal double TreePadding { get; private set; }
2020

2121
/// <summary>
2222
/// Initializes a new instance of the <see cref="DataRow"/> class.
@@ -68,6 +68,8 @@ private void DataRow_Unloaded(object sender, RoutedEventArgs e)
6868
/// <inheritdoc/>
6969
protected override Size MeasureOverride(Size availableSize)
7070
{
71+
//Debug.WriteLine($"DataRow.MeasureOverride");
72+
7173
// We should probably only have to do this once ever?
7274
_parentTable ??= InitializeParentHeaderConnection();
7375

@@ -95,70 +97,72 @@ protected override Size MeasureOverride(Size availableSize)
9597
// Handle DataTable Parent
9698
else
9799
{
98-
if (_parentTable.Children.Count == Children.Count)
100+
int maxChildCount = Math.Min(_parentTable.Children.Count, Children.Count);
101+
102+
// Measure all children which have corresponding visible DataColumns.
103+
for (int i = 0; i < maxChildCount; i++)
99104
{
100-
// TODO: Need to check visibility
101-
// Measure all children since we need to determine the row's height at minimum
102-
for (int i = 0; i < Children.Count; i++)
105+
var child = Children[i];
106+
var column = _parentTable.Children[i] as DataColumn;
107+
if (column?.Visibility != Visibility.Visible)
108+
continue;
109+
110+
// For TreeView in the first column, we want the header to expand to encompass
111+
// the maximum indentation of the tree.
112+
//// TODO: We only want/need to do this once? We may want to do if we're not an Auto column too...?
113+
if (i == 0 && _isTreeView)
103114
{
104-
if (_parentTable.Children[i] is DataColumn { CurrentWidth.GridUnitType: GridUnitType.Auto } col)
115+
// Get our containing grid from TreeViewItem, start with our indented padding
116+
var parentContainer = this.FindAscendant("MultiSelectGrid") as Grid;
117+
if (parentContainer != null)
105118
{
106-
Children[i].Measure(availableSize);
107-
108-
// For TreeView in the first column, we want the header to expand to encompass
109-
// the maximum indentation of the tree.
110-
double padding = 0;
111-
//// TODO: We only want/need to do this once? We may want to do if we're not an Auto column too...?
112-
if (i == 0 && _isTreeView)
119+
TreePadding = parentContainer.Padding.Left;
120+
// We assume our 'DataRow' is in the last child slot of the Grid, need to know
121+
// how large the other columns are.
122+
for (int j = 0; j < parentContainer.Children.Count - 1; j++)
113123
{
114-
// Get our containing grid from TreeViewItem, start with our indented padding
115-
var parentContainer = this.FindAscendant("MultiSelectGrid") as Grid;
116-
if (parentContainer != null)
117-
{
118-
_treePadding = parentContainer.Padding.Left;
119-
// We assume our 'DataRow' is in the last child slot of the Grid, need to know how large the other columns are.
120-
for (int j = 0; j < parentContainer.Children.Count - 1; j++)
121-
{
122-
// TODO: We may need to get the actual size here later in Arrange?
123-
_treePadding += parentContainer.Children[j].DesiredSize.Width;
124-
}
125-
}
126-
padding = _treePadding;
124+
// TODO: We may need to get the actual size here later in Arrange?
125+
TreePadding += parentContainer.Children[j].DesiredSize.Width;
127126
}
127+
}
128+
}
128129

129-
// TODO: Do we want this to ever shrink back?
130-
var prev = col.MaxChildDesiredWidth;
131-
col.MaxChildDesiredWidth = Math.Max(col.MaxChildDesiredWidth, Children[i].DesiredSize.Width + padding);
132-
if (col.MaxChildDesiredWidth != prev)
133-
{
134-
// If our measure has changed, then we have to invalidate the arrange of the DataTable
135-
_parentTable.ColumnResized();
136-
}
130+
double width = column.ActualCurrentWidth;
137131

138-
}
139-
else if (_parentTable.Children[i] is DataColumn { CurrentWidth.GridUnitType: GridUnitType.Pixel } pixel)
140-
{
141-
Children[i].Measure(new(pixel.DesiredWidth.Value, availableSize.Height));
142-
}
143-
else
132+
if (column.IsAuto && !column.IsFixed)
133+
{
134+
// We should get the *required* width from the child.
135+
child.Measure(new Size(double.PositiveInfinity, availableSize.Height));
136+
137+
var childWidth = child.DesiredSize.Width;
138+
if (i == 0)
139+
childWidth += TreePadding;
140+
141+
// If the adjusted column width is smaller than the current cell width,
142+
// we should call DataTable.MeasureOverride() again to extend it.
143+
if (!(width >= childWidth))
144144
{
145-
Children[i].Measure(availableSize);
145+
_parentTable.InvalidateMeasure();
146146
}
147-
148-
maxHeight = Math.Max(maxHeight, Children[i].DesiredSize.Height);
149147
}
148+
else
149+
{
150+
child.Measure(new Size(width, availableSize.Height));
151+
}
152+
153+
maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
150154
}
151155

152-
// TODO: What do we want to do if there's unequal children in the DataTable vs. DataRow?
156+
// Returns the same width as the DataTable requests, regardless of the IsAutoFit column presence.
157+
return new Size(_parentTable.DesiredSize.Width, maxHeight);
153158
}
154-
155-
// Otherwise, return our parent's size as the desired size.
156-
return new(_parentTable?.DesiredSize.Width ?? availableSize.Width, maxHeight);
157159
}
158160

159161
/// <inheritdoc/>
160162
protected override Size ArrangeOverride(Size finalSize)
161163
{
164+
//Debug.WriteLine($"DataRow.ArrangeOverride");
165+
162166
// If we don't have DataTable, just layout children like a horizontal StackPanel.
163167
if (_parentTable is null)
164168
{
@@ -182,37 +186,34 @@ protected override Size ArrangeOverride(Size finalSize)
182186
// Handle DataTable Parent
183187
else
184188
{
185-
int column = 0;
186-
double x = 0;
187-
double spacing = _parentTable.ColumnSpacing;
188-
double width = 0;
189+
int maxChildCount = Math.Min(_parentTable.Children.Count, Children.Count);
189190

190-
int i = 0;
191-
foreach (UIElement child in Children.Where(static e => e.Visibility == Visibility.Visible))
191+
double columnSpacing = _parentTable.ColumnSpacing;
192+
double x = double.NaN;
193+
194+
// Arrange all children which have corresponding visible DataColumns.
195+
for (int i = 0; i < maxChildCount; i++)
192196
{
193-
// TODO: Need to check Column visibility here as well...
194-
if (column < _parentTable.Children.Count)
195-
{
196-
// TODO: This is messy...
197-
width = (_parentTable.Children[column++] as DataColumn)?.ActualWidth ?? 0;
198-
}
197+
var column = _parentTable.Children[i] as DataColumn;
198+
if (column?.Visibility != Visibility.Visible)
199+
continue;
199200

200-
// 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.
201-
if (i == 0)
202-
{
203-
child.Arrange(new Rect(x, 0, width, finalSize.Height));
204-
}
201+
if (double.IsNaN(x))
202+
x = 0;
205203
else
206-
{
207-
// If we're in a tree, remove the indentation from the layout of columns beyond the first.
208-
child.Arrange(new Rect(x - _treePadding, 0, width, finalSize.Height));
209-
}
204+
x += columnSpacing;
205+
206+
double width = column.ActualCurrentWidth;
207+
if (i == 0)
208+
width = Math.Max(0, width - TreePadding);
209+
210+
var child = Children[i];
211+
child?.Arrange(new Rect(x, 0, width, finalSize.Height));
210212

211-
x += width + spacing;
212-
i++;
213+
x += width;
213214
}
214215

215-
return new Size(x - spacing, finalSize.Height);
216+
return new Size(x, finalSize.Height);
216217
}
217218
}
218219
}

0 commit comments

Comments
 (0)