diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 2967f2df8ee..44027e28bc7 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -1079,6 +1079,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Triggers/ControlSizeTrigger.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Triggers/ControlSizeTrigger.bind new file mode 100644 index 00000000000..583f4cbbbda --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Triggers/ControlSizeTrigger.bind @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml index 780f14479e8..c51455532c3 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml @@ -31,6 +31,7 @@ + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index 2447ae36db2..6e9841a5a36 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -920,6 +920,15 @@ "Icon": "/Assets/Helpers.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/helpers/Triggers.md" }, + { + "Name": "ControlSizeTrigger", + "Subcategory": "State Triggers", + "About": "Enables a state if the target control meets the specified size", + "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI/Triggers/ControlSizeTrigger.cs", + "XamlCodeFile": "/SamplePages/Triggers/ControlSizeTrigger.bind", + "Icon": "/Assets/Helpers.png", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/helpers/Triggers.md" + }, { "Name": "IsEqualStateTrigger", "Subcategory": "State Triggers", diff --git a/Microsoft.Toolkit.Uwp.UI/Triggers/ControlSizeTrigger.cs b/Microsoft.Toolkit.Uwp.UI/Triggers/ControlSizeTrigger.cs new file mode 100644 index 00000000000..1fcbd28291e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI/Triggers/ControlSizeTrigger.cs @@ -0,0 +1,185 @@ +// 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 Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Triggers +{ + /// + /// A conditional state trigger that functions + /// based on the target control's width or height. + /// + public class ControlSizeTrigger : StateTriggerBase + { + /// + /// Gets or sets a value indicating + /// whether this trigger will be active or not. + /// + public bool CanTrigger + { + get => (bool)GetValue(CanTriggerProperty); + set => SetValue(CanTriggerProperty, value); + } + + /// + /// Identifies the DependencyProperty. + /// + public static readonly DependencyProperty CanTriggerProperty = DependencyProperty.Register( + nameof(CanTrigger), + typeof(bool), + typeof(ControlSizeTrigger), + new PropertyMetadata(true, (d, e) => ((ControlSizeTrigger)d).UpdateTrigger())); + + /// + /// Gets or sets the max width at which to trigger. + /// This value is exclusive, meaning this trigger + /// could be active if the value is less than MaxWidth. + /// + public double MaxWidth + { + get => (double)GetValue(MaxWidthProperty); + set => SetValue(MaxWidthProperty, value); + } + + /// + /// Identifies the DependencyProperty. + /// + public static readonly DependencyProperty MaxWidthProperty = DependencyProperty.Register( + nameof(MaxWidth), + typeof(double), + typeof(ControlSizeTrigger), + new PropertyMetadata(double.PositiveInfinity, (d, e) => ((ControlSizeTrigger)d).UpdateTrigger())); + + /// + /// Gets or sets the min width at which to trigger. + /// This value is inclusive, meaning this trigger + /// could be active if the value is >= MinWidth. + /// + public double MinWidth + { + get => (double)GetValue(MinWidthProperty); + set => SetValue(MinWidthProperty, value); + } + + /// + /// Identifies the DependencyProperty. + /// + public static readonly DependencyProperty MinWidthProperty = DependencyProperty.Register( + nameof(MinWidth), + typeof(double), + typeof(ControlSizeTrigger), + new PropertyMetadata(0.0, (d, e) => ((ControlSizeTrigger)d).UpdateTrigger())); + + /// + /// Gets or sets the max height at which to trigger. + /// This value is exclusive, meaning this trigger + /// could be active if the value is less than MaxHeight. + /// + public double MaxHeight + { + get => (double)GetValue(MaxHeightProperty); + set => SetValue(MaxHeightProperty, value); + } + + /// + /// Identifies the DependencyProperty. + /// + public static readonly DependencyProperty MaxHeightProperty = DependencyProperty.Register( + nameof(MaxHeight), + typeof(double), + typeof(ControlSizeTrigger), + new PropertyMetadata(double.PositiveInfinity, (d, e) => ((ControlSizeTrigger)d).UpdateTrigger())); + + /// + /// Gets or sets the min height at which to trigger. + /// This value is inclusive, meaning this trigger + /// could be active if the value is >= MinHeight. + /// + public double MinHeight + { + get => (double)GetValue(MinHeightProperty); + set => SetValue(MinHeightProperty, value); + } + + /// + /// Identifies the DependencyProperty. + /// + public static readonly DependencyProperty MinHeightProperty = DependencyProperty.Register( + nameof(MinHeight), + typeof(double), + typeof(ControlSizeTrigger), + new PropertyMetadata(0.0, (d, e) => ((ControlSizeTrigger)d).UpdateTrigger())); + + /// + /// Gets or sets the element whose width will observed + /// for the trigger. + /// + public FrameworkElement TargetElement + { + get => (FrameworkElement)GetValue(TargetElementProperty); + set => SetValue(TargetElementProperty, value); + } + + /// + /// Identifies the DependencyProperty. + /// + /// + /// Using a DependencyProperty as the backing store for TargetElement. This enables animation, styling, binding, etc. + /// + public static readonly DependencyProperty TargetElementProperty = DependencyProperty.Register( + nameof(TargetElement), + typeof(FrameworkElement), + typeof(ControlSizeTrigger), + new PropertyMetadata(null, OnTargetElementPropertyChanged)); + + /// + /// Gets a value indicating whether the trigger is active. + /// + public bool IsActive { get; private set; } + + private static void OnTargetElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((ControlSizeTrigger)d).UpdateTargetElement((FrameworkElement)e.OldValue, (FrameworkElement)e.NewValue); + } + + // Handle event to get current values + private void OnTargetElementSizeChanged(object sender, SizeChangedEventArgs e) + { + UpdateTrigger(); + } + + private void UpdateTargetElement(FrameworkElement oldValue, FrameworkElement newValue) + { + if (oldValue != null) + { + oldValue.SizeChanged -= OnTargetElementSizeChanged; + } + + if (newValue != null) + { + newValue.SizeChanged += OnTargetElementSizeChanged; + } + + UpdateTrigger(); + } + + // Logic to evaluate and apply trigger value + private void UpdateTrigger() + { + if (TargetElement == null || !CanTrigger) + { + SetActive(false); + return; + } + + bool activate = MinWidth <= TargetElement.ActualWidth && + TargetElement.ActualWidth < MaxWidth && + MinHeight <= TargetElement.ActualHeight && + TargetElement.ActualHeight < MaxHeight; + + IsActive = activate; + SetActive(activate); + } + } +} diff --git a/UnitTests/UnitTests.UWP/UI/Triggers/Test_ControlSizeTrigger.cs b/UnitTests/UnitTests.UWP/UI/Triggers/Test_ControlSizeTrigger.cs new file mode 100644 index 00000000000..0cd0f790e1e --- /dev/null +++ b/UnitTests/UnitTests.UWP/UI/Triggers/Test_ControlSizeTrigger.cs @@ -0,0 +1,125 @@ +// 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.Toolkit.Uwp; +using Microsoft.Toolkit.Uwp.UI.Triggers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Threading.Tasks; +using Windows.UI.Xaml.Controls; + +namespace UnitTests.UWP.UI.Triggers +{ + [TestClass] + [TestCategory("Test_ControlSizeTrigger")] + public class Test_ControlSizeTrigger : VisualUITestBase + { + [DataTestMethod] + [DataRow(450, 450, true)] + [DataRow(400, 400, true)] + [DataRow(500, 500, false)] + [DataRow(399, 400, false)] + [DataRow(400, 399, false)] + public async Task ControlSizeTriggerTest(double width, double height, bool expectedResult) + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + Grid grid = CreateGrid(width, height); + await SetTestContentAsync(grid); + var trigger = new ControlSizeTrigger(); + + trigger.TargetElement = grid; + trigger.MaxHeight = 500; + trigger.MinHeight = 400; + trigger.MaxWidth = 500; + trigger.MinWidth = 400; + + Assert.AreEqual(expectedResult, trigger.IsActive); + }); + } + + [DataTestMethod] + [DataRow(400, 400, true)] + [DataRow(400, 399, false)] + public async Task ControlSizeMinHeightTriggerTest(double width, double height, bool expectedResult) + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + Grid grid = CreateGrid(width, height); + await SetTestContentAsync(grid); + var trigger = new ControlSizeTrigger(); + + trigger.TargetElement = grid; + trigger.MinHeight = 400; + + Assert.AreEqual(expectedResult, trigger.IsActive); + }); + } + + [DataTestMethod] + [DataRow(399, 400, false)] + [DataRow(400, 400, true)] + public async Task ControlSizeMinWidthTriggerTest(double width, double height, bool expectedResult) + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + Grid grid = CreateGrid(width, height); + await SetTestContentAsync(grid); + var trigger = new ControlSizeTrigger(); + + trigger.TargetElement = grid; + trigger.MinWidth = 400; + + Assert.AreEqual(expectedResult, trigger.IsActive); + }); + } + + [DataTestMethod] + [DataRow(450, 450, false)] + [DataRow(450, 449, true)] + public async Task ControlSizeMaxHeightTriggerTest(double width, double height, bool expectedResult) + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + Grid grid = CreateGrid(width, height); + await SetTestContentAsync(grid); + var trigger = new ControlSizeTrigger(); + + trigger.TargetElement = grid; + trigger.MaxHeight = 450; + + Assert.AreEqual(expectedResult, trigger.IsActive); + }); + } + + [DataTestMethod] + [DataRow(450, 450, false)] + [DataRow(449, 450, true)] + public async Task ControlSizeMaxWidthTriggerTest(double width, double height, bool expectedResult) + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + Grid grid = CreateGrid(width, height); + await SetTestContentAsync(grid); + var trigger = new ControlSizeTrigger(); + + trigger.TargetElement = grid; + trigger.MaxWidth = 450; + + Assert.AreEqual(expectedResult, trigger.IsActive); + }); + } + + private Grid CreateGrid(double width, double height) + { + var grid = new Grid() + { + Height = height, + Width = width + }; + + return grid; + } + } +} diff --git a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj index fef868d9e62..769f3737d85 100644 --- a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj +++ b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj @@ -255,6 +255,7 @@ +