Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding control size trigger #3867

Merged
14 commits merged into from
Aug 26, 2021
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
dpaulino marked this conversation as resolved.
Show resolved Hide resolved
{
/// <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;
michael-hawker marked this conversation as resolved.
Show resolved Hide resolved

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);
dpaulino marked this conversation as resolved.
Show resolved Hide resolved
});
}

[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