diff --git a/components/WrapPanel2/OpenSolution.bat b/components/WrapPanel2/OpenSolution.bat
new file mode 100644
index 000000000..814a56d4b
--- /dev/null
+++ b/components/WrapPanel2/OpenSolution.bat
@@ -0,0 +1,3 @@
+@ECHO OFF
+
+powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
\ No newline at end of file
diff --git a/components/WrapPanel2/samples/Assets/icon.png b/components/WrapPanel2/samples/Assets/icon.png
new file mode 100644
index 000000000..8435bcaa9
Binary files /dev/null and b/components/WrapPanel2/samples/Assets/icon.png differ
diff --git a/components/WrapPanel2/samples/Dependencies.props b/components/WrapPanel2/samples/Dependencies.props
new file mode 100644
index 000000000..e622e1df4
--- /dev/null
+++ b/components/WrapPanel2/samples/Dependencies.props
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/WrapPanel2/samples/WrapPanel2.Samples.csproj b/components/WrapPanel2/samples/WrapPanel2.Samples.csproj
new file mode 100644
index 000000000..2f1628cb6
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2.Samples.csproj
@@ -0,0 +1,30 @@
+
+
+
+
+ WrapPanel2
+
+
+
+
+
+
+ WrapPanel2BasicSample.xaml
+
+
+
+
+ WrapPanel2ProportionalSample.xaml
+
+
+
+
+ WrapPanel2MegaSample.xaml
+
+
+
+
+ Designer
+
+
+
diff --git a/components/WrapPanel2/samples/WrapPanel2.md b/components/WrapPanel2/samples/WrapPanel2.md
new file mode 100644
index 000000000..93cfd85b1
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2.md
@@ -0,0 +1,67 @@
+---
+title: WrapPanel2
+author: Avid29
+description: A labs-component candidate for a new WrapPanel implementation.
+keywords: WrapPanel, Control, Layout
+dev_langs:
+ - csharp
+category: Layouts
+subcategory: Panel
+discussion-id: 762
+issue-id: 763
+icon: assets/icon.png
+---
+
+# WrapPanel2
+
+The WrapPanel2 is an experiment for a new WrapPanel API using GridLength definitions to define the item's desired sizings.
+
+When stretched along the main axis, the child elements with star-sized GridLength values will proportionally occupy the available space.
+
+When not stretched along the main axis, star-sized child elements will be the smallest size possible while maintaining proportional sizing relative to each other and ensuring that all child elements are fully visible.
+
+
+> [!Sample WrapPanel2BasicSample]
+
+## Properties
+
+### Fixed Row Length
+
+When `FixedRowLengths` is enabled, all rows/columns will to stretch to the size of the largest row/column in the panel. When this is not enabled, rows/columns will size to their content individually.
+
+### Stretching Children
+
+The `StretchChildren` property allows you to specify how the panel should handle stretching in rows without star-sized definitions.
+
+#### StarSizedOnly
+
+When set to `StarSizedOnly`, this panel will never stretch rows/columns that do not have star-sized definitions. When the alignment is set to stretch, and even when fixed row lengths is enabled, the rows/columns without star-sized definitions will size to their content.
+
+#### First
+
+When set the `First`, this panel will stretch the first item in the row/column to occupy the remaining space when needed if `FixedRowLengths` is enabled.
+
+#### Last
+
+When set to `Last`, this panel will stretch the last item in the row/column to occupy the remaining space when needed if `FixedRowLengths` is enabled.
+
+#### Equal
+
+When set to `Equal`, this panel will stretch all items in the row/column to occupy the equal space throughout the row when needed if `FixedRowLengths` is enabled.
+
+#### Proportional
+
+When set to `Proportional`, this panel will stretch all items in the row/column proportionally to their defined size to occupy the remaining space when needed if `FixedRowLengths` is enabled.
+
+## Additional Samples
+
+### Adjusted Sizings Sample
+
+> [!Sample WrapPanel2MegaSample]
+
+### Proportional Sizing
+
+Encoding diagram example
+
+> [!Sample WrapPanel2ProportionalSample]
+
diff --git a/components/WrapPanel2/samples/WrapPanel2BasicSample.xaml b/components/WrapPanel2/samples/WrapPanel2BasicSample.xaml
new file mode 100644
index 000000000..366faf463
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2BasicSample.xaml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/WrapPanel2/samples/WrapPanel2BasicSample.xaml.cs b/components/WrapPanel2/samples/WrapPanel2BasicSample.xaml.cs
new file mode 100644
index 000000000..d3a97138f
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2BasicSample.xaml.cs
@@ -0,0 +1,115 @@
+// 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;
+using Windows.UI;
+
+namespace WrapPanel2Experiment.Samples;
+
+///
+/// An example sample page of a custom control inheriting from Panel.
+///
+[ToolkitSampleMultiChoiceOption("LayoutOrientation", "Horizontal", "Vertical", Title = "Orientation")]
+[ToolkitSampleMultiChoiceOption("LayoutHorizontalAlignment", "Left", "Center", "Right", "Stretch", Title = "Horizontal Alignment")]
+[ToolkitSampleMultiChoiceOption("LayoutVerticalAlignment", "Top", "Center", "Bottom", "Stretch", Title = "Vertical Alignment")]
+[ToolkitSampleNumericOption("ItemSpacing", 8, 0, 16, Title = "Item Spacing")]
+[ToolkitSampleNumericOption("LineSpacing", 2, 0, 16, Title = "Line Spacing")]
+[ToolkitSampleBoolOption("FixedRowLengths", false, Title = "Fixed Row Lengths")]
+[ToolkitSampleMultiChoiceOption("LayoutStretchChildren", "StarSizedOnly", "First", "Last", "Equal", "Proportional", Title = "Forced Stretch Method")]
+[ToolkitSampleMultiChoiceOption("LayoutOverflowBehavior", "Wrap", "Drop", Title = "Overflow Behavior")]
+
+[ToolkitSample(id: nameof(WrapPanel2BasicSample), $"Basic demo of the {nameof(WrapPanel2)} with auto-sized items.", description: $"A sample showing every property of the {nameof(WrapPanel2)} panel.")]
+public sealed partial class WrapPanel2BasicSample : Page
+{
+ public WrapPanel2BasicSample()
+ {
+ this.InitializeComponent();
+ }
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static Orientation ConvertStringToOrientation(string orientation) => orientation switch
+ {
+ "Vertical" => Orientation.Vertical,
+ "Horizontal" => Orientation.Horizontal,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static HorizontalAlignment ConvertStringToHorizontalAlignment(string alignment) => alignment switch
+ {
+ "Left" => HorizontalAlignment.Left,
+ "Center" => HorizontalAlignment.Center,
+ "Right" => HorizontalAlignment.Right,
+ "Stretch" => HorizontalAlignment.Stretch,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static VerticalAlignment ConvertStringToVerticalAlignment(string alignment) => alignment switch
+ {
+ "Top" => VerticalAlignment.Top,
+ "Center" => VerticalAlignment.Center,
+ "Bottom" => VerticalAlignment.Bottom,
+ "Stretch" => VerticalAlignment.Stretch,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static StretchChildren ConvertStringToForcedStretchMethod(string stretchMethod) => stretchMethod switch
+ {
+ "StarSizedOnly" => StretchChildren.StarSizedOnly,
+ "First" => StretchChildren.First,
+ "Last" => StretchChildren.Last,
+ "Equal" => StretchChildren.Equal,
+ "Proportional" => StretchChildren.Proportional,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static OverflowBehavior ConvertStringToOverflowBehavior(string overflowBehavior) => overflowBehavior switch
+ {
+ "Wrap" => OverflowBehavior.Wrap,
+ "Drop" => OverflowBehavior.Drop,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ private int _index;
+
+ private string[] LoremIpsumWords => LoremIpsum.Split(' ');
+
+ private string LoremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam fermentum placerat pretium. Phasellus molestie faucibus purus ut semper. Etiam felis ante, condimentum sed leo in, aliquam pharetra libero. Etiam ante ante, sagittis in semper eu, aliquam non sapien. Donec a pharetra magna. Suspendisse et nulla magna. Cras varius sem dolor, ac faucibus turpis malesuada ac. Maecenas rutrum tortor et faucibus rutrum. Vestibulum in gravida odio, non dapibus dui. Praesent leo tellus, vulputate sed sollicitudin id, fringilla quis ligula. Cras eget ex vitae purus pulvinar mattis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec consectetur tellus id augue ultrices, eget congue tellus pharetra.";
+
+ private void AddItemClick(object sender, RoutedEventArgs e)
+ {
+ AddItem();
+ }
+
+ private void Add5ItemsClick(object sender, RoutedEventArgs e)
+ {
+ for (int i = 0; i < 5; i++)
+ AddItem();
+ }
+
+ private void ClearItemsClick(object sender, RoutedEventArgs e)
+ {
+ WrapPanel.Children.Clear();
+ _index = 0;
+ }
+
+ private void AddItem()
+ {
+ _index = _index % LoremIpsumWords.Length;
+
+ var currentWord = LoremIpsumWords[_index++];
+ var border = new Border()
+ {
+ Child = new TextBlock()
+ {
+ Text = currentWord,
+ }
+ };
+
+ WrapPanel.Children.Add(border);
+ }
+}
diff --git a/components/WrapPanel2/samples/WrapPanel2MegaSample.xaml b/components/WrapPanel2/samples/WrapPanel2MegaSample.xaml
new file mode 100644
index 000000000..41ffdf76f
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2MegaSample.xaml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/WrapPanel2/samples/WrapPanel2MegaSample.xaml.cs b/components/WrapPanel2/samples/WrapPanel2MegaSample.xaml.cs
new file mode 100644
index 000000000..cfd4c7fbd
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2MegaSample.xaml.cs
@@ -0,0 +1,75 @@
+// 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 WrapPanel2Experiment.Samples;
+
+///
+/// An example sample page of a custom control inheriting from Panel.
+///
+[ToolkitSampleMultiChoiceOption("LayoutOrientation", "Horizontal", "Vertical", Title = "Orientation")]
+[ToolkitSampleMultiChoiceOption("LayoutHorizontalAlignment", "Left", "Center", "Right", "Stretch", Title = "Horizontal Alignment")]
+[ToolkitSampleMultiChoiceOption("LayoutVerticalAlignment", "Top", "Center", "Bottom", "Stretch", Title = "Vertical Alignment")]
+[ToolkitSampleNumericOption("ItemSpacing", 8, 0, 16, Title = "Item Spacing")]
+[ToolkitSampleNumericOption("LineSpacing", 2, 0, 16, Title = "Line Spacing")]
+[ToolkitSampleBoolOption("FixedRowLengths", false, Title = "Fixed Row Lengths")]
+[ToolkitSampleMultiChoiceOption("LayoutStretchChildren", "StarSizedOnly", "First", "Last", "Equal", "Proportional", Title = "Forced Stretch Method")]
+[ToolkitSampleMultiChoiceOption("LayoutOverflowBehavior", "Wrap", "Drop", Title = "Overflow Behavior")]
+
+[ToolkitSample(id: nameof(WrapPanel2MegaSample), "Demo of all WrapPanel2 feature", description: $"A sample showing every property of the {nameof(WrapPanel2)} panel.")]
+public sealed partial class WrapPanel2MegaSample : Page
+{
+ public WrapPanel2MegaSample()
+ {
+ this.InitializeComponent();
+ }
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static Orientation ConvertStringToOrientation(string orientation) => orientation switch
+ {
+ "Vertical" => Orientation.Vertical,
+ "Horizontal" => Orientation.Horizontal,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static HorizontalAlignment ConvertStringToHorizontalAlignment(string alignment) => alignment switch
+ {
+ "Left" => HorizontalAlignment.Left,
+ "Center" => HorizontalAlignment.Center,
+ "Right" => HorizontalAlignment.Right,
+ "Stretch" => HorizontalAlignment.Stretch,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static VerticalAlignment ConvertStringToVerticalAlignment(string alignment) => alignment switch
+ {
+ "Top" => VerticalAlignment.Top,
+ "Center" => VerticalAlignment.Center,
+ "Bottom" => VerticalAlignment.Bottom,
+ "Stretch" => VerticalAlignment.Stretch,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static StretchChildren ConvertStringToForcedStretchMethod(string stretchMethod) => stretchMethod switch
+ {
+ "StarSizedOnly" => StretchChildren.StarSizedOnly,
+ "First" => StretchChildren.First,
+ "Last" => StretchChildren.Last,
+ "Equal" => StretchChildren.Equal,
+ "Proportional" => StretchChildren.Proportional,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static OverflowBehavior ConvertStringToOverflowBehavior(string overflowBehavior) => overflowBehavior switch
+ {
+ "Wrap" => OverflowBehavior.Wrap,
+ "Drop" => OverflowBehavior.Drop,
+ _ => throw new System.NotImplementedException(),
+ };
+}
diff --git a/components/WrapPanel2/samples/WrapPanel2ProportionalSample.xaml b/components/WrapPanel2/samples/WrapPanel2ProportionalSample.xaml
new file mode 100644
index 000000000..460d8f9d1
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2ProportionalSample.xaml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/WrapPanel2/samples/WrapPanel2ProportionalSample.xaml.cs b/components/WrapPanel2/samples/WrapPanel2ProportionalSample.xaml.cs
new file mode 100644
index 000000000..cba863bfe
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2ProportionalSample.xaml.cs
@@ -0,0 +1,19 @@
+// 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 WrapPanel2Experiment.Samples;
+
+///
+/// An example sample page of a custom control inheriting from Panel.
+///
+[ToolkitSample(id: nameof(WrapPanel2ProportionalSample), "Demo of proportional sizing", description: $"A sample showing every property of the {nameof(WrapPanel2)} panel.")]
+public sealed partial class WrapPanel2ProportionalSample : Page
+{
+ public WrapPanel2ProportionalSample()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/WrapPanel2/src/CommunityToolkit.WinUI.Controls.StretchPanel.csproj b/components/WrapPanel2/src/CommunityToolkit.WinUI.Controls.StretchPanel.csproj
new file mode 100644
index 000000000..fef145482
--- /dev/null
+++ b/components/WrapPanel2/src/CommunityToolkit.WinUI.Controls.StretchPanel.csproj
@@ -0,0 +1,14 @@
+
+
+
+
+ WrapPanel2
+ This package contains WrapPanel2.
+
+
+ CommunityToolkit.WinUI.Controls.WrapPanel2Rns
+
+
+
+
+
diff --git a/components/WrapPanel2/src/Dependencies.props b/components/WrapPanel2/src/Dependencies.props
new file mode 100644
index 000000000..e622e1df4
--- /dev/null
+++ b/components/WrapPanel2/src/Dependencies.props
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/WrapPanel2/src/MultiTarget.props b/components/WrapPanel2/src/MultiTarget.props
new file mode 100644
index 000000000..b11c19426
--- /dev/null
+++ b/components/WrapPanel2/src/MultiTarget.props
@@ -0,0 +1,9 @@
+
+
+
+ uwp;wasdk;wpf;wasm;linuxgtk;macos;ios;android;
+
+
\ No newline at end of file
diff --git a/components/WrapPanel2/src/OverflowBehavior.cs b/components/WrapPanel2/src/OverflowBehavior.cs
new file mode 100644
index 000000000..ebd22c0ca
--- /dev/null
+++ b/components/WrapPanel2/src/OverflowBehavior.cs
@@ -0,0 +1,21 @@
+// 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 CommunityToolkit.WinUI.Controls;
+
+///
+/// Describes the behavior of items that exceed the available space in the panel.
+///
+public enum OverflowBehavior
+{
+ ///
+ /// When an item exceeds the available space, it will be moved to a new row or column.
+ ///
+ Wrap,
+
+ ///
+ /// Items which do not fit within the available space will be removed from the layout.
+ ///
+ Drop,
+}
diff --git a/components/WrapPanel2/src/StretchChildren.cs b/components/WrapPanel2/src/StretchChildren.cs
new file mode 100644
index 000000000..80f35d084
--- /dev/null
+++ b/components/WrapPanel2/src/StretchChildren.cs
@@ -0,0 +1,36 @@
+// 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 CommunityToolkit.WinUI.Controls;
+
+///
+/// Describes the behavior for stretching childrenn in the wrap-panel.
+///
+public enum StretchChildren
+{
+ ///
+ /// Only star-sized items will ever be stretched.
+ ///
+ StarSizedOnly,
+
+ ///
+ /// In a row without star-sized items, the first item in the row will be stretched to fill the row.
+ ///
+ First,
+
+ ///
+ /// In a row without star-sized items, the last item in the row will be stretched to fill the row.
+ ///
+ Last,
+
+ ///
+ /// In a row without star-sized items, each item will be stretched to an equal size to fill the row.
+ ///
+ Equal,
+
+ ///
+ /// In a row without star-sized items, each item will be stretched proportional to their desired size to fill the row.
+ ///
+ Proportional,
+}
diff --git a/components/WrapPanel2/src/WrapPanel2.Properties.cs b/components/WrapPanel2/src/WrapPanel2.Properties.cs
new file mode 100644
index 000000000..864c402c4
--- /dev/null
+++ b/components/WrapPanel2/src/WrapPanel2.Properties.cs
@@ -0,0 +1,142 @@
+// 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 CommunityToolkit.WinUI.Controls;
+
+public partial class WrapPanel2
+{
+ ///
+ /// An attached property for identifying the requested layout of a child within the panel.
+ ///
+ public static readonly DependencyProperty LayoutLengthProperty =
+ DependencyProperty.Register(
+ "LayoutLength",
+ typeof(GridLength),
+ typeof(WrapPanel2),
+ new PropertyMetadata(GridLength.Auto));
+
+ ///
+ /// Backing for the property.
+ ///
+ public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
+ nameof(Orientation),
+ typeof(Orientation),
+ typeof(WrapPanel2),
+ new PropertyMetadata(Orientation.Horizontal, OnPropertyChanged));
+
+ ///
+ /// Backing for the property.
+ ///
+ public static readonly DependencyProperty ItemSpacingProperty = DependencyProperty.Register(
+ nameof(ItemSpacing),
+ typeof(double),
+ typeof(WrapPanel2),
+ new PropertyMetadata(default(double), OnPropertyChanged));
+
+ ///
+ /// Backing for the property.
+ ///
+ public static readonly DependencyProperty lineSpacingProperty = DependencyProperty.Register(
+ nameof(LineSpacing),
+ typeof(double),
+ typeof(WrapPanel2),
+ new PropertyMetadata(default(double), OnPropertyChanged));
+
+ ///
+ /// Backing for the property.
+ ///
+ public static readonly DependencyProperty FixedRowLengthsProperty = DependencyProperty.Register(
+ nameof(FixedRowLengths),
+ typeof(bool),
+ typeof(WrapPanel2),
+ new PropertyMetadata(default(bool), OnPropertyChanged));
+
+ ///
+ /// Backing for the property.
+ ///
+ public static readonly DependencyProperty StretchChildrenProperty = DependencyProperty.Register(
+ nameof(StretchChildren),
+ typeof(StretchChildren),
+ typeof(WrapPanel2),
+ new PropertyMetadata(default(StretchChildren), OnPropertyChanged));
+
+ ///
+ /// Backing for the property.
+ ///
+ public static readonly DependencyProperty OverflowBehaviorProperty = DependencyProperty.Register(
+ nameof(OverflowBehavior),
+ typeof(OverflowBehavior),
+ typeof(WrapPanel2),
+ new PropertyMetadata(default(OverflowBehavior), OnPropertyChanged));
+
+ ///
+ /// Gets or sets the panel orientation.
+ ///
+ public Orientation Orientation
+ {
+ get => (Orientation)GetValue(OrientationProperty);
+ set => SetValue(OrientationProperty, value);
+ }
+
+ ///
+ /// Gets or sets the spacing between items.
+ ///
+ public double ItemSpacing
+ {
+ get => (double)GetValue(ItemSpacingProperty);
+ set => SetValue(ItemSpacingProperty, value);
+ }
+
+ ///
+ /// Gets or sets the vertical spacing between items.
+ ///
+ public double LineSpacing
+ {
+ get => (double)GetValue(lineSpacingProperty);
+ set => SetValue(lineSpacingProperty, value);
+ }
+
+ ///
+ /// Gets or sets whether or not all rows/columns should stretch to match the length of the longest.
+ ///
+ public bool FixedRowLengths
+ {
+ get => (bool)GetValue(FixedRowLengthsProperty);
+ set => SetValue(FixedRowLengthsProperty, value);
+ }
+
+ ///
+ /// Gets or sets the method used to fill rows without a star-sized item.
+ ///
+ public StretchChildren StretchChildren
+ {
+ get => (StretchChildren)GetValue(StretchChildrenProperty);
+ set => SetValue(StretchChildrenProperty, value);
+ }
+
+ ///
+ /// Gets or sets how the panel handles content overflowing the available space.
+ ///
+ public OverflowBehavior OverflowBehavior
+ {
+ get => (OverflowBehavior)GetValue(OverflowBehaviorProperty);
+ set => SetValue(OverflowBehaviorProperty, value);
+ }
+
+ ///
+ /// Gets the of an item in the .
+ ///
+ public static GridLength GetLayoutLength(DependencyObject obj) => (GridLength)obj.GetValue(LayoutLengthProperty);
+
+ ///
+ /// Sets the of an item in the .
+ ///
+ public static void SetLayoutLength(DependencyObject obj, GridLength value) => obj.SetValue(LayoutLengthProperty, value);
+
+ private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var panel = (WrapPanel2)d;
+ panel.InvalidateMeasure();
+ }
+}
diff --git a/components/WrapPanel2/src/WrapPanel2.Structs.cs b/components/WrapPanel2/src/WrapPanel2.Structs.cs
new file mode 100644
index 000000000..57ab4a47c
--- /dev/null
+++ b/components/WrapPanel2/src/WrapPanel2.Structs.cs
@@ -0,0 +1,176 @@
+// 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 CommunityToolkit.WinUI.Controls;
+
+public partial class WrapPanel2
+{
+ ///
+ /// A struct representing the specifications of a row or column in the panel.
+ ///
+ private struct RowSpec
+ {
+ public RowSpec(GridLength layout, UVCoord desiredSize)
+ {
+ switch (layout.GridUnitType)
+ {
+ case GridUnitType.Auto:
+ ReservedSpace = desiredSize.U;
+ break;
+ case GridUnitType.Pixel:
+ ReservedSpace = layout.Value;
+ break;
+ case GridUnitType.Star:
+ PortionsSum = layout.Value;
+ MinPortionSize = desiredSize.U / layout.Value;
+ break;
+ }
+
+ MaxInAxisSize = desiredSize.U;
+ MaxOffAxisSize = desiredSize.V;
+ ItemsCount = 1;
+ }
+
+ ///
+ /// Gets the total reserved space for spacing in the row/column.
+ ///
+ ///
+ /// Items with a fixed size or auto size contribute to this value.
+ ///
+ public double ReservedSpace { get; private set; }
+
+ ///
+ /// Gets the sum of portions in the row/column.
+ ///
+ ///
+ /// Items with a star-sized length contribute to this value.
+ ///
+ public double PortionsSum { get; private set; }
+
+ ///
+ /// Gets the maximum width/height of items in the row/column.
+ ///
+ ///
+ /// Height in vertical orientation, width in horizontal orientation.
+ ///
+ public double MaxInAxisSize { get; private set; }
+
+ ///
+ /// Gets the maximum width/height of items in the row/column.
+ ///
+ ///
+ /// Width in vertical orientation, height in horizontal orientation.
+ ///
+ public double MaxOffAxisSize { get; private set; }
+
+ ///
+ /// Gets the minimum size of a portion in the row/column.
+ ///
+ public double MinPortionSize { get; private set; }
+
+ ///
+ /// Gets the number of items in the row/column.
+ ///
+ public int ItemsCount { get; private set; }
+
+ public bool TryAdd(RowSpec addend, double spacing, double maxSize, bool equalStretching)
+ {
+ // Check if adding the new spec would exceed the maximum size
+ var sum = this + addend;
+ if (sum.Measure(spacing, equalStretching) > maxSize)
+ return false;
+
+ // Update the current spec to include the new spec
+ this = sum;
+ return true;
+ }
+
+ public readonly double Measure(double spacing, bool equalStretching)
+ {
+ var totalSpacing = (ItemsCount - 1) * spacing;
+
+ // Handle equal-sized items child stretching.
+ // Without this check, children might become scrunched in the arrange
+ // step when they are made equal sizes.
+ if (equalStretching && PortionsSum is 0)
+ {
+ return (MaxInAxisSize * ItemsCount) + totalSpacing;
+ }
+
+ // Otherwise, base size is reserved space + spacing
+ var totalSize = ReservedSpace + totalSpacing;
+
+ // Also add star-sized items if applicable
+ if (!double.IsNaN(MinPortionSize) && !double.IsInfinity(MinPortionSize))
+ totalSize += MinPortionSize * PortionsSum;
+
+
+ return totalSize;
+ }
+
+ public static RowSpec operator +(RowSpec a, RowSpec b)
+ {
+ var combined = new RowSpec
+ {
+ ReservedSpace = a.ReservedSpace + b.ReservedSpace,
+ PortionsSum = a.PortionsSum + b.PortionsSum,
+ MinPortionSize = Math.Max(a.MinPortionSize, b.MinPortionSize),
+ MaxInAxisSize = Math.Max(a.MaxInAxisSize, b.MaxInAxisSize),
+ MaxOffAxisSize = Math.Max(a.MaxOffAxisSize, b.MaxOffAxisSize),
+ ItemsCount = a.ItemsCount + b.ItemsCount
+ };
+ return combined;
+ }
+ }
+
+ ///
+ /// A struct for mapping X/Y coordinates to an orientation adjusted U/V coordinate system.
+ ///
+ private struct UVCoord(double x, double y, Orientation orientation)
+ {
+ private readonly bool _horizontal = orientation is Orientation.Horizontal;
+
+ public UVCoord(Size size, Orientation orientation) : this(size.Width, size.Height, orientation)
+ {
+ }
+
+ public double X { get; set; } = x;
+
+ public double Y { get; set; } = y;
+
+ public double U
+ {
+ readonly get => _horizontal ? X : Y;
+ set
+ {
+ if (_horizontal)
+ {
+ X = value;
+ }
+ else
+ {
+ Y = value;
+ }
+ }
+ }
+
+ public double V
+ {
+ readonly get => _horizontal ? Y : X;
+ set
+ {
+ if (_horizontal)
+ {
+ Y = value;
+ }
+ else
+ {
+ X = value;
+ }
+ }
+ }
+
+ public readonly Size Size => new(X, Y);
+ }
+}
diff --git a/components/WrapPanel2/src/WrapPanel2.cs b/components/WrapPanel2/src/WrapPanel2.cs
new file mode 100644
index 000000000..15f95714e
--- /dev/null
+++ b/components/WrapPanel2/src/WrapPanel2.cs
@@ -0,0 +1,330 @@
+// 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 CommunityToolkit.WinUI.Controls;
+
+///
+/// A panel that arranges its children in a grid-like fashion, stretching them to fill available space.
+///
+public partial class WrapPanel2 : Panel
+{
+ private List? _rowSpecs;
+ private double _longestRowSize = 0;
+
+ ///
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ _rowSpecs = [];
+ _longestRowSize = 0;
+
+ // Define XY/UV coordinate variables
+ var uvAvailableSize = new UVCoord(availableSize.Width, availableSize.Height, Orientation);
+
+ RowSpec currentRowSpec = default;
+
+ var elements = Children.Where(static e => e.Visibility is Visibility.Visible);
+
+ // Do nothing if the panel is empty
+ if (!elements.Any())
+ {
+ return new Size(0, 0);
+ }
+
+ // Adjusted measuring will be required if fixed row lengths is enabled and
+ // the StretchChildren is set to Equal. Condense this into a bool here.
+ bool equalStretching = FixedRowLengths && StretchChildren is StretchChildren.Equal;
+
+ foreach (var child in elements)
+ {
+ // Measure the child's desired size and get layout
+ child.Measure(availableSize);
+ var uvDesiredSize = new UVCoord(child.DesiredSize, Orientation);
+ var layoutLength = GetLayoutLength(child);
+
+ // Attempt to add the child to the current row/column
+ var spec = new RowSpec(layoutLength, uvDesiredSize);
+ if (!currentRowSpec.TryAdd(spec, ItemSpacing, uvAvailableSize.U, equalStretching))
+ {
+ // If the overflow behavior is drop, just end the row here.
+ if (OverflowBehavior is OverflowBehavior.Drop)
+ break;
+
+ // Could not add to current row/column
+ // Start a new row/column
+ _rowSpecs.Add(currentRowSpec);
+ _longestRowSize = Math.Max(_longestRowSize, currentRowSpec.Measure(ItemSpacing, equalStretching));
+ currentRowSpec = spec;
+ }
+ }
+
+ // Add the final row/column
+ _rowSpecs.Add(currentRowSpec);
+ _longestRowSize = Math.Max(_longestRowSize, currentRowSpec.Measure(ItemSpacing, equalStretching));
+
+ // Calculate final desired size
+ var uvSize = new UVCoord(0, 0, Orientation)
+ {
+ U = IsMainAxisStretch(uvAvailableSize.U) ? uvAvailableSize.U : _longestRowSize,
+ V = _rowSpecs.Sum(static rs => rs.MaxOffAxisSize) + (LineSpacing * (_rowSpecs.Count - 1))
+ };
+
+ // Clamp to available size and return
+ uvSize.U = Math.Min(uvSize.U, uvAvailableSize.U);
+ uvSize.V = Math.Min(uvSize.V, uvAvailableSize.V);
+ return uvSize.Size;
+ }
+
+ ///
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ // Do nothing if there are no rows/columns
+ if (_rowSpecs is null || _rowSpecs.Count is 0)
+ return new Size(0, 0);
+
+ // Create XY/UV coordinate variables
+ var pos = new UVCoord(0, 0, Orientation);
+ var uvFinalSize = new UVCoord(finalSize, Orientation);
+
+ // Adjust the starting position based on off-axis alignment
+ var contentHeight = _rowSpecs.Sum(static rs => rs.MaxOffAxisSize) + (LineSpacing * (_rowSpecs.Count - 1));
+ pos.V = GetStartByAlignment(GetOffAlignment(), contentHeight, uvFinalSize.V);
+
+ var childQueue = new Queue(Children.Where(static e => e.Visibility is Visibility.Visible));
+
+ foreach (var row in _rowSpecs)
+ {
+ // Arrange the row/column
+ ArrangeRow(ref pos, row, uvFinalSize, childQueue);
+ }
+
+ // "Arrange" remaning children by rendering them with zero size
+ while (childQueue.TryDequeue(out var child))
+ {
+ // Arrange with zero size
+ child.Arrange(new Rect(0, 0, 0, 0));
+ }
+
+ return finalSize;
+ }
+
+ private void ArrangeRow(ref UVCoord pos, RowSpec row, UVCoord uvFinalSize, Queue childQueue)
+ {
+ var spacingTotalSize = ItemSpacing * (row.ItemsCount - 1);
+ var remainingSpace = uvFinalSize.U - row.ReservedSpace - spacingTotalSize;
+ var portionSize = row.MinPortionSize;
+
+ // Determine if the desired alignment is stretched.
+ // Or if fixed row lengths are in use.
+ bool stretch = IsMainAxisStretch(uvFinalSize.U) || FixedRowLengths;
+
+ // Calculate portion size if stretching
+ // Same logic applies for matching row lengths, since the size was determined during measure
+ if (stretch)
+ {
+ portionSize = remainingSpace / row.PortionsSum;
+ }
+
+ // Reset the starting U position
+ pos.U = 0;
+
+ // Adjust the starting position if not stretching
+ // Also do this if there are no star-sized items in the row/column and no forced streching is in use.
+ if (!stretch || (row.PortionsSum is 0 && StretchChildren is StretchChildren.StarSizedOnly))
+ {
+ var rowSize = row.Measure(ItemSpacing, false);
+ pos.U = GetStartByAlignment(GetAlignment(), rowSize, uvFinalSize.U);
+ }
+
+ // Set a flag for if the row is being forced to stretch
+ bool forceStretch = FixedRowLengths && row.PortionsSum is 0 && StretchChildren is not StretchChildren.StarSizedOnly;
+
+ // Setup portionSize for forced stretching
+ if (forceStretch)
+ {
+ portionSize = StretchChildren switch
+ {
+ // The first child's size will be overridden to 1*
+ // Change portion size to fill remaining space plus its original size
+ StretchChildren.First =>
+ remainingSpace + GetChildSize(childQueue.Peek()),
+
+ // The last child's size will be overridden to 1*
+ // Change portion size to fill remaining space plus its original size
+ StretchChildren.Last =>
+ remainingSpace + GetChildSize(childQueue.ElementAt(row.ItemsCount - 1)),
+
+ // All children's sizes will be overridden to 1*
+ // Change portion size to evenly distribute remaining space
+ StretchChildren.Equal =>
+ (uvFinalSize.U - spacingTotalSize) / row.ItemsCount,
+
+ // All children's sizes will be overridden to star sizes proportional to their original size
+ // Change portion size to distribute remaining space proportionally
+ StretchChildren.Proportional =>
+ (uvFinalSize.U - spacingTotalSize) / row.ReservedSpace,
+
+ // Default case (should not be hit)
+ _ => row.MinPortionSize,
+ };
+ }
+
+ // Arrange each child in the row/column
+ for (int i = 0; i < row.ItemsCount; i++)
+ {
+ // Get the next child
+ var child = childQueue.Dequeue();
+
+ // Sanity check
+ if (child is null)
+ return;
+
+ // Determine the child's size
+ var size = GetChildSize(child, i, row, portionSize, forceStretch);
+
+ // NOTE: The arrange method is still in X/Y coordinate system
+ child.Arrange(new Rect(pos.X, pos.Y, size.X, size.Y));
+
+ // Advance the position
+ pos.U += size.U + ItemSpacing;
+ }
+
+ // Advance to the next row/column
+ pos.V += row.MaxOffAxisSize + LineSpacing;
+ }
+
+ private UVCoord GetChildSize(UIElement child, int indexInRow, RowSpec row, double portionSize, bool forceStretch)
+ {
+ // Get layout and desired size
+ var layoutLength = GetLayoutLength(child);
+ var uvDesiredSize = new UVCoord(child.DesiredSize, Orientation);
+
+ // Override the layout based on the forced stretch method if necessary
+ if (forceStretch)
+ {
+ var oneStar = new GridLength(1, GridUnitType.Star);
+ layoutLength = StretchChildren switch
+ {
+ // Override the first item's layout to 1*
+ StretchChildren.First when indexInRow is 0 => oneStar,
+
+ // Override the last item's layout to 1*
+ StretchChildren.Last when indexInRow == (row.ItemsCount - 1) => oneStar,
+
+ // Override all item's layouts to 1*
+ StretchChildren.Equal => oneStar,
+
+ // Override all item's layouts to star sizes proportional to their original size
+ StretchChildren.Proportional => layoutLength.GridUnitType switch
+ {
+ GridUnitType.Auto => new GridLength(uvDesiredSize.U, GridUnitType.Star),
+ GridUnitType.Pixel or _ => new GridLength(layoutLength.Value, GridUnitType.Star),
+ },
+
+ // If the above conditions aren't met, do nothing
+ _ => layoutLength,
+ };
+ }
+
+ // Determine the child's U size
+ double uSize = layoutLength.GridUnitType switch
+ {
+ GridUnitType.Auto => uvDesiredSize.U,
+ GridUnitType.Pixel => layoutLength.Value,
+ GridUnitType.Star => layoutLength.Value * portionSize,
+ _ => uvDesiredSize.U,
+ };
+
+ // Return the final size
+ return new UVCoord(0, 0, Orientation)
+ {
+ U = uSize,
+ V = row.MaxOffAxisSize
+ };
+ }
+
+ private static double GetStartByAlignment(Alignment alignment, double size, double availableSize)
+ {
+ return alignment switch
+ {
+ Alignment.Start => 0,
+ Alignment.Center => (availableSize / 2) - (size / 2),
+ Alignment.End => availableSize - size,
+ _ => 0,
+ };
+ }
+
+ private Alignment GetAlignment()
+ {
+ return Orientation switch
+ {
+ Orientation.Horizontal => HorizontalAlignment switch
+ {
+ HorizontalAlignment.Left => Alignment.Start,
+ HorizontalAlignment.Center => Alignment.Center,
+ HorizontalAlignment.Right => Alignment.End,
+ HorizontalAlignment.Stretch => Alignment.Stretch,
+ _ => Alignment.Start,
+ },
+ Orientation.Vertical => VerticalAlignment switch
+ {
+ VerticalAlignment.Top => Alignment.Start,
+ VerticalAlignment.Center => Alignment.Center,
+ VerticalAlignment.Bottom => Alignment.End,
+ VerticalAlignment.Stretch => Alignment.Stretch,
+ _ => Alignment.Start,
+ },
+ _ => Alignment.Start,
+ };
+ }
+
+ private Alignment GetOffAlignment()
+ {
+ return Orientation switch
+ {
+ Orientation.Horizontal => VerticalAlignment switch
+ {
+ VerticalAlignment.Top => Alignment.Start,
+ VerticalAlignment.Center => Alignment.Center,
+ VerticalAlignment.Bottom => Alignment.End,
+ VerticalAlignment.Stretch => Alignment.Stretch,
+ _ => Alignment.Start,
+ },
+ Orientation.Vertical => HorizontalAlignment switch
+ {
+ HorizontalAlignment.Left => Alignment.Start,
+ HorizontalAlignment.Center => Alignment.Center,
+ HorizontalAlignment.Right => Alignment.End,
+ HorizontalAlignment.Stretch => Alignment.Stretch,
+ _ => Alignment.Start,
+ },
+ _ => Alignment.Start,
+ };
+ }
+
+ ///
+ /// Determine if the desired alignment is stretched.
+ /// Don't stretch if infinite space is available though. Attempting to divide infinite space will result in a crash.
+ ///
+ private bool IsMainAxisStretch(double availableSize) => GetAlignment() is Alignment.Stretch && !double.IsInfinity(availableSize);
+
+ private double GetChildSize(UIElement child)
+ {
+ var childLayout = GetLayoutLength(child);
+
+ return childLayout.GridUnitType switch
+ {
+ GridUnitType.Auto => new UVCoord(child.DesiredSize, Orientation).U,
+ GridUnitType.Pixel => childLayout.Value,
+ _ => 0,
+ };
+ }
+
+ private enum Alignment
+ {
+ Start,
+ Center,
+ End,
+ Stretch
+ }
+}
diff --git a/components/WrapPanel2/tests/ExampleWrapPanel2TestClass.cs b/components/WrapPanel2/tests/ExampleWrapPanel2TestClass.cs
new file mode 100644
index 000000000..5a41388c8
--- /dev/null
+++ b/components/WrapPanel2/tests/ExampleWrapPanel2TestClass.cs
@@ -0,0 +1,134 @@
+// 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.Tooling.TestGen;
+using CommunityToolkit.Tests;
+using CommunityToolkit.WinUI.Controls;
+
+namespace WrapPanel2Tests;
+
+[TestClass]
+public partial class ExampleWrapPanel2TestClass : VisualUITestBase
+{
+ // If you don't need access to UI objects directly or async code, use this pattern.
+ [TestMethod]
+ public void SimpleSynchronousExampleTest()
+ {
+ var assembly = typeof(WrapPanel2).Assembly;
+ var type = assembly.GetType(typeof(WrapPanel2).FullName ?? string.Empty);
+
+ Assert.IsNotNull(type, "Could not find WrapPanel2 type.");
+ Assert.AreEqual(typeof(WrapPanel2), type, "Type of WrapPanel2 does not match expected type.");
+ }
+
+ // If you don't need access to UI objects directly, use this pattern.
+ [TestMethod]
+ public async Task SimpleAsyncExampleTest()
+ {
+ await Task.Delay(250);
+
+ Assert.IsTrue(true);
+ }
+
+ // Example that shows how to check for exception throwing.
+ [TestMethod]
+ public void SimpleExceptionCheckTest()
+ {
+ // If you need to check exceptions occur for invalid inputs, etc...
+ // Use Assert.ThrowsException to limit the scope to where you expect the error to occur.
+ // Otherwise, using the ExpectedException attribute could swallow or
+ // catch other issues in setup code.
+ Assert.ThrowsException(() => throw new NotImplementedException());
+ }
+
+ // The UIThreadTestMethod automatically dispatches to the UI for us to work with UI objects.
+ [UIThreadTestMethod]
+ public void SimpleUIAttributeExampleTest()
+ {
+ var component = new WrapPanel2();
+ Assert.IsNotNull(component);
+ }
+
+ // The UIThreadTestMethod can also easily grab a XAML Page for us by passing its type as a parameter.
+ // This lets us actually test a control as it would behave within an actual application.
+ // The page will already be loaded by the time your test is called.
+ [UIThreadTestMethod]
+ public void SimpleUIExamplePageTest(ExampleWrapPanel2TestPage page)
+ {
+ // You can use the Toolkit Visual Tree helpers here to find the component by type or name:
+ var component = page.FindDescendant();
+
+ Assert.IsNotNull(component);
+
+ var componentByName = page.FindDescendant("WrapPanel2Control");
+
+ Assert.IsNotNull(componentByName);
+ }
+
+ // You can still do async work with a UIThreadTestMethod as well.
+ [UIThreadTestMethod]
+ public async Task SimpleAsyncUIExamplePageTest(ExampleWrapPanel2TestPage page)
+ {
+ // This helper can be used to wait for a rendering pass to complete.
+ // Note, this is already done by loading a Page with the [UIThreadTestMethod] helper.
+ await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });
+
+ var component = page.FindDescendant();
+
+ Assert.IsNotNull(component);
+ }
+
+ //// ----------------------------- ADVANCED TEST SCENARIOS -----------------------------
+
+ // If you need to use DataRow, you can use this pattern with the UI dispatch still.
+ // Otherwise, checkout the UIThreadTestMethod attribute above.
+ // See https://github.com/CommunityToolkit/Labs-Windows/issues/186
+ [TestMethod]
+ public async Task ComplexAsyncUIExampleTest()
+ {
+ await EnqueueAsync(() =>
+ {
+ var component = new WrapPanel2();
+ Assert.IsNotNull(component);
+ });
+ }
+
+ // If you want to load other content not within a XAML page using the UIThreadTestMethod above.
+ // Then you can do that using the Load/UnloadTestContentAsync methods.
+ [TestMethod]
+ public async Task ComplexAsyncLoadUIExampleTest()
+ {
+ await EnqueueAsync(async () =>
+ {
+ var component = new WrapPanel2();
+ Assert.IsNotNull(component);
+ Assert.IsFalse(component.IsLoaded);
+
+ await LoadTestContentAsync(component);
+
+ Assert.IsTrue(component.IsLoaded);
+
+ await UnloadTestContentAsync(component);
+
+ Assert.IsFalse(component.IsLoaded);
+ });
+ }
+
+ // You can still use the UIThreadTestMethod to remove the extra layer for the dispatcher as well:
+ [UIThreadTestMethod]
+ public async Task ComplexAsyncLoadUIExampleWithoutDispatcherTest()
+ {
+ var component = new WrapPanel2();
+ Assert.IsNotNull(component);
+ Assert.IsFalse(component.IsLoaded);
+
+ await LoadTestContentAsync(component);
+
+ Assert.IsTrue(component.IsLoaded);
+
+ await UnloadTestContentAsync(component);
+
+ Assert.IsFalse(component.IsLoaded);
+ }
+}
diff --git a/components/WrapPanel2/tests/ExampleWrapPanel2TestPage.xaml b/components/WrapPanel2/tests/ExampleWrapPanel2TestPage.xaml
new file mode 100644
index 000000000..5e5b69b41
--- /dev/null
+++ b/components/WrapPanel2/tests/ExampleWrapPanel2TestPage.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/components/WrapPanel2/tests/ExampleWrapPanel2TestPage.xaml.cs b/components/WrapPanel2/tests/ExampleWrapPanel2TestPage.xaml.cs
new file mode 100644
index 000000000..0272a9498
--- /dev/null
+++ b/components/WrapPanel2/tests/ExampleWrapPanel2TestPage.xaml.cs
@@ -0,0 +1,16 @@
+// 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 WrapPanel2Tests;
+
+///
+/// An empty page that can be used on its own or navigated to within a Frame.
+///
+public sealed partial class ExampleWrapPanel2TestPage : Page
+{
+ public ExampleWrapPanel2TestPage()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/WrapPanel2/tests/WrapPanel2.Tests.projitems b/components/WrapPanel2/tests/WrapPanel2.Tests.projitems
new file mode 100644
index 000000000..5dd673b98
--- /dev/null
+++ b/components/WrapPanel2/tests/WrapPanel2.Tests.projitems
@@ -0,0 +1,23 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ 1EFF9838-CA24-43C3-AA4F-0B321F74861B
+
+
+ WrapPanel2Tests
+
+
+
+
+ ExampleWrapPanel2TestPage.xaml
+
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+
\ No newline at end of file
diff --git a/components/WrapPanel2/tests/WrapPanel2.Tests.shproj b/components/WrapPanel2/tests/WrapPanel2.Tests.shproj
new file mode 100644
index 000000000..8db9f9ff5
--- /dev/null
+++ b/components/WrapPanel2/tests/WrapPanel2.Tests.shproj
@@ -0,0 +1,13 @@
+
+
+
+ 1EFF9838-CA24-43C3-AA4F-0B321F74861B
+ 14.0
+
+
+
+
+
+
+
+