Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Content>
<Content Include="SamplePages\Triggers\ControlSizeTrigger.bind">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Content>
<Page Include="SamplePages\Triggers\FullScreenModeStateTriggerPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:triggers="using:Microsoft.Toolkit.Uwp.UI.Triggers"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<triggers:ControlSizeTrigger
TargetElement="{Binding ElementName=ParentGrid}"
MinWidth="400"
MaxWidth="500"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="ResizingText.FontSize" Value="20"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

<StackPanel VerticalAlignment="Center" Width="500">
<Grid
x:Name="ParentGrid"
Width="{Binding Value, ElementName=Slider, Mode=OneWay}"
Height="50"
Background="Blue"/>
<TextBlock
x:Name="ResizingText"
FontSize="12"
Text="Windows Community Toolkit"
HorizontalAlignment="Center"/>
<Slider
x:Name="Slider"
Minimum="0"
Maximum="500" />
</StackPanel>
</Grid>
</Page>
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<triggers:RegexStateTrigger x:Key="RegexStateTrigger" />
<triggers:UserHandPreferenceStateTrigger x:Key="UserHandPreferenceStateTrigger" />
<triggers:UserInteractionModeStateTrigger x:Key="UserInteractionModeStateTrigger" />
<triggers:ControlSizeTrigger x:Key="ControlSizeTrigger" />
<behaviors:StartAnimationAction x:Key="StartAnimationAction" />
<behaviors:KeyDownTriggerBehavior x:Key="KeyDownTriggerBehavior" />
<behaviors:AutoSelectBehavior x:Key="AutoSelectBehavior" />
Expand Down
9 changes: 9 additions & 0 deletions Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
185 changes: 185 additions & 0 deletions Microsoft.Toolkit.Uwp.UI/Triggers/ControlSizeTrigger.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// A conditional state trigger that functions
/// based on the target control's width or height.
/// </summary>
public class ControlSizeTrigger : StateTriggerBase
{
/// <summary>
/// Gets or sets a value indicating
/// whether this trigger will be active or not.
/// </summary>
public bool CanTrigger
{
get => (bool)GetValue(CanTriggerProperty);
set => SetValue(CanTriggerProperty, value);
}

/// <summary>
/// Identifies the <see cref="CanTrigger"/> DependencyProperty.
/// </summary>
public static readonly DependencyProperty CanTriggerProperty = DependencyProperty.Register(
nameof(CanTrigger),
typeof(bool),
typeof(ControlSizeTrigger),
new PropertyMetadata(true, (d, e) => ((ControlSizeTrigger)d).UpdateTrigger()));

/// <summary>
/// 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.
/// </summary>
public double MaxWidth
{
get => (double)GetValue(MaxWidthProperty);
set => SetValue(MaxWidthProperty, value);
}

/// <summary>
/// Identifies the <see cref="MaxWidth"/> DependencyProperty.
/// </summary>
public static readonly DependencyProperty MaxWidthProperty = DependencyProperty.Register(
nameof(MaxWidth),
typeof(double),
typeof(ControlSizeTrigger),
new PropertyMetadata(double.PositiveInfinity, (d, e) => ((ControlSizeTrigger)d).UpdateTrigger()));

/// <summary>
/// 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.
/// </summary>
public double MinWidth
{
get => (double)GetValue(MinWidthProperty);
set => SetValue(MinWidthProperty, value);
}

/// <summary>
/// Identifies the <see cref="MinWidth"/> DependencyProperty.
/// </summary>
public static readonly DependencyProperty MinWidthProperty = DependencyProperty.Register(
nameof(MinWidth),
typeof(double),
typeof(ControlSizeTrigger),
new PropertyMetadata(0.0, (d, e) => ((ControlSizeTrigger)d).UpdateTrigger()));

/// <summary>
/// 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.
/// </summary>
public double MaxHeight
{
get => (double)GetValue(MaxHeightProperty);
set => SetValue(MaxHeightProperty, value);
}

/// <summary>
/// Identifies the <see cref="MaxHeight"/> DependencyProperty.
/// </summary>
public static readonly DependencyProperty MaxHeightProperty = DependencyProperty.Register(
nameof(MaxHeight),
typeof(double),
typeof(ControlSizeTrigger),
new PropertyMetadata(double.PositiveInfinity, (d, e) => ((ControlSizeTrigger)d).UpdateTrigger()));

/// <summary>
/// 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.
/// </summary>
public double MinHeight
{
get => (double)GetValue(MinHeightProperty);
set => SetValue(MinHeightProperty, value);
}

/// <summary>
/// Identifies the <see cref="MinHeight"/> DependencyProperty.
/// </summary>
public static readonly DependencyProperty MinHeightProperty = DependencyProperty.Register(
nameof(MinHeight),
typeof(double),
typeof(ControlSizeTrigger),
new PropertyMetadata(0.0, (d, e) => ((ControlSizeTrigger)d).UpdateTrigger()));

/// <summary>
/// Gets or sets the element whose width will observed
/// for the trigger.
/// </summary>
public FrameworkElement TargetElement
{
get => (FrameworkElement)GetValue(TargetElementProperty);
set => SetValue(TargetElementProperty, value);
}

/// <summary>
/// Identifies the <see cref="TargetElement"/> DependencyProperty.
/// </summary>
/// <remarks>
/// Using a DependencyProperty as the backing store for TargetElement. This enables animation, styling, binding, etc.
/// </remarks>
public static readonly DependencyProperty TargetElementProperty = DependencyProperty.Register(
nameof(TargetElement),
typeof(FrameworkElement),
typeof(ControlSizeTrigger),
new PropertyMetadata(null, OnTargetElementPropertyChanged));

/// <summary>
/// Gets a value indicating whether the trigger is active.
/// </summary>
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);
}
}
}
125 changes: 125 additions & 0 deletions UnitTests/UnitTests.UWP/UI/Triggers/Test_ControlSizeTrigger.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
1 change: 1 addition & 0 deletions UnitTests/UnitTests.UWP/UnitTests.UWP.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@
<Compile Include="UI\Extensions\Test_VisualExtensions.cs" />
<Compile Include="UI\Person.cs" />
<Compile Include="UI\Test_AdvancedCollectionView.cs" />
<Compile Include="UI\Triggers\Test_ControlSizeTrigger.cs" />
<Compile Include="VisualUITestBase.cs" />
</ItemGroup>
<ItemGroup>
Expand Down