Skip to content

Commit 06eed0d

Browse files
author
msftbot[bot]
authored
Add FrameworkElement.VerticalAlignment and FrameworkElement.HorizontalAlignment support to WrapPanel (#3471)
## Fixes #3466 This PR adds the support of the `FrameworkElement.VerticalAlignment` and `FrameworkElement.HorizontalAlignment` to `WrapPanel`. ## PR Type What kind of change does this PR introduce? - Bugfix ## What is the current behavior? The wrap panel is always top/left aligning its children. ## What is the new behavior? The controls will now use the children alignment to properly align them inside their row/column. The `Arrange` code has been updated and is now a two steps process. The position and size of all the controls are stored in a new `UvRect` structure. Those `UvRect` are grouped by `Row`. This allows us to get the final height of the row before drawing the children so we can adjust their position. **VerticalAlignment.Top** ![image](https://user-images.githubusercontent.com/20152272/92946831-74f88e80-f457-11ea-9467-1e3124ef837a.png) **VerticalAlignment.Center** ![image](https://user-images.githubusercontent.com/20152272/92946849-7c1f9c80-f457-11ea-8a1c-868438d9da90.png) ![image](https://user-images.githubusercontent.com/20152272/92946920-978aa780-f457-11ea-9928-1346b5cf3a8e.png) **VerticalAlignment.Bottom** ![image](https://user-images.githubusercontent.com/20152272/92946873-8772c800-f457-11ea-86ae-049fbe1676ab.png) **VerticalAlignment.Stretch** ![image](https://user-images.githubusercontent.com/20152272/92946900-90fc3000-f457-11ea-8f70-fe01db58930a.png) ## PR Checklist Please check if your PR fulfills the following requirements: - [x] Tested code with current [supported SDKs](../readme.md#supported) - [ ] Pull Request has been submitted to the documentation repository [instructions](..\contributing.md#docs). Link: <!-- docs PR link --> - [x] Sample in sample app has been added / updated (for bug fixes / features) - [ ] Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/windows-toolkit/WindowsCommunityToolkit-design-assets) - [ ] Tests for the changes have been added (for bug fixes / features) (if applicable) - [ ] Header has been added to all new source files (run *build/UpdateHeaders.bat*) - [x] Contains **NO** breaking changes
2 parents e3171b2 + a9c59ae commit 06eed0d

File tree

4 files changed

+169
-101
lines changed

4 files changed

+169
-101
lines changed

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapPanel/WrapPanel.bind

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@
4949
HorizontalSpacing="@[HorizontalSpacing:Slider:5:0-200]@" />
5050
</ItemsPanelTemplate>
5151
</ItemsControl.ItemsPanel>
52+
<ListView.ItemContainerStyle>
53+
<Style TargetType="ListViewItem">
54+
<!-- Change those values to change the WrapPanel's children alignment -->
55+
<Setter Property="VerticalContentAlignment" Value="Center" />
56+
<Setter Property="HorizontalContentAlignment" Value="Center" />
57+
<Setter Property="Padding" Value="0" />
58+
<Setter Property="MinWidth" Value="0" />
59+
<Setter Property="MinHeight" Value="0" />
60+
</Style>
61+
</ListView.ItemContainerStyle>
5262
</ListView>
5363
</Grid>
5464
</Page>

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapPanel/WrapPanelPage.xaml.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ private void AddButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
6464
{
6565
Category = "Remove",
6666
Thumbnail = "ms-appx:///Assets/Photos/BigFourSummerHeat.jpg",
67-
Width = Rand.Next(120, 180),
68-
Height = Rand.Next(80, 130)
67+
Width = Rand.Next(60, 180),
68+
Height = Rand.Next(40, 140)
6969
});
7070
}
7171

Microsoft.Toolkit.Uwp.UI.Controls/WrapPanel/WrapPanel.Data.cs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
6+
using System.Collections.Generic;
7+
using Microsoft.Toolkit.Diagnostics;
8+
using Windows.Foundation;
59
using Windows.UI.Xaml.Controls;
610

711
namespace Microsoft.Toolkit.Uwp.UI.Controls
@@ -14,12 +18,17 @@ public partial class WrapPanel
1418
[System.Diagnostics.DebuggerDisplay("U = {U} V = {V}")]
1519
private struct UvMeasure
1620
{
17-
internal static readonly UvMeasure Zero = default(UvMeasure);
21+
internal static UvMeasure Zero => default;
1822

1923
internal double U { get; set; }
2024

2125
internal double V { get; set; }
2226

27+
public UvMeasure(Orientation orientation, Size size)
28+
: this(orientation, size.Width, size.Height)
29+
{
30+
}
31+
2332
public UvMeasure(Orientation orientation, double width, double height)
2433
{
2534
if (orientation == Orientation.Horizontal)
@@ -33,6 +42,56 @@ public UvMeasure(Orientation orientation, double width, double height)
3342
V = width;
3443
}
3544
}
45+
46+
public UvMeasure Add(double u, double v)
47+
=> new UvMeasure { U = U + u, V = V + v };
48+
49+
public UvMeasure Add(UvMeasure measure)
50+
=> Add(measure.U, measure.V);
51+
52+
public Size ToSize(Orientation orientation)
53+
=> orientation == Orientation.Horizontal ? new Size(U, V) : new Size(V, U);
54+
}
55+
56+
private struct UvRect
57+
{
58+
public UvMeasure Position { get; set; }
59+
60+
public UvMeasure Size { get; set; }
61+
62+
public Rect ToRect(Orientation orientation) => orientation switch
63+
{
64+
Orientation.Vertical => new Rect(Position.V, Position.U, Size.V, Size.U),
65+
Orientation.Horizontal => new Rect(Position.U, Position.V, Size.U, Size.V),
66+
_ => ThrowHelper.ThrowNotSupportedException<Rect>("unsupported orientation"),
67+
};
68+
}
69+
70+
private struct Row
71+
{
72+
public Row(List<UvRect> childrenRects, UvMeasure size)
73+
{
74+
ChildrenRects = childrenRects;
75+
Size = size;
76+
}
77+
78+
public List<UvRect> ChildrenRects { get; }
79+
80+
public UvMeasure Size { get; set; }
81+
82+
public UvRect Rect => ChildrenRects.Count > 0 ?
83+
new UvRect { Position = ChildrenRects[0].Position, Size = Size } :
84+
new UvRect { Position = UvMeasure.Zero, Size = Size };
85+
86+
public void Add(UvMeasure position, UvMeasure size)
87+
{
88+
ChildrenRects.Add(new UvRect { Position = position, Size = size });
89+
Size = new UvMeasure
90+
{
91+
U = position.U + size.U,
92+
V = Math.Max(Size.V, size.V),
93+
};
94+
}
3695
}
3796
}
3897
}

Microsoft.Toolkit.Uwp.UI.Controls/WrapPanel/WrapPanel.cs

Lines changed: 97 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
68
using Windows.Foundation;
79
using Windows.UI.Xaml;
810
using Windows.UI.Xaml.Controls;
@@ -128,133 +130,130 @@ private static void LayoutPropertyChanged(DependencyObject d, DependencyProperty
128130
}
129131
}
130132

133+
private readonly List<Row> _rows = new List<Row>();
134+
131135
/// <inheritdoc />
132136
protected override Size MeasureOverride(Size availableSize)
133137
{
134-
availableSize.Width = availableSize.Width - Padding.Left - Padding.Right;
135-
availableSize.Height = availableSize.Height - Padding.Top - Padding.Bottom;
136-
var totalMeasure = UvMeasure.Zero;
137-
var parentMeasure = new UvMeasure(Orientation, availableSize.Width, availableSize.Height);
138-
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
139-
var lineMeasure = UvMeasure.Zero;
140-
138+
var childAvailableSize = new Size(
139+
availableSize.Width - Padding.Left - Padding.Right,
140+
availableSize.Height - Padding.Top - Padding.Bottom);
141141
foreach (var child in Children)
142142
{
143-
child.Measure(availableSize);
144-
var currentMeasure = new UvMeasure(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
145-
if (currentMeasure.U == 0)
146-
{
147-
continue; // ignore collapsed items
148-
}
143+
child.Measure(childAvailableSize);
144+
}
149145

150-
// if this is the first item, do not add spacing. Spacing is added to the "left"
151-
double uChange = lineMeasure.U == 0
152-
? currentMeasure.U
153-
: currentMeasure.U + spacingMeasure.U;
154-
if (parentMeasure.U >= uChange + lineMeasure.U)
155-
{
156-
lineMeasure.U += uChange;
157-
lineMeasure.V = Math.Max(lineMeasure.V, currentMeasure.V);
158-
}
159-
else
160-
{
161-
// new line should be added
162-
// to get the max U to provide it correctly to ui width ex: ---| or -----|
163-
totalMeasure.U = Math.Max(lineMeasure.U, totalMeasure.U);
164-
totalMeasure.V += lineMeasure.V + spacingMeasure.V;
146+
var requiredSize = UpdateRows(availableSize);
147+
return requiredSize;
148+
}
165149

166-
// if the next new row still can handle more controls
167-
if (parentMeasure.U > currentMeasure.U)
168-
{
169-
// set lineMeasure initial values to the currentMeasure to be calculated later on the new loop
170-
lineMeasure = currentMeasure;
171-
}
150+
/// <inheritdoc />
151+
protected override Size ArrangeOverride(Size finalSize)
152+
{
153+
if ((Orientation == Orientation.Horizontal && finalSize.Width < DesiredSize.Width) ||
154+
(Orientation == Orientation.Vertical && finalSize.Height < DesiredSize.Height))
155+
{
156+
// We haven't received our desired size. We need to refresh the rows.
157+
UpdateRows(finalSize);
158+
}
172159

173-
// the control will take one row alone
174-
else
160+
if (_rows.Count > 0)
161+
{
162+
// Now that we have all the data, we do the actual arrange pass
163+
var childIndex = 0;
164+
foreach (var row in _rows)
165+
{
166+
foreach (var rect in row.ChildrenRects)
175167
{
176-
// validate the new control measures
177-
totalMeasure.U = Math.Max(currentMeasure.U, totalMeasure.U);
178-
totalMeasure.V += currentMeasure.V;
168+
var child = Children[childIndex++];
169+
var arrangeRect = new UvRect
170+
{
171+
Position = rect.Position,
172+
Size = new UvMeasure { U = rect.Size.U, V = row.Size.V },
173+
};
179174

180-
// add new empty line
181-
lineMeasure = UvMeasure.Zero;
175+
var finalRect = arrangeRect.ToRect(Orientation);
176+
child.Arrange(finalRect);
182177
}
183178
}
184179
}
185180

186-
// update value with the last line
187-
// if the last loop is (parentMeasure.U > currentMeasure.U + lineMeasure.U) the total isn't calculated then calculate it
188-
// if the last loop is (parentMeasure.U > currentMeasure.U) the currentMeasure isn't added to the total so add it here
189-
// for the last condition it is zeros so adding it will make no difference
190-
// this way is faster than an if condition in every loop for checking the last item
191-
totalMeasure.U = Math.Max(lineMeasure.U, totalMeasure.U);
192-
totalMeasure.V += lineMeasure.V;
193-
194-
totalMeasure.U = Math.Ceiling(totalMeasure.U);
195-
196-
return Orientation == Orientation.Horizontal ? new Size(totalMeasure.U, totalMeasure.V) : new Size(totalMeasure.V, totalMeasure.U);
181+
return finalSize;
197182
}
198183

199-
/// <inheritdoc />
200-
protected override Size ArrangeOverride(Size finalSize)
184+
private Size UpdateRows(Size availableSize)
201185
{
202-
if (Children.Count > 0)
186+
_rows.Clear();
187+
188+
var paddingStart = new UvMeasure(Orientation, Padding.Left, Padding.Top);
189+
var paddingEnd = new UvMeasure(Orientation, Padding.Right, Padding.Bottom);
190+
191+
if (Children.Count == 0)
203192
{
204-
var parentMeasure = new UvMeasure(Orientation, finalSize.Width, finalSize.Height);
205-
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
206-
var paddingStart = new UvMeasure(Orientation, Padding.Left, Padding.Top);
207-
var paddingEnd = new UvMeasure(Orientation, Padding.Right, Padding.Bottom);
208-
var position = new UvMeasure(Orientation, Padding.Left, Padding.Top);
209-
210-
double currentV = 0;
211-
void Arrange(UIElement child, bool isLast = false)
212-
{
213-
var desiredMeasure = new UvMeasure(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
214-
if (desiredMeasure.U == 0)
215-
{
216-
return; // if an item is collapsed, avoid adding the spacing
217-
}
193+
var emptySize = paddingStart.Add(paddingEnd).ToSize(Orientation);
194+
return emptySize;
195+
}
218196

219-
if ((desiredMeasure.U + position.U + paddingEnd.U) > parentMeasure.U)
220-
{
221-
// next row!
222-
position.U = paddingStart.U;
223-
position.V += currentV + spacingMeasure.V;
224-
currentV = 0;
225-
}
197+
var parentMeasure = new UvMeasure(Orientation, availableSize.Width, availableSize.Height);
198+
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
199+
var position = new UvMeasure(Orientation, Padding.Left, Padding.Top);
226200

227-
// Stretch the last item to fill the available space
228-
if (isLast)
229-
{
230-
desiredMeasure.U = parentMeasure.U - position.U;
231-
}
201+
var currentRow = new Row(new List<UvRect>(), default);
202+
var finalMeasure = new UvMeasure(Orientation, width: 0.0, height: 0.0);
203+
void Arrange(UIElement child, bool isLast = false)
204+
{
205+
var desiredMeasure = new UvMeasure(Orientation, child.DesiredSize);
206+
if (desiredMeasure.U == 0)
207+
{
208+
return; // if an item is collapsed, avoid adding the spacing
209+
}
232210

233-
// place the item
234-
if (Orientation == Orientation.Horizontal)
235-
{
236-
child.Arrange(new Rect(position.U, position.V, desiredMeasure.U, desiredMeasure.V));
237-
}
238-
else
239-
{
240-
child.Arrange(new Rect(position.V, position.U, desiredMeasure.V, desiredMeasure.U));
241-
}
211+
if ((desiredMeasure.U + position.U + paddingEnd.U) > parentMeasure.U)
212+
{
213+
// next row!
214+
position.U = paddingStart.U;
215+
position.V += currentRow.Size.V + spacingMeasure.V;
242216

243-
// adjust the location for the next items
244-
position.U += desiredMeasure.U + spacingMeasure.U;
245-
currentV = Math.Max(desiredMeasure.V, currentV);
217+
_rows.Add(currentRow);
218+
currentRow = new Row(new List<UvRect>(), default);
246219
}
247220

248-
var lastIndex = Children.Count - 1;
249-
for (var i = 0; i < lastIndex; i++)
221+
// Stretch the last item to fill the available space
222+
if (isLast)
250223
{
251-
Arrange(Children[i]);
224+
desiredMeasure.U = parentMeasure.U - position.U;
252225
}
253226

254-
Arrange(Children[lastIndex], StretchChild == StretchChild.Last);
227+
currentRow.Add(position, desiredMeasure);
228+
229+
// adjust the location for the next items
230+
position.U += desiredMeasure.U + spacingMeasure.U;
231+
finalMeasure.U = Math.Max(finalMeasure.U, position.U);
255232
}
256233

257-
return finalSize;
234+
var lastIndex = Children.Count - 1;
235+
for (var i = 0; i < lastIndex; i++)
236+
{
237+
Arrange(Children[i]);
238+
}
239+
240+
Arrange(Children[lastIndex], StretchChild == StretchChild.Last);
241+
if (currentRow.ChildrenRects.Count > 0)
242+
{
243+
_rows.Add(currentRow);
244+
}
245+
246+
if (_rows.Count == 0)
247+
{
248+
var emptySize = paddingStart.Add(paddingEnd).ToSize(Orientation);
249+
return emptySize;
250+
}
251+
252+
// Get max V here before computing final rect
253+
var lastRowRect = _rows.Last().Rect;
254+
finalMeasure.V = lastRowRect.Position.V + lastRowRect.Size.V;
255+
var finalRect = finalMeasure.Add(paddingEnd).ToSize(Orientation);
256+
return finalRect;
258257
}
259258
}
260259
}

0 commit comments

Comments
 (0)