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()