diff --git a/CommunityToolkit.Diagnostics/Guard.String.cs b/CommunityToolkit.Diagnostics/Guard.String.cs
index 3aa138ad2fc..d7c11150c30 100644
--- a/CommunityToolkit.Diagnostics/Guard.String.cs
+++ b/CommunityToolkit.Diagnostics/Guard.String.cs
@@ -37,7 +37,8 @@ public static void IsNullOrEmpty(string? text, string name)
///
/// The input instance to test.
/// The name of the input parameter being tested.
- /// Thrown if is or empty.
+ /// Thrown if is .
+ /// Thrown if is empty.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IsNotNullOrEmpty([NotNull] string? text, string name)
{
@@ -89,7 +90,8 @@ public static void IsNullOrWhitespace(string? text, string name)
///
/// The input instance to test.
/// The name of the input parameter being tested.
- /// Thrown if is or whitespace.
+ /// Thrown if is .
+ /// Thrown if is whitespace.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IsNotNullOrWhiteSpace([NotNull] string? text, string name)
{
diff --git a/CommunityToolkit.Diagnostics/Internals/Guard.String.ThrowHelper.cs b/CommunityToolkit.Diagnostics/Internals/Guard.String.ThrowHelper.cs
index 1c471d25b45..feaebb1ec8b 100644
--- a/CommunityToolkit.Diagnostics/Internals/Guard.String.ThrowHelper.cs
+++ b/CommunityToolkit.Diagnostics/Internals/Guard.String.ThrowHelper.cs
@@ -4,6 +4,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
namespace CommunityToolkit.Diagnostics
{
@@ -27,12 +28,23 @@ public static void ThrowArgumentExceptionForIsNullOrEmpty(string? text, string n
}
///
- /// Throws an when fails.
+ /// Throws an or when fails.
///
[DoesNotReturn]
public static void ThrowArgumentExceptionForIsNotNullOrEmpty(string? text, string name)
{
- throw new ArgumentException($"Parameter {AssertString(name)} (string) must not be null or empty, was {(text is null ? "null" : "empty")}", name);
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static Exception GetException(string? text, string name)
+ {
+ if (text is null)
+ {
+ return new ArgumentNullException(name, $"Parameter {AssertString(name)} (string) must not be null or empty, was null");
+ }
+
+ return new ArgumentException($"Parameter {AssertString(name)} (string) must not be null or empty, was empty", name);
+ }
+
+ throw GetException(text, name);
}
///
@@ -50,7 +62,18 @@ public static void ThrowArgumentExceptionForIsNullOrWhiteSpace(string? text, str
[DoesNotReturn]
public static void ThrowArgumentExceptionForIsNotNullOrWhiteSpace(string? text, string name)
{
- throw new ArgumentException($"Parameter {AssertString(name)} (string) must not be null or whitespace, was {(text is null ? "null" : "whitespace")}", name);
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static Exception GetException(string? text, string name)
+ {
+ if (text is null)
+ {
+ return new ArgumentNullException(name, $"Parameter {AssertString(name)} (string) must not be null or whitespace, was null");
+ }
+
+ return new ArgumentException($"Parameter {AssertString(name)} (string) must not be null or whitespace, was whitespace", name);
+ }
+
+ throw GetException(text, name);
}
///
diff --git a/CommunityToolkit.WinUI.SampleApp/SamplePages/ViewportBehavior/ViewportBehaviorXaml.bind b/CommunityToolkit.WinUI.SampleApp/SamplePages/ViewportBehavior/ViewportBehaviorXaml.bind
index a9784441edc..1c3c1ff836a 100644
--- a/CommunityToolkit.WinUI.SampleApp/SamplePages/ViewportBehavior/ViewportBehaviorXaml.bind
+++ b/CommunityToolkit.WinUI.SampleApp/SamplePages/ViewportBehavior/ViewportBehaviorXaml.bind
@@ -14,7 +14,7 @@
Height="200"
Background="Gray">
-
+
+
diff --git a/CommunityToolkit.WinUI.UI.Behaviors/Viewport/ViewportBehavior.Properties.cs b/CommunityToolkit.WinUI.UI.Behaviors/Viewport/ViewportBehavior.Properties.cs
new file mode 100644
index 00000000000..9bc31d9dbf0
--- /dev/null
+++ b/CommunityToolkit.WinUI.UI.Behaviors/Viewport/ViewportBehavior.Properties.cs
@@ -0,0 +1,106 @@
+// 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 System;
+using Microsoft.UI.Xaml;
+using Microsoft.Xaml.Interactivity;
+
+namespace CommunityToolkit.WinUI.UI.Behaviors
+{
+ ///
+ /// A class for listening to an element enter or exit the ScrollViewer viewport
+ ///
+ public partial class ViewportBehavior
+ {
+ ///
+ /// The IsFullyInViewport value of the associated element
+ ///
+ public static readonly DependencyProperty IsFullyInViewportProperty =
+ DependencyProperty.Register(nameof(IsFullyInViewport), typeof(bool), typeof(ViewportBehavior), new PropertyMetadata(default(bool), OnIsFullyInViewportChanged));
+
+ ///
+ /// The IsInViewport value of the associated element
+ ///
+ public static readonly DependencyProperty IsInViewportProperty =
+ DependencyProperty.Register(nameof(IsInViewport), typeof(bool), typeof(ViewportBehavior), new PropertyMetadata(default(bool), OnIsInViewportChanged));
+
+ ///
+ /// The IsAlwaysOn value of the associated element
+ ///
+ public static readonly DependencyProperty IsAlwaysOnProperty =
+ DependencyProperty.Register(nameof(IsAlwaysOn), typeof(bool), typeof(ViewportBehavior), new PropertyMetadata(default(bool)));
+
+ ///
+ /// Gets or sets a value indicating whether this behavior will remain attached after the associated element enters the viewport. When false, the behavior will remove itself after entering.
+ ///
+ public bool IsAlwaysOn
+ {
+ get { return (bool)GetValue(IsAlwaysOnProperty); }
+ set { SetValue(IsAlwaysOnProperty, value); }
+ }
+
+ ///
+ /// Gets a value indicating whether associated element is fully in the ScrollViewer viewport
+ ///
+ public bool IsFullyInViewport
+ {
+ get { return (bool)GetValue(IsFullyInViewportProperty); }
+ private set { SetValue(IsFullyInViewportProperty, value); }
+ }
+
+ ///
+ /// Gets a value indicating whether associated element is in the ScrollViewer viewport
+ ///
+ public bool IsInViewport
+ {
+ get { return (bool)GetValue(IsInViewportProperty); }
+ private set { SetValue(IsInViewportProperty, value); }
+ }
+
+ ///
+ /// Event tracking when the object is fully within the viewport or not
+ ///
+ /// DependencyObject
+ /// EventArgs
+ private static void OnIsFullyInViewportChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var obj = (ViewportBehavior)d;
+ var value = (bool)e.NewValue;
+
+ if (value)
+ {
+ obj.EnteredViewport?.Invoke(obj.AssociatedObject, EventArgs.Empty);
+
+ if (!obj.IsAlwaysOn)
+ {
+ Interaction.GetBehaviors(obj.AssociatedObject).Remove(obj);
+ }
+ }
+ else
+ {
+ obj.ExitingViewport?.Invoke(obj.AssociatedObject, EventArgs.Empty);
+ }
+ }
+
+ ///
+ /// Event tracking the state of the object as it moves into and out of the viewport
+ ///
+ /// DependencyObject
+ /// EventArgs
+ private static void OnIsInViewportChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var obj = (ViewportBehavior)d;
+ var value = (bool)e.NewValue;
+
+ if (value)
+ {
+ obj.EnteringViewport?.Invoke(obj.AssociatedObject, EventArgs.Empty);
+ }
+ else
+ {
+ obj.ExitedViewport?.Invoke(obj.AssociatedObject, EventArgs.Empty);
+ }
+ }
+ }
+}
diff --git a/CommunityToolkit.WinUI.UI.Behaviors/Viewport/ViewportBehavior.cs b/CommunityToolkit.WinUI.UI.Behaviors/Viewport/ViewportBehavior.cs
index 386201ab8f6..2164268b757 100644
--- a/CommunityToolkit.WinUI.UI.Behaviors/Viewport/ViewportBehavior.cs
+++ b/CommunityToolkit.WinUI.UI.Behaviors/Viewport/ViewportBehavior.cs
@@ -12,33 +12,15 @@
namespace CommunityToolkit.WinUI.UI.Behaviors
{
///
- /// A class for listening element enter or exit the ScrollViewer viewport
+ /// A class for listening to an element enter or exit the ScrollViewer viewport
///
- public class ViewportBehavior : BehaviorBase
+ public partial class ViewportBehavior : BehaviorBase
{
///
/// The ScrollViewer hosting this element.
///
private ScrollViewer _hostScrollViewer;
- ///
- /// The IsFullyInViewport value of the associated element
- ///
- public static readonly DependencyProperty IsFullyInViewportProperty =
- DependencyProperty.Register(nameof(IsFullyInViewport), typeof(bool), typeof(ViewportBehavior), new PropertyMetadata(default(bool), OnIsFullyInViewportChanged));
-
- ///
- /// The IsInViewport value of the associated element
- ///
- public static readonly DependencyProperty IsInViewportProperty =
- DependencyProperty.Register(nameof(IsInViewport), typeof(bool), typeof(ViewportBehavior), new PropertyMetadata(default(bool), OnIsInViewportChanged));
-
- ///
- /// The IsAlwaysOn value of the associated element
- ///
- public static readonly DependencyProperty IsAlwaysOnProperty =
- DependencyProperty.Register(nameof(IsAlwaysOn), typeof(bool), typeof(ViewportBehavior), new PropertyMetadata(default(bool)));
-
///
/// Associated element fully enter the ScrollViewer viewport event
///
@@ -59,33 +41,6 @@ public class ViewportBehavior : BehaviorBase
///
public event EventHandler ExitingViewport;
- ///
- /// Gets or sets a value indicating whether this behavior will remain attached after the associated element enters the viewport. When false, the behavior will remove itself after entering.
- ///
- public bool IsAlwaysOn
- {
- get { return (bool)GetValue(IsAlwaysOnProperty); }
- set { SetValue(IsAlwaysOnProperty, value); }
- }
-
- ///
- /// Gets a value indicating whether associated element is fully in the ScrollViewer viewport
- ///
- public bool IsFullyInViewport
- {
- get { return (bool)GetValue(IsFullyInViewportProperty); }
- private set { SetValue(IsFullyInViewportProperty, value); }
- }
-
- ///
- /// Gets a value indicating whether associated element is in the ScrollViewer viewport
- ///
- public bool IsInViewport
- {
- get { return (bool)GetValue(IsInViewportProperty); }
- private set { SetValue(IsInViewportProperty, value); }
- }
-
///
/// Called after the behavior is attached to the .
///
@@ -120,39 +75,6 @@ protected override void OnDetaching()
_hostScrollViewer = null;
}
- private static void OnIsFullyInViewportChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- var obj = (ViewportBehavior)d;
- var value = (bool)e.NewValue;
- if (value)
- {
- obj.EnteredViewport?.Invoke(obj.AssociatedObject, EventArgs.Empty);
-
- if (!obj.IsAlwaysOn)
- {
- Interaction.GetBehaviors(obj.AssociatedObject).Remove(obj);
- }
- }
- else
- {
- obj.ExitingViewport?.Invoke(obj.AssociatedObject, EventArgs.Empty);
- }
- }
-
- private static void OnIsInViewportChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- var obj = (ViewportBehavior)d;
- var value = (bool)e.NewValue;
- if (value)
- {
- obj.EnteringViewport?.Invoke(obj.AssociatedObject, EventArgs.Empty);
- }
- else
- {
- obj.ExitedViewport?.Invoke(obj.AssociatedObject, EventArgs.Empty);
- }
- }
-
private void ParentScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
var associatedElementRect = AssociatedObject.TransformToVisual(_hostScrollViewer)
diff --git a/CommunityToolkit.WinUI.UI.Controls.Primitives/ConstrainedBox/AspectRatio.cs b/CommunityToolkit.WinUI.UI.Controls.Primitives/ConstrainedBox/AspectRatio.cs
index 8cbaf0a58b8..4f528d81672 100644
--- a/CommunityToolkit.WinUI.UI.Controls.Primitives/ConstrainedBox/AspectRatio.cs
+++ b/CommunityToolkit.WinUI.UI.Controls.Primitives/ConstrainedBox/AspectRatio.cs
@@ -2,6 +2,7 @@
// 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;
using System.Globalization;
namespace CommunityToolkit.WinUI.UI.Controls
@@ -63,6 +64,13 @@ public AspectRatio(double ratio)
/// value representing the .
public static implicit operator AspectRatio(double ratio) => new AspectRatio(ratio);
+ ///
+ /// Implicit conversion operator to convert a to an value.
+ /// Creates a simple aspect ratio of N:1, where N is int
+ ///
+ /// value representing the .
+ public static implicit operator AspectRatio(int width) => new AspectRatio(width, 1.0);
+
///
/// Converter to take a string aspect ration like "16:9" and convert it to an struct.
/// Used automatically by XAML.
diff --git a/UITests/UITests.App/UITests.App.csproj b/UITests/UITests.App/UITests.App.csproj
index 417ae6b273c..f7aa6a031ac 100644
--- a/UITests/UITests.App/UITests.App.csproj
+++ b/UITests/UITests.App/UITests.App.csproj
@@ -102,6 +102,10 @@
{e9faabfb-d726-42c1-83c1-cb46a29fea81}
CommunityToolkit.WinUI.UI.Controls.Core
+
+ {84ab7dc5-95c9-4cf8-a370-d077e9e9ef1a}
+ CommunityToolkit.WinUI.UI.Controls.Primitives
+
{75f9ee44-3efa-47bc-aedd-351b9834a0af}
CommunityToolkit.WinUI.UI.Media
diff --git a/UITests/UITests.Tests.Shared/Controls/ConstrainedBoxTest.cs b/UITests/UITests.Tests.Shared/Controls/ConstrainedBoxTest.cs
new file mode 100644
index 00000000000..275180aae9c
--- /dev/null
+++ b/UITests/UITests.Tests.Shared/Controls/ConstrainedBoxTest.cs
@@ -0,0 +1,42 @@
+// 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 Microsoft.UI.Xaml.Tests.MUXControls.InteractionTests.Common;
+using Microsoft.UI.Xaml.Tests.MUXControls.InteractionTests.Infra;
+using Microsoft.Windows.Apps.Test.Automation;
+using Microsoft.Windows.Apps.Test.Foundation;
+using Microsoft.Windows.Apps.Test.Foundation.Controls;
+
+#if USING_TAEF
+using WEX.Logging.Interop;
+using WEX.TestExecution;
+using WEX.TestExecution.Markup;
+#else
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+#endif
+
+namespace UITests.Tests
+{
+ [TestClass]
+ public class ConstrainedBoxTest : UITestBase
+ {
+ [ClassInitialize]
+ [TestProperty("RunAs", "User")]
+ [TestProperty("Classification", "ScenarioTestSuite")]
+ [TestProperty("Platform", "Any")]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ TestEnvironment.Initialize(testContext, UITestsAppSampleApp);
+ }
+
+ [TestMethod]
+ [TestPage("ConstrainedBoxTestPage")]
+ public void Test_AspectRatioBoundToInteger_Placeholder()
+ {
+ // The test is if the AspectRatio can be bound to integer
+ // This test method acts as a placeholder, to spawn the XAML test page
+ // and test the binding to an integer
+ }
+ }
+}
\ No newline at end of file
diff --git a/UITests/UITests.Tests.Shared/Controls/ConstrainedBoxTestPage.xaml b/UITests/UITests.Tests.Shared/Controls/ConstrainedBoxTestPage.xaml
new file mode 100644
index 00000000000..b1a7204c26b
--- /dev/null
+++ b/UITests/UITests.Tests.Shared/Controls/ConstrainedBoxTestPage.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/UITests/UITests.Tests.Shared/Controls/ConstrainedBoxTestPage.xaml.cs b/UITests/UITests.Tests.Shared/Controls/ConstrainedBoxTestPage.xaml.cs
new file mode 100644
index 00000000000..75745e31fd0
--- /dev/null
+++ b/UITests/UITests.Tests.Shared/Controls/ConstrainedBoxTestPage.xaml.cs
@@ -0,0 +1,20 @@
+// 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 System.Collections.Generic;
+using CommunityToolkit.WinUI.UI.Controls;
+using Microsoft.UI.Xaml.Controls;
+
+namespace UITests.App.Pages
+{
+ public sealed partial class ConstrainedBoxTestPage : Page
+ {
+ public int IntegerWidth { get; set; } = 2;
+
+ public ConstrainedBoxTestPage()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTests/UnitTests.Shared/Diagnostics/Test_Guard.cs b/UnitTests/UnitTests.Shared/Diagnostics/Test_Guard.cs
index f98f569e27b..d2e361d749f 100644
--- a/UnitTests/UnitTests.Shared/Diagnostics/Test_Guard.cs
+++ b/UnitTests/UnitTests.Shared/Diagnostics/Test_Guard.cs
@@ -121,6 +121,68 @@ public void Test_Guard_IsAssignableToType_Fail()
Guard.IsAssignableToType(7, typeof(string), nameof(Test_Guard_IsAssignableToType_Fail));
}
+ [TestCategory("Guard")]
+ [TestMethod]
+ public void Test_Guard_IsNullOrEmpty_Ok()
+ {
+ Guard.IsNullOrEmpty(null, nameof(Test_Guard_IsNullOrEmpty_Ok));
+ Guard.IsNullOrEmpty(string.Empty, nameof(Test_Guard_IsNullOrEmpty_Ok));
+ }
+
+ [TestCategory("Guard")]
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentException))]
+ public void Test_Guard_IsNullOrEmpty_Fail()
+ {
+ Guard.IsNullOrEmpty("Hello", nameof(Test_Guard_IsNullOrEmpty_Fail));
+ }
+
+ [TestCategory("Guard")]
+ [TestMethod]
+ public void Test_Guard_IsNotNullOrEmpty_Ok()
+ {
+ Guard.IsNotNullOrEmpty("Hello", nameof(Test_Guard_IsNotNullOrEmpty_Ok));
+ }
+
+ [TestCategory("Guard")]
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentNullException))]
+ public void Test_Guard_IsNotNullOrEmpty_Null()
+ {
+ Guard.IsNotNullOrEmpty(null, nameof(Test_Guard_IsNotNullOrEmpty_Null));
+ }
+
+ [TestCategory("Guard")]
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentException))]
+ public void Test_Guard_IsNotNullOrEmpty_Empty()
+ {
+ Guard.IsNotNullOrEmpty(string.Empty, nameof(Test_Guard_IsNotNullOrEmpty_Empty));
+ }
+
+ [TestCategory("Guard")]
+ [TestMethod]
+ public void Test_Guard_IsNotNullOrWhiteSpace_Ok()
+ {
+ Guard.IsNotNullOrWhiteSpace("Hello", nameof(Test_Guard_IsNotNullOrWhiteSpace_Ok));
+ }
+
+ [TestCategory("Guard")]
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentNullException))]
+ public void Test_Guard_IsNotNullOrWhiteSpace_Null()
+ {
+ Guard.IsNotNullOrWhiteSpace(null, nameof(Test_Guard_IsNotNullOrWhiteSpace_Null));
+ }
+
+ [TestCategory("Guard")]
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentException))]
+ public void Test_Guard_IsNotNullOrWhiteSpace_Empty()
+ {
+ Guard.IsNotNullOrWhiteSpace(" ", nameof(Test_Guard_IsNotNullOrWhiteSpace_Empty));
+ }
+
[TestCategory("Guard")]
[TestMethod]
public void Test_Guard_IsEqualTo_Ok()
diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.AspectRatio.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.AspectRatio.cs
index fb90df2d123..b5f329230a2 100644
--- a/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.AspectRatio.cs
+++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.AspectRatio.cs
@@ -91,6 +91,46 @@ await App.DispatcherQueue.EnqueueAsync(async () =>
});
}
+ [TestCategory("ConstrainedBox")]
+ [TestMethod]
+ public async Task Test_ConstrainedBox_Normal_IntegerWidth()
+ {
+ await App.DispatcherQueue.EnqueueAsync(async () =>
+ {
+ var treeRoot = XamlReader.Load(@"
+
+
+
+") as FrameworkElement;
+
+ Assert.IsNotNull(treeRoot, "Could not load XAML tree.");
+
+ // Initialize Visual Tree
+ await SetTestContentAsync(treeRoot);
+
+ var panel = treeRoot.FindChild("ConstrainedBox") as ConstrainedBox;
+
+ Assert.IsNotNull(panel, "Could not find ConstrainedBox in tree.");
+
+ // Check Size
+ Assert.AreEqual(2.0, panel.AspectRatio, 0.01, "ApectRatio does not meet expected value of 2.0");
+
+ // Force Layout calculations
+ panel.UpdateLayout();
+
+ var child = panel.Content as Border;
+
+ Assert.IsNotNull(child, "Could not find inner Border");
+
+ // Check Size
+ Assert.AreEqual(200, child.ActualWidth, 0.01, "Actual width does not meet expected value of 200");
+ Assert.AreEqual(100, child.ActualHeight, 0.01, "Actual height does not meet expected value of 100");
+ });
+ }
+
[TestCategory("ConstrainedBox")]
[TestMethod]
public void Test_ConstrainedBox_AspectRatioParsing_WidthAndHeight()