Skip to content

Commit 0bb6eb8

Browse files
Merge pull request #3956 from Sergio0694/feature/translation-attached-property
Added VisualExtensions.Translation attached property
2 parents 7e2d8a8 + 129ab97 commit 0bb6eb8

File tree

5 files changed

+223
-6
lines changed

5 files changed

+223
-6
lines changed

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Visual Extensions/VisualExtensionsCode.bind

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,49 @@
55
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
66
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
77
xmlns:ui="using:Microsoft.Toolkit.Uwp.UI"
8+
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
9+
xmlns:interactions="using:Microsoft.Xaml.Interactions.Core"
10+
xmlns:ani="using:Microsoft.Toolkit.Uwp.UI.Animations"
11+
xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors"
812
mc:Ignorable="d">
913

10-
<Grid>
14+
<StackPanel Spacing="120" VerticalAlignment="Center">
15+
16+
<!--This is a static element, with some Visual properties being modified through
17+
the VisualExtensions class. You can modify their values to see how the position,
18+
orientation and alignment of the Border element changes.-->
1119
<Border Height="100"
1220
Width="100"
1321
Background="Purple"
22+
ui:VisualExtensions.CenterPoint="50,50,0"
23+
ui:VisualExtensions.Offset="50"
1424
ui:VisualExtensions.Opacity="0.5"
1525
ui:VisualExtensions.RotationAngleInDegrees="80"
1626
ui:VisualExtensions.Scale="2, 0.5, 1"
17-
ui:VisualExtensions.NormalizedCenterPoint="0.5" />
18-
</Grid>
27+
ui:VisualExtensions.NormalizedCenterPoint="0.5"
28+
ui:VisualExtensions.Translation="20,12,0"/>
29+
30+
<!--This Button demonstrates the VisualExtensions.Translation property in combination with a translation
31+
animation. The Translation property is set to indicate the starting position of the element (relative
32+
to its offset), and the animation will modify that to reach the specified translation value. Note how
33+
the animation doesn't have an explicit starting value, as it will just start animating the translation
34+
from the current value set via the VisualExtensions.Translation attached property.-->
35+
<Button Height="120"
36+
Width="360"
37+
Background="Green"
38+
Content="Click me!"
39+
FontSize="32"
40+
ui:VisualExtensions.Translation="20,-40,0">
41+
<ani:Explicit.Animations>
42+
<ani:AnimationSet x:Name="MoveAnimation">
43+
<ani:TranslationAnimation To="480,80,0" Duration="0:0:2"/>
44+
</ani:AnimationSet>
45+
</ani:Explicit.Animations>
46+
<interactivity:Interaction.Behaviors>
47+
<interactions:EventTriggerBehavior EventName="Click">
48+
<behaviors:StartAnimationAction Animation="{Binding ElementName=MoveAnimation}"/>
49+
</interactions:EventTriggerBehavior>
50+
</interactivity:Interaction.Behaviors>
51+
</Button>
52+
</StackPanel>
1953
</Page>

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Visual Extensions/VisualExtensionsPage.xaml

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
44
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
55
xmlns:ui="using:Microsoft.Toolkit.Uwp.UI"
6+
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
7+
xmlns:interactions="using:Microsoft.Xaml.Interactions.Core"
8+
xmlns:ani="using:Microsoft.Toolkit.Uwp.UI.Animations"
9+
xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors"
610
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
711
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
812
mc:Ignorable="d">
913

10-
<Grid>
14+
<StackPanel Spacing="120" VerticalAlignment="Center">
1115
<Border Height="100"
1216
Width="100"
1317
Background="Purple"
@@ -16,6 +20,25 @@
1620
ui:VisualExtensions.Opacity="0.5"
1721
ui:VisualExtensions.RotationAngleInDegrees="80"
1822
ui:VisualExtensions.Scale="2, 0.5, 1"
19-
ui:VisualExtensions.NormalizedCenterPoint="0.5"/>
20-
</Grid>
23+
ui:VisualExtensions.NormalizedCenterPoint="0.5"
24+
ui:VisualExtensions.Translation="20,12,0"/>
25+
<Button
26+
Height="120"
27+
Width="360"
28+
Background="Green"
29+
Content="Click me!"
30+
FontSize="32"
31+
ui:VisualExtensions.Translation="20,-40,0">
32+
<ani:Explicit.Animations>
33+
<ani:AnimationSet x:Name="MoveAnimation">
34+
<ani:TranslationAnimation To="480,80,0" Duration="0:0:2"/>
35+
</ani:AnimationSet>
36+
</ani:Explicit.Animations>
37+
<interactivity:Interaction.Behaviors>
38+
<interactions:EventTriggerBehavior EventName="Click">
39+
<behaviors:StartAnimationAction Animation="{Binding ElementName=MoveAnimation}"/>
40+
</interactions:EventTriggerBehavior>
41+
</interactivity:Interaction.Behaviors>
42+
</Button>
43+
</StackPanel>
2144
</Page>

Microsoft.Toolkit.Uwp.UI/Extensions/VisualExtensions.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Globalization;
56
using System.Numerics;
67
using Windows.UI.Composition;
78
using Windows.UI.Xaml;
@@ -114,6 +115,36 @@ public static void SetOffset(DependencyObject obj, string value)
114115
obj.SetValue(OffsetProperty, value);
115116
}
116117

118+
/// <summary>
119+
/// Gets the <c>"Translation"</c> property of the underlying <see cref="Visual"/> object for a <see cref="UIElement"/>, in <see cref="string"/> form.
120+
/// </summary>
121+
/// <param name="obj">The <see cref="DependencyObject"/> instance.</param>
122+
/// <returns>The <see cref="string"/> representation of the <c>"Translation"</c> property property.</returns>
123+
public static string GetTranslation(DependencyObject obj)
124+
{
125+
if (!DesignTimeHelpers.IsRunningInLegacyDesignerMode && obj is UIElement element)
126+
{
127+
return GetTranslationForElement(element);
128+
}
129+
130+
return (string)obj.GetValue(TranslationProperty);
131+
}
132+
133+
/// <summary>
134+
/// Sets the <c>"Translation"</c> property of the underlying <see cref="Visual"/> object for a <see cref="UIElement"/>, in <see cref="string"/> form.
135+
/// </summary>
136+
/// <param name="obj">The <see cref="DependencyObject"/> instance.</param>
137+
/// <param name="value">The <see cref="string"/> representation of the <c>"Translation"</c> property property to be set.</param>
138+
public static void SetTranslation(DependencyObject obj, string value)
139+
{
140+
if (!DesignTimeHelpers.IsRunningInLegacyDesignerMode && obj is UIElement element)
141+
{
142+
SetTranslationForElement(value, element);
143+
}
144+
145+
obj.SetValue(TranslationProperty, value);
146+
}
147+
117148
/// <summary>
118149
/// Gets the <see cref="Visual.Opacity"/> of a UIElement
119150
/// </summary>
@@ -334,6 +365,12 @@ public static void SetNormalizedCenterPoint(DependencyObject obj, string value)
334365
public static readonly DependencyProperty OffsetProperty =
335366
DependencyProperty.RegisterAttached("Offset", typeof(string), typeof(VisualExtensions), new PropertyMetadata(null, OnOffsetChanged));
336367

368+
/// <summary>
369+
/// Identifies the Translation attached property.
370+
/// </summary>
371+
public static readonly DependencyProperty TranslationProperty =
372+
DependencyProperty.RegisterAttached("Translation", typeof(string), typeof(VisualExtensions), new PropertyMetadata(null, OnTranslationChanged));
373+
337374
/// <summary>
338375
/// Identifies the Opacity attached property.
339376
/// </summary>
@@ -400,6 +437,14 @@ private static void OnOffsetChanged(DependencyObject d, DependencyPropertyChange
400437
}
401438
}
402439

440+
private static void OnTranslationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
441+
{
442+
if (e.NewValue is string str)
443+
{
444+
SetTranslation(d, str);
445+
}
446+
}
447+
403448
private static void OnOpacityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
404449
{
405450
if (e.NewValue is double dbl)
@@ -503,6 +548,37 @@ private static void SetOffsetForElement(string value, UIElement element)
503548
visual.Offset = value.ToVector3();
504549
}
505550

551+
private static string GetTranslationForElement(UIElement element)
552+
{
553+
CompositionGetValueStatus result = GetVisual(element).Properties.TryGetVector3("Translation", out Vector3 translation);
554+
555+
return result switch
556+
{
557+
// The ("G", CultureInfo.InvariantCulture) combination produces a string with the default numeric
558+
// formatting style, and using ',' as component separator, so that the resulting text can safely
559+
// be parsed back if needed with the StringExtensions.ToVector3(string) extension, which uses
560+
// the invariant culture mode by default so that the syntax will always match that from XAML.
561+
CompositionGetValueStatus.Succeeded => translation.ToString("G", CultureInfo.InvariantCulture),
562+
_ => "<0, 0, 0>"
563+
};
564+
}
565+
566+
private static void SetTranslationForElement(string value, UIElement element)
567+
{
568+
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
569+
570+
// The "Translation" attached property refers to the "hidden" property that is enabled
571+
// through "ElementCompositionPreview.SetIsTranslationEnabled". The value for this property
572+
// is not available directly on the Visual class and can only be accessed through its property
573+
// set. Note that this "Translation" value is not the same as Visual.TransformMatrix.Translation.
574+
// In fact, the latter doesn't require to be explicitly enabled and is actually combined with
575+
// this at runtime (ie. the whole transform matrix is combined with the additional translation
576+
// from the "Translation" property, if any), and the two can be set and animated independently.
577+
// In this case we're just interested in the "Translation" property, which is more commonly used
578+
// as it can also be animated directly with a Vector3 animation instead of a Matrix4x4 one.
579+
GetVisual(element).Properties.InsertVector3("Translation", value.ToVector3());
580+
}
581+
506582
private static double GetOpacityForElement(UIElement element)
507583
{
508584
var visual = GetVisual(element);
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Threading.Tasks;
6+
using Microsoft.VisualStudio.TestTools.UnitTesting;
7+
using Microsoft.Toolkit.Uwp;
8+
using Windows.UI.Xaml.Controls;
9+
using Microsoft.Toolkit.Uwp.UI;
10+
using System.Numerics;
11+
using Windows.UI.Composition;
12+
using Microsoft.Toolkit.Uwp.UI.Animations;
13+
14+
namespace UnitTests.UWP.UI
15+
{
16+
[TestClass]
17+
[TestCategory("Test_VisualExtensions")]
18+
public class Test_VisualExtensions : VisualUITestBase
19+
{
20+
[TestMethod]
21+
public async Task GetDefaultTranslation()
22+
{
23+
await App.DispatcherQueue.EnqueueAsync(() =>
24+
{
25+
var button = new Button();
26+
27+
string text = VisualExtensions.GetTranslation(button);
28+
29+
Assert.AreEqual(text, "<0, 0, 0>");
30+
});
31+
}
32+
33+
[TestMethod]
34+
public async Task SetAndGetTranslation()
35+
{
36+
await App.DispatcherQueue.EnqueueAsync(async () =>
37+
{
38+
var button = new Button();
39+
var grid = new Grid() { Children = { button } };
40+
41+
VisualExtensions.SetTranslation(button, "80, 20, 0");
42+
43+
await SetTestContentAsync(grid);
44+
45+
var success = button.GetVisual().Properties.TryGetVector3("Translation", out Vector3 translation);
46+
47+
Assert.AreEqual(success, CompositionGetValueStatus.Succeeded);
48+
Assert.AreEqual(translation, new Vector3(80, 20, 0));
49+
50+
string text = VisualExtensions.GetTranslation(button);
51+
52+
Assert.AreEqual(text, new Vector3(80, 20, 0).ToString());
53+
});
54+
}
55+
56+
[TestMethod]
57+
public async Task SetAndAnimateTranslation()
58+
{
59+
await App.DispatcherQueue.EnqueueAsync(async () =>
60+
{
61+
var button = new Button();
62+
var grid = new Grid() { Children = { button } };
63+
64+
VisualExtensions.SetTranslation(button, "80, 20, 0");
65+
66+
await SetTestContentAsync(grid);
67+
68+
await AnimationBuilder.Create()
69+
.Translation(to: new Vector3(11, 22, 0))
70+
.StartAsync(button);
71+
72+
var success = button.GetVisual().Properties.TryGetVector3("Translation", out Vector3 translation);
73+
74+
Assert.AreEqual(success, CompositionGetValueStatus.Succeeded);
75+
Assert.AreEqual(translation, new Vector3(11, 22, 0));
76+
77+
string text = VisualExtensions.GetTranslation(button);
78+
79+
Assert.AreEqual(text, new Vector3(11, 22, 0).ToString());
80+
});
81+
}
82+
}
83+
}

UnitTests/UnitTests.UWP/UnitTests.UWP.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@
211211
<Compile Include="UI\Controls\Test_UniformGrid_Dimensions.cs" />
212212
<Compile Include="UI\Controls\Test_WrapPanel_Visibility.cs" />
213213
<Compile Include="UI\Controls\Test_WrapPanel_BasicLayout.cs" />
214+
<Compile Include="UI\Extensions\Test_VisualExtensions.cs" />
214215
<Compile Include="UI\Person.cs" />
215216
<Compile Include="UI\Test_AdvancedCollectionView.cs" />
216217
<Compile Include="UnitTestApp.xaml.cs">

0 commit comments

Comments
 (0)