Skip to content
Open
9 changes: 8 additions & 1 deletion components/Primitives/src/WrapPanel/WrapPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ public Thickness Padding
/// <summary>
/// Gets or sets a value indicating how to arrange child items
/// </summary>
/// <remarks>
/// When the available size provided to the panel is infinite (for example,
/// when placed in a container with Auto sizing), the last child will not be
/// stretched. Attempting to stretch in this scenario would cause the element
/// to expand to an infinite size and result in a runtime exception.
/// </remarks>
public StretchChild StretchChild
{
get { return (StretchChild)GetValue(StretchChildProperty); }
Expand Down Expand Up @@ -219,7 +225,8 @@ void Arrange(UIElement child, bool isLast = false)
}

// Stretch the last item to fill the available space
if (isLast)
// if the parent measure is not infinite
if (isLast && !double.IsInfinity(parentMeasure.U))
{
desiredMeasure.U = parentMeasure.U - position.U;
}
Expand Down
29 changes: 29 additions & 0 deletions components/Primitives/tests/Primitives.Tests.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,23 @@
<Compile Include="$(MSBuildThisFileDirectory)Test_UniformGrid_FreeSpots.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Test_UniformGrid_RowColDefinitions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Test_WrapPanel_BasicLayout.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Test_WrapPanel_StretchChild.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Test_WrapPanel_Visibility.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UniformGrid\AutoLayoutFixedElementZeroZeroSpecialPage.xaml.cs">
<DependentUpon>AutoLayoutFixedElementZeroZeroSpecialPage.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)WrapPanel\HorizontalWrapPanelInsideParentWithInfinityWidth.xaml.cs">
<DependentUpon>HorizontalWrapPanelInsideParentWithInfinityWidth.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)WrapPanel\HorizontalWrapPanelInsideParentWithLimitedWidth.xaml.cs">
<DependentUpon>HorizontalWrapPanelInsideParentWithLimitedWidth.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)WrapPanel\VerticalWrapPanelInsideParentWithInfinityHeight.xaml.cs">
<DependentUpon>VerticalWrapPanelInsideParentWithInfinityHeight.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)WrapPanel\VerticalWrapPanelInsideParentWithLimitedHeight.xaml.cs">
<DependentUpon>VerticalWrapPanelInsideParentWithLimitedHeight.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Page Include="$(MSBuildThisFileDirectory)DockPanel\DockPanelSample.xaml">
Expand All @@ -47,6 +60,22 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)WrapPanel\HorizontalWrapPanelInsideParentWithInfinityWidth.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)WrapPanel\HorizontalWrapPanelInsideParentWithLimitedWidth.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)WrapPanel\VerticalWrapPanelInsideParentWithInfinityHeight.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)WrapPanel\VerticalWrapPanelInsideParentWithLimitedHeight.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Include="$(MSBuildThisFileDirectory)SwitchPresenter\SwitchConverterBrushSample.xaml">
Expand Down
104 changes: 104 additions & 0 deletions components/Primitives/tests/Test_WrapPanel_StretchChild.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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.Tests;
using CommunityToolkit.Tooling.TestGen;
using CommunityToolkit.WinUI.Controls;

namespace PrimitivesTests;

[TestClass]
public partial class Test_WrapPanel_StretchChild : VisualUITestBase
{
/// <summary>
/// When a WrapPanel is inside a parent with infinite width, the last child cannot stretch to fill the remaining space.
/// Instead, it should measure to its desired size.
/// </summary>
[TestCategory("WrapPanel")]
[UIThreadTestMethod]
public void VerticalWrapPanelInsideParentWithInfinityHeightTest(VerticalWrapPanelInsideParentWithInfinityHeight page)
{
var wrapPanel = page.FindDescendant<WrapPanel>();
Assert.IsNotNull(wrapPanel, "Could not find WrapPanel.");
Assert.IsFalse(wrapPanel.StretchChild is not StretchChild.Last, "WrapPanel StretchChild property not set to Last.");
Assert.IsFalse(wrapPanel.Children.Count < 1, "No children to test.");

foreach (var child in wrapPanel.Children.Cast<FrameworkElement>())
{
double expectedHeight = child.DesiredSize.Height;
Assert.AreEqual(expectedHeight, child.ActualHeight, "Child height not as expected.");
}
}

/// <summary>
/// When a WrapPanel is inside a parent with limited height, the last child with Stretch alignment should fill the remaining space.
/// </summary>
[TestCategory("WrapPanel")]
[UIThreadTestMethod]
public void VerticalWrapPanelInsideParentWithLimitedHeightTest(VerticalWrapPanelInsideParentWithLimitedHeight page)
{
var wrapPanel = page.FindDescendant<WrapPanel>();
Assert.IsNotNull(wrapPanel, "Could not find WrapPanel.");
Assert.IsFalse(wrapPanel.StretchChild is not StretchChild.Last, "WrapPanel StretchChild property not set to Last.");
Assert.IsFalse(wrapPanel.Children.Count < 1, "No children to test.");

var precedingChildren = wrapPanel.Children.Cast<FrameworkElement>().Take(wrapPanel.Children.Count - 1);

foreach (var child in precedingChildren)
{
double expectedHeight = child.DesiredSize.Height;
Assert.AreEqual(expectedHeight, child.ActualHeight, "Preceding child height not as expected.");
}

var lastChild = wrapPanel.Children.Cast<FrameworkElement>().Last();
double lastChildExpectedHeight = wrapPanel.ActualHeight - precedingChildren.Sum(child => child.ActualHeight);
Assert.AreEqual(lastChildExpectedHeight, lastChild.ActualHeight, "Last child height not as expected.");
}

/// <summary>
/// When a WrapPanel is inside a parent with infinite width, the last child cannot stretch to fill the remaining space.
/// Instead, it should measure to its desired size.
/// </summary>
[TestCategory("WrapPanel")]
[UIThreadTestMethod]
public void HorizontalWrapPanelInsideParentWithInfinityWidthTest(HorizontalWrapPanelInsideParentWithInfinityWidth page)
{
var wrapPanel = page.FindDescendant<WrapPanel>();
Assert.IsNotNull(wrapPanel, "Could not find WrapPanel.");
Assert.IsFalse(wrapPanel.StretchChild is not StretchChild.Last, "WrapPanel StretchChild property not set to Last.");
Assert.IsFalse(wrapPanel.Children.Count < 1, "No children to test.");

foreach (var child in wrapPanel.Children.Cast<FrameworkElement>())
{
double expectedWidth = child.DesiredSize.Width;
Assert.AreEqual(expectedWidth, child.ActualWidth, "Preceding child width not as expected.");
}
}

/// <summary>
/// When a WrapPanel is inside a parent with limited width, the last child with Stretch alignment should fill the remaining space.
/// </summary>
/// <param name="page"></param>
[TestCategory("WrapPanel")]
[UIThreadTestMethod]
public void HorizontalWrapPanelInsideParentWithLimitedWidthTest(HorizontalWrapPanelInsideParentWithLimitedWidth page)
{
var wrapPanel = page.FindDescendant<WrapPanel>();
Assert.IsNotNull(wrapPanel, "Could not find WrapPanel.");
Assert.IsFalse(wrapPanel.StretchChild is not StretchChild.Last, "WrapPanel StretchChild property not set to Last.");
Assert.IsFalse(wrapPanel.Children.Count < 1, "No children to test.");

var precedingChildren = wrapPanel.Children.Cast<FrameworkElement>().Take(wrapPanel.Children.Count - 1);

foreach (var child in precedingChildren)
{
double expectedWidth = child.DesiredSize.Width;
Assert.AreEqual(expectedWidth, child.ActualWidth, "Child width not as expected.");
}

var lastChild = wrapPanel.Children.Cast<FrameworkElement>().Last();
double lastChildExpectedWidth = wrapPanel.ActualWidth - precedingChildren.Sum(child => child.ActualWidth);
Assert.AreEqual(lastChildExpectedWidth, lastChild.ActualWidth, "Last child width not as expected.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Page x:Class="PrimitivesTests.HorizontalWrapPanelInsideParentWithInfinityWidth"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:PrimitivesTests"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<controls:WrapPanel Orientation="Horizontal"
StretchChild="Last">
<Button HorizontalAlignment="Stretch"
Content="Child 1" />
<Button HorizontalAlignment="Stretch"
Content="Child 2" />
<Button HorizontalAlignment="Stretch"
Content="Last Child" />
</controls:WrapPanel>
</Grid>

</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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.

namespace PrimitivesTests;

public sealed partial class HorizontalWrapPanelInsideParentWithInfinityWidth : Page
{
public HorizontalWrapPanelInsideParentWithInfinityWidth()
{
this.InitializeComponent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Page x:Class="PrimitivesTests.HorizontalWrapPanelInsideParentWithLimitedWidth"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:PrimitivesTests"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<controls:WrapPanel Orientation="Horizontal"
StretchChild="Last">
<Button HorizontalAlignment="Stretch"
Content="Child 1" />
<Button HorizontalAlignment="Stretch"
Content="Child 2" />
<Button HorizontalAlignment="Stretch"
Content="Last Child" />
</controls:WrapPanel>
</Grid>

</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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.

namespace PrimitivesTests;

public sealed partial class HorizontalWrapPanelInsideParentWithLimitedWidth : Page
{
public HorizontalWrapPanelInsideParentWithLimitedWidth()
{
this.InitializeComponent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Page x:Class="PrimitivesTests.VerticalWrapPanelInsideParentWithInfinityHeight"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:PrimitivesTests"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<controls:WrapPanel Orientation="Vertical"
StretchChild="Last">
<Button VerticalAlignment="Stretch"
Content="Child 1" />
<Button VerticalAlignment="Stretch"
Content="Child 2" />
<Button VerticalAlignment="Stretch"
Content="Last Child" />
</controls:WrapPanel>
</Grid>

</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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.

namespace PrimitivesTests;

public sealed partial class VerticalWrapPanelInsideParentWithInfinityHeight : Page
{
public VerticalWrapPanelInsideParentWithInfinityHeight()
{
this.InitializeComponent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Page x:Class="PrimitivesTests.VerticalWrapPanelInsideParentWithLimitedHeight"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:PrimitivesTests"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<controls:WrapPanel Orientation="Vertical"
StretchChild="Last">
<Button VerticalAlignment="Stretch"
Content="Child 1" />
<Button VerticalAlignment="Stretch"
Content="Child 2" />
<Button VerticalAlignment="Stretch"
Content="Last Child" />
</controls:WrapPanel>
</Grid>

</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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.

namespace PrimitivesTests;

public sealed partial class VerticalWrapPanelInsideParentWithLimitedHeight : Page
{
public VerticalWrapPanelInsideParentWithLimitedHeight()
{
this.InitializeComponent();
}
}
Loading