I am using UWP and working with the Composition API to programmatically slide items around on the screen and I have found that when the application first starts that the initial slide for many of the elements are not performing correctly. Upon further inspection I have found that the ElementVisual's Offset properties that I use for computing and setting both the initial and destination positions of the animation are sometimes coming up with all zero values for the X,Y,Z values on the very first animation. Best I can tell all animations of that element from that point forward are working correctly as long as the app keeps running.
The app I am working on is more complex and interesting, but I have created a simplified test application to demonstrate the problem I am encountering.
From a new blank UWP project I add the following GridView and Button to the root grid of the page:
<GridView x:Name="TestGridView" HorizontalAlignment="Center" VerticalAlignment="Center">
<GridViewItem >
<Border Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 1" />
</Grid>
</Border>
</GridViewItem>
<GridViewItem>
<Border Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 2" />
</Grid>
</Border>
</GridViewItem>
<GridViewItem>
<Border Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 3"/>
</Grid>
</Border>
</GridViewItem>
</GridView>
<Button Height="125" Width="125" Click="Button_Click"/>
In the codebehind file I add the following additional using statements, a variable declaration and this event handler:
using System.Numerics;
using Windows.UI.Composition;
using Windows.UI.Xaml.Hosting;
using System.Diagnostics;
Compositor compositor;
private void Button_Click(object sender, RoutedEventArgs e)
{
foreach (var element in TestGridView.ItemsPanelRoot.Children)
{
var slideVisual = ElementCompositionPreview.GetElementVisual(element);
Debug.WriteLine("Element Offset: " + slideVisual.Offset);
var slideAnimation = compositor.CreateVector3KeyFrameAnimation();
slideAnimation.InsertKeyFrame(0f, slideVisual.Offset);
slideAnimation.InsertKeyFrame(1f, new Vector3(slideVisual.Offset.X, slideVisual.Offset.Y + 20f, 0f));
slideAnimation.Duration = TimeSpan.FromMilliseconds(800);
slideVisual.StartAnimation(nameof(slideVisual.Offset), slideAnimation);
}
}
I also initialize the compositor in the MainPage() constructor:
public MainPage()
{
this.InitializeComponent();
compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
}
I then run the project in Debug mode from Visual Studio. Pressing the button produces the following output in my Immediate Window (separator lines added in editing for clarity)
Element Offset: <0, 0, 0>
Element Offset: <0, 0, 0>
Element Offset: <0, 0, 0>
Element Offset: <0, 20, 0>
Element Offset: <129, 0, 0>
Element Offset: <258, 0, 0>
Element Offset: <0, 40, 0>
Element Offset: <129, 20, 0>
Element Offset: <258, 20, 0>
Element Offset: <0, 60, 0>
Element Offset: <129, 40, 0>
Element Offset: <258, 40, 0>
Note that the first of the three elements does animate on the first slide, but the other two do not and that their Offset values are all zero values.
On each slide there after all three elements animate from their previous position as they should, although the side effects from the first slide animation linger on.
I have tried unsuccessfully to find a way to force the elements in our application to update their Offset values so that the initial slide animation for each element works correctly including doing things like inserting a dummy WarmUp animation just before the actual intended animation that very quickly tells the element to either move from its current position to the same position, or a very slightly modified start position to its actual current position.
var warmUpAnimation = compositor.CreateVector3KeyFrameAnimation();
warmUpAnimation.InsertKeyFrame(0.0f, new Vector3(slideVisual.Offset.X + 1, slideVisual.Offset.Y, slideVisual.Offset.Z));
warmUpAnimation.InsertKeyFrame(1f, slideVisual.Offset);
warmUpAnimation.Duration = TimeSpan.FromMilliseconds(1);
I am guessing this is a bug in the Windows Composition API but I am hoping someone can suggest a simple and effective work around that will force the ElementVisual to correctly initialize after load.
Two things:
1 - Generally, DON'T use
Offset
to animate the position of Visual's that are created from aFrameworkElement
. Use the manualTranslation
property instead.This can be done by calling the following on each element, preferably in their
Loaded
eventsYou would then change the animation to something like
This used to be very evident in the old Composition animation documentation, but they seem to have largely wiped away its mentions from the recent updates. In general the composition documentation isn't very good at all. I literally can't even find the page right now that explains why you should use Translation over Offset (which is to do with XAML properties overwriting Composition Properties).
Essentially
Offset
is the absolute X.Y. position of an element relative to its parent set by the XAML rendering engine, andTranslation
is akin to aRenderTransform
on top of that - the different is XAML will overwrite Composition'sOffset
with its values with no care as to what you put in at the Composition level, but it has no "knowledge" of theTranslation
property so it never overwrites it.Translation
always applies as an additive to the current Offset. It also only works for Visuals created from FrameworkElements - it's unneeded for any other type of CompositionVisual as their offsets don't get changed by XAML.2 - It may help to call
ElementCompositionPreview.GetElementVisual()
on each target element you know you're going to animate at some point in their loaded events - note it has to be directly on the element that is going to be animated. This ensures the handoff visual is created and fully ready by the time you need it. (Created is asynchronous due to the nature of how Composition APIs works with the system compositor).