Skip to content

Commit

Permalink
Merge pull request #4169 from michael-hawker/mhawker/constrained-box-…
Browse files Browse the repository at this point in the history
…fixes

Add Value Coercion to ConstrainedBox
  • Loading branch information
michael-hawker authored Aug 16, 2021
2 parents 2d74076 + 85a457a commit 5171b99
Show file tree
Hide file tree
Showing 7 changed files with 599 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public int MultipleX
/// Identifies the <see cref="MultipleX"/> property.
/// </summary>
public static readonly DependencyProperty MultipleXProperty =
DependencyProperty.Register(nameof(MultipleX), typeof(int), typeof(ConstrainedBox), new PropertyMetadata(null));
DependencyProperty.Register(nameof(MultipleX), typeof(int), typeof(ConstrainedBox), new PropertyMetadata(null, ConstraintPropertyChanged));

/// <summary>
/// Gets or sets the integer multiple that the height of the panel should be floored to. Default is null (no snap).
Expand All @@ -76,7 +76,7 @@ public int MultipleY
/// Identifies the <see cref="MultipleY"/> property.
/// </summary>
public static readonly DependencyProperty MultipleYProperty =
DependencyProperty.Register(nameof(MultipleY), typeof(int), typeof(ConstrainedBox), new PropertyMetadata(null));
DependencyProperty.Register(nameof(MultipleY), typeof(int), typeof(ConstrainedBox), new PropertyMetadata(null, ConstraintPropertyChanged));

/// <summary>
/// Gets or sets aspect Ratio to use for the contents of the Panel (after scaling).
Expand All @@ -93,11 +93,71 @@ public AspectRatio AspectRatio
public static readonly DependencyProperty AspectRatioProperty =
DependencyProperty.Register(nameof(AspectRatio), typeof(AspectRatio), typeof(ConstrainedBox), new PropertyMetadata(null, ConstraintPropertyChanged));

private bool _propertyUpdating;

private static void ConstraintPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ConstrainedBox panel)
if (d is ConstrainedBox panel && !panel._propertyUpdating)
{
panel._propertyUpdating = true;

panel.CoerceValues();

panel.InvalidateMeasure();

panel._propertyUpdating = false;
}
}

private void CoerceValues()
{
// Check if scale properties are in range
if (!double.IsNaN(ScaleX))
{
if (ScaleX < 0)
{
ScaleX = 0;
}
else if (ScaleX > 1.0)
{
ScaleX = 1.0;
}
}
else
{
ScaleX = 1.0;
}

if (!double.IsNaN(ScaleY))
{
if (ScaleY < 0)
{
ScaleY = 0;
}
else if (ScaleY > 1.0)
{
ScaleY = 1.0;
}
}
else
{
ScaleY = 1.0;
}

// Clear invalid values less than 0 for other properties
if (ReadLocalValue(MultipleXProperty) is int value && value <= 0)
{
ClearValue(MultipleXProperty);
}

if (ReadLocalValue(MultipleYProperty) is int value2 && value2 <= 0)
{
ClearValue(MultipleYProperty);
}

if (ReadLocalValue(AspectRatioProperty) is AspectRatio ratio && ratio <= 0)
{
ClearValue(AspectRatioProperty);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp;
using Microsoft.Toolkit.Uwp.UI;
using Microsoft.Toolkit.Uwp.UI.Controls;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;

namespace UnitTests.UWP.UI.Controls
{
/// <summary>
/// These tests check whether the inner alignment of the box within it's parent works as expected.
/// </summary>
public partial class Test_ConstrainedBox : VisualUITestBase
{
// For this test we're testing within the confines of a 200x200 box to position a contrained
// 50x100 element in all the different alignment combinations.
[TestCategory("ConstrainedBox")]
[TestMethod]
[DataRow("Left", 0, "Center", 50, DisplayName = "LeftCenter")]
[DataRow("Left", 0, "Top", 0, DisplayName = "LeftTop")]
[DataRow("Center", 75, "Top", 0, DisplayName = "CenterTop")]
[DataRow("Right", 150, "Top", 0, DisplayName = "RightTop")]
[DataRow("Right", 150, "Center", 50, DisplayName = "RightCenter")]
[DataRow("Right", 150, "Bottom", 100, DisplayName = "RightBottom")]
[DataRow("Center", 75, "Bottom", 100, DisplayName = "CenterBottom")]
[DataRow("Left", 0, "Bottom", 100, DisplayName = "LeftBottom")]
[DataRow("Center", 75, "Center", 50, DisplayName = "CenterCenter")]
public async Task Test_ConstrainedBox_Alignment_Aspect(string horizontalAlignment, int expectedLeft, string verticalAlignment, int expectedTop)
{
await App.DispatcherQueue.EnqueueAsync(async () =>
{
var treeRoot = XamlReader.Load(@$"<Page
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:controls=""using:Microsoft.Toolkit.Uwp.UI.Controls"">
<Grid x:Name=""ParentGrid""
Width=""200"" Height=""200"">
<controls:ConstrainedBox x:Name=""ConstrainedBox"" AspectRatio=""1:2"" MaxHeight=""100""
UseLayoutRounding=""False""
HorizontalAlignment=""{horizontalAlignment}""
VerticalAlignment=""{verticalAlignment}"">
<Border HorizontalAlignment=""Stretch"" VerticalAlignment=""Stretch"" Background=""Red""/>
</controls:ConstrainedBox>
</Grid>
</Page>") as FrameworkElement;

Assert.IsNotNull(treeRoot, "Could not load XAML tree.");

// Initialize Visual Tree
await SetTestContentAsync(treeRoot);

var grid = treeRoot.FindChild("ParentGrid") as Grid;

Assert.IsNotNull(grid, "Could not find the ParentGrid in tree.");

var panel = treeRoot.FindChild("ConstrainedBox") as ConstrainedBox;

Assert.IsNotNull(panel, "Could not find ConstrainedBox in tree.");

// Force Layout calculations
panel.UpdateLayout();

var child = panel.Content as Border;

Assert.IsNotNull(child, "Could not find inner Border");

// Check Size
Assert.AreEqual(50, child.ActualWidth, 0.01, "Actual width does not meet expected value of 50");
Assert.AreEqual(100, child.ActualHeight, 0.01, "Actual height does not meet expected value of 100");

// Check inner Positioning, we do this from the Grid as the ConstainedBox also modifies its own size
// and is hugging the child.
var position = grid.CoordinatesTo(child);

Assert.AreEqual(expectedLeft, position.X, 0.01, "X position does not meet expected value of 0");
Assert.AreEqual(expectedTop, position.Y, 0.01, "Y position does not meet expected value of 50");
});
}
}
}
140 changes: 140 additions & 0 deletions UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.Coerce.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp;
using Microsoft.Toolkit.Uwp.UI;
using Microsoft.Toolkit.Uwp.UI.Controls;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;

namespace UnitTests.UWP.UI.Controls
{
/// <summary>
/// These tests check for the various values which can be coerced and changed if out of bounds for each property.
/// </summary>
public partial class Test_ConstrainedBox : VisualUITestBase
{
[TestCategory("ConstrainedBox")]
[TestMethod]
public async Task Test_ConstrainedBox_Coerce_Scale()
{
await App.DispatcherQueue.EnqueueAsync(async () =>
{
var treeRoot = XamlReader.Load(@"<Page
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:controls=""using:Microsoft.Toolkit.Uwp.UI.Controls"">
<controls:ConstrainedBox x:Name=""ConstrainedBox"" ScaleX=""0.5"" ScaleY=""0.5""/>
</Page>") 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(0.5, panel.ScaleX, 0.01, "ScaleX does not meet expected initial value of 0.5");
Assert.AreEqual(0.5, panel.ScaleY, 0.01, "ScaleY does not meet expected initial value of 0.5");

// Change values now to be invalid
panel.ScaleX = double.NaN;
panel.ScaleY = double.NaN;

Assert.AreEqual(1.0, panel.ScaleX, 0.01, "ScaleX does not meet expected value of 1.0 after change.");
Assert.AreEqual(1.0, panel.ScaleY, 0.01, "ScaleY does not meet expected value of 1.0 after change.");

// Change values now to be invalid
panel.ScaleX = double.NegativeInfinity;
panel.ScaleY = double.NegativeInfinity;

Assert.AreEqual(0.0, panel.ScaleX, 0.01, "ScaleX does not meet expected value of 0.0 after change.");
Assert.AreEqual(0.0, panel.ScaleY, 0.01, "ScaleY does not meet expected value of 0.0 after change.");

// Change values now to be invalid
panel.ScaleX = double.PositiveInfinity;
panel.ScaleY = double.PositiveInfinity;

Assert.AreEqual(1.0, panel.ScaleX, 0.01, "ScaleX does not meet expected value of 1.0 after change.");
Assert.AreEqual(1.0, panel.ScaleY, 0.01, "ScaleY does not meet expected value of 1.0 after change.");
});
}

[TestCategory("ConstrainedBox")]
[TestMethod]
public async Task Test_ConstrainedBox_Coerce_Multiple()
{
await App.DispatcherQueue.EnqueueAsync(async () =>
{
var treeRoot = XamlReader.Load(@"<Page
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:controls=""using:Microsoft.Toolkit.Uwp.UI.Controls"">
<controls:ConstrainedBox x:Name=""ConstrainedBox"" MultipleX=""32"" MultipleY=""32""/>
</Page>") 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(32, panel.MultipleX, "MultipleX does not meet expected value of 32");
Assert.AreEqual(32, panel.MultipleY, "MultipleY does not meet expected value of 32");

// Change values now to be invalid
panel.MultipleX = 0;
panel.MultipleY = int.MinValue;

Assert.AreEqual(DependencyProperty.UnsetValue, panel.ReadLocalValue(ConstrainedBox.MultipleXProperty), "MultipleX does not meet expected value of UnsetValue after change.");
Assert.AreEqual(DependencyProperty.UnsetValue, panel.ReadLocalValue(ConstrainedBox.MultipleYProperty), "MultipleY does not meet expected value of UnsetValue after change.");
});
}

[TestCategory("ConstrainedBox")]
[TestMethod]
public async Task Test_ConstrainedBox_Coerce_AspectRatio()
{
await App.DispatcherQueue.EnqueueAsync(async () =>
{
var treeRoot = XamlReader.Load(@"<Page
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:controls=""using:Microsoft.Toolkit.Uwp.UI.Controls"">
<controls:ConstrainedBox x:Name=""ConstrainedBox"" AspectRatio=""1.25:3.5""/>
</Page>") 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(1.25 / 3.5, panel.AspectRatio, 0.01, "ApectRatio does not meet expected value of 1.25/3.5");

// Change values now to be invalid
panel.AspectRatio = -1;

Assert.AreEqual(DependencyProperty.UnsetValue, panel.ReadLocalValue(ConstrainedBox.AspectRatioProperty), "AspectRatio does not meet expected value of UnsetValue after change.");
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

namespace UnitTests.UWP.UI.Controls
{
/// <summary>
/// These tests check multiple constraints are applied together in the correct order.
/// </summary>
public partial class Test_ConstrainedBox : VisualUITestBase
{
[TestCategory("ConstrainedBox")]
Expand Down
Loading

0 comments on commit 5171b99

Please sign in to comment.