How do I create custom animations in UWP

0

The basic animations in UWP is good and all but I would like to create my own animation. So I looked at the different animations and saw that they all are a subclass of Timeline. Just to test, I decided to do a copy of the DoubleAnimation class like this:

public sealed class MyAnimation : Timeline
{
    public static DependencyProperty _ByProperty;
    public static DependencyProperty _EasingFunctionProperty;
    public static DependencyProperty _EnableDependentAnimationProperty;
    public static DependencyProperty _FromProperty;
    public static DependencyProperty _ToProperty;

    public MyAnimation() : base()
    {
    }

    static MyAnimation()
    {
        _ByProperty = DependencyProperty.Register("By", typeof(double?), typeof(MyAnimation), new PropertyMetadata((double?)null));
        _EasingFunctionProperty = DependencyProperty.Register("EasingFunction", typeof(EasingFunctionBase), typeof(MyAnimation), new PropertyMetadata(null));
        _EnableDependentAnimationProperty = DependencyProperty.Register("EnableDependentAnimation", typeof(bool), typeof(MyAnimation), new PropertyMetadata(false));
        _FromProperty = DependencyProperty.Register("From", typeof(double?), typeof(MyAnimation), new PropertyMetadata((double?)null));
        _ToProperty = DependencyProperty.Register("To", typeof(double?), typeof(MyAnimation), new PropertyMetadata((double?)null));
    }

    public static DependencyProperty ByProperty { get { return _ByProperty; } }
    public static DependencyProperty EasingFunctionProperty { get { return _EasingFunctionProperty; } }
    public static DependencyProperty EnableDependentAnimationProperty { get { return _EnableDependentAnimationProperty; } }
    public static DependencyProperty FromProperty { get { return _FromProperty; } }
    public static DependencyProperty ToProperty { get { return _ToProperty; } }

    public double? To { get { return (double?)GetValue(_ToProperty); } set { SetValue(_ToProperty, value); } }
    public double? From { get { return (double?)GetValue(_FromProperty); } set { SetValue(_FromProperty, value); } }
    public bool EnableDependentAnimation { get { return (bool)GetValue(_EnableDependentAnimationProperty); } set { SetValue(_EnableDependentAnimationProperty, value); } }
    public EasingFunctionBase EasingFunction { get { return (EasingFunctionBase)GetValue(_EasingFunctionProperty); } set { SetValue(_EasingFunctionProperty, value); } }
    public double? By { get { return (double?)GetValue(_ByProperty); } set { SetValue(_ByProperty, value); } }
}

Then I created an object to move:

<Grid Name="Root" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Ellipse Width="200" Height="200" Fill="Green" Name="MyEllipse"/>
</Grid>

And then I start the animation when everything is loaded:

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    Storyboard sb = new Storyboard();
    MyAnimation da = new MyAnimation();
    da.From = 200;
    da.To = 500;
    da.Duration = new Duration(TimeSpan.FromSeconds(5));
    da.EnableDependentAnimation = true;
    sb.Children.Add(da);
    Storyboard.SetTargetProperty(da, "Width");
    Storyboard.SetTarget(da, MyEllipse);
    sb.Begin();
}

Now to my problems. When I run this I get the following exception:

ERROR: System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component.
    at Windows.UI.Xaml.Media.Animation.Storyboard.Begin()
    at TestAnimation.MainPage.Button_Click(Object sender, RoutedEventArgs e)

Which gives me zero explanation for what went wrong. I am guessing something more needs to be done inside the constructor but I can not read the source code of the DoubleAnimation, only the metadata, which makes it impossible to know what actually happens. Anyone know what needs to be done in order to get this to work?

c#
uwp
asked on Stack Overflow Sep 20, 2017 by FewWords

2 Answers

0

Please read the comments on the question because I think they still give a better answer to the question

But, I came across this question as I was also getting a

ERROR: System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component

In my case it had nothing to do with the animation system. It was because I had made a mistake in my scaling algorithm and was returning a desiredSize of new Size(27688, 8) from my override MeasureOverride(...).

answered on Stack Overflow Jul 31, 2018 by AlSki
0

From I read and after multiple attempts, UWP does not offer the means to create your own animations as there are no methods to override from Timeline.

My scenario was supposed to be a simple one - how do I animate a Grid using a DoubleAnimation?! Sounds trivial, but a) Grid takes GridLength and not Double, b) one cannot attach an IValueConverter to StoryBoard.TargetProperty, and c) creating your own custom animation seems not to be supported in UWP.

As such, the only option remaining is to leverage from the existing animations and build a layer on top. The most common approach is to resort to two Dependency Properties in the Page, bind one with the DoubleAnimation and another with the Grid. Works, but it is not scalable, especially if you want to decouple your styling.

Here is the outline of my approach: - Create a global ConverterService that attaches a property "Converter" to any Dependency Object - Create a generic converter that is an IValueConverter - The generic value converter has a DependencyProperty called Input of type Tin, and another DependencyProperty called Output of type Tout - Bind the DoubleAnimation with the Input of your converter and the Output with the Grid.

Here is how it is used:

    <Grid Style="{StaticResource MainGrid}" x:Name="MainGrid">

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="{Binding ElementName=MyMove, Path=Output}" x:Name="MyColumn">
            <conv:ConverterService.Converter>
                <conv:DoubleToGridLengthConverter x:Name="MyMove" Input="2" GridUnit="Star" />
            </conv:ConverterService.Converter>
        </ColumnDefinition>
     </Grid.ColumnDefinitions>

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState x:Name="Portrait">
                <VisualState.StateTriggers>
                    <stateTriggers:WindowAspectRatioTrigger Orientation="Portrait" />
                </VisualState.StateTriggers>
                <VisualState.Storyboard>
                    <Storyboard>

                        <DoubleAnimation
                            BeginTime="0:0:0"
                            EnableDependentAnimation="True"
                            Storyboard.TargetName ="MyMove"
                            Storyboard.TargetProperty="Input"
                            From="1" To="15" Duration="0:0:3" FillBehavior="HoldEnd" />
                    </Storyboard>
                </VisualState.Storyboard>
            </VisualState>


        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

   </Grid>

And of course, mind to import the namespaces at the top of your page. In my case:

<base:BasePage
x:Class="MyProject.MainPage"
x:Name="MyPage"
xmlns:base ="using:MyProject.Base"
xmlns:local="using:MyProject"
xmlns:conv="using:MyProject.Converters"
xmlns:stateTriggers="using:MyProject.StateTriggers"
xmlns:ctrl="using:MyProject.UserControls"
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"
mc:Ignorable="d">

And here is the code for the ConverterService and BaseConverter and DoubleToGridLengthConverter:

   /// <summary>A generic converter.</summary>
public interface IConverter: IValueConverter    
{
    /// <summary>The input value.</summary>
    object Input { get; set; }

    /// <summary>The output value.</summary>
    object Output { get; set; }
}

/// <summary>A service that provides conversion capabilities to dependency objects via an attached property.</summary>
public abstract class ConverterService : DependencyObject
{
    /// <summary>The Converter dependency property.</summary>
    public static readonly DependencyProperty ConverterProperty;

    /// <summary>The Converters dependency property which is a collection of Converter.</summary>
    public static readonly DependencyProperty ConvertersProperty;

    static ConverterService()
    {
        ConverterProperty = DependencyProperty.RegisterAttached("Converter",
                                                                typeof(IConverter),
                                                                typeof(ConverterService),
                                                                new PropertyMetadata(null));

        ConvertersProperty = DependencyProperty.RegisterAttached("Converters",
                                                                typeof(IList<IConverter>),
                                                                typeof(ConverterService),
                                                                new PropertyMetadata(new List<IConverter>()));
    }

    /// <summary>Property getter for attached property Converter.</summary>
    /// <param name="element">The dependency object to which the attached property applies.</param>
    /// <returns>Returns the converter associated with the specified dependency object.</returns>
    public static IConverter GetConverter(DependencyObject element)
    {
        return (IConverter)element.GetValue(ConverterProperty);
    }

    /// <summary>Property getter for attached property Converter.</summary>
    /// <param name="element">The dependency object to which the attached property applies.</param>
    /// <param name="value">The converter to associate.</param>
    public static void SetConverter(DependencyObject element, IConverter value)
    {
        element.SetValue(ConverterProperty, value);
    }

    /// <summary>Property getter for attached property Converters.</summary>
    /// <param name="element">The dependency object to which the attached property applies.</param>
    /// <returns>Returns the collection of converters associated with the specified dependency object.</returns>
    public static IList<IConverter> GetConverters(DependencyObject element)
    {
        return (IList<IConverter>)element.GetValue(ConverterProperty);
    }

    /// <summary>Property getter for attached property Converters.</summary>
    /// <param name="element">The dependency object to which the attached property applies.</param>
    /// <param name="value">The converters to associate.</param>
    public static void SetConverters(DependencyObject element, IList<IConverter> value)
    {
        element.SetValue(ConverterProperty, value);
    }
}

/// <summary>A strongly-typed base converter.</summary>
/// <typeparam name="Tin">The input type.</typeparam>
/// <typeparam name="Tout">The output type.</typeparam>
public abstract class BaseConverter<Tin, Tout>: DependencyObject, IConverter
{
    /// <summary>The Input dependency property.</summary>
    public static readonly DependencyProperty InputProperty;

    /// <summary>The Output dependency property.</summary>
    public static readonly DependencyProperty OutputProperty;

    static BaseConverter()
    {
        OutputProperty = DependencyProperty.Register("Output",
                                                     typeof(Tout),
                                                     typeof(BaseConverter<Tin, Tout>),
                                                     new PropertyMetadata(GetDefault(typeof(Tout)), OutChanged));

        InputProperty = DependencyProperty.Register("Input",
                                                     typeof(Tin),
                                                     typeof(BaseConverter<Tin, Tout>),
                                                     new PropertyMetadata(GetDefault(typeof(Tin)), InChanged));
    }

    /// <summary>Gets or sets the input value to convert from.</summary>
    public Tin Input
    {
        get { return (Tin)GetValue(InputProperty); }
        set { SetValue(InputProperty, value); }
    }

    /// <summary>Gets or sets the output value to convert to.</summary>
    public Tout Output
    {
        get { return (Tout)GetValue(OutputProperty); }
        set { SetValue(OutputProperty, value); }
    }

    /// <summary>Gets the from type.</summary>
    public static Type From
    {
        get { return typeof(Tin); }
    }        

    /// <summary>Gets the to type.</summary>
    public static Type To
    {
        get { return typeof(Tout); }
    }

    #region IConverter

    object IConverter.Input { get => Input; set => Input = (Tin)value; }
    object IConverter.Output { get => Output; set => Output = (Tout)value; }

    #endregion

    #region IValueConverter

    object IValueConverter.Convert(object value, Type targetType, object parameter, string language)
    {
        return Convert((Tin)value, parameter, language);
    }

    object IValueConverter.ConvertBack(object value, Type targetType, object parameter, string language)
    {
        return ConvertBack((Tout)value, parameter, language);
    }

    #endregion

    /// <summary>Converts an input value into an output value.</summary>
    /// <param name="value">The value to convert.</param>
    /// <param name="parameter">An optional parameter to pass onto the conversion process (from IValueConverter).</param>
    /// <param name="language">An optional language parameter to pass onto the conversion process (from IValueConverter).</param>
    /// <returns>Returns the converted value.</returns>
    protected abstract Tout Convert(Tin value, object parameter, string language);

    /// <summary>Converts back an output value into its original input.</summary>
    /// <param name="value">The value to convert.</param>
    /// <param name="parameter">An optional parameter to pass onto the conversion process (from IValueConverter).</param>
    /// <param name="language">An optional language parameter to pass onto the conversion process (from IValueConverter).</param>
    /// <returns>Returns the converted value from output to input.</returns>
    protected abstract Tin ConvertBack(Tout value, object parameter, string language);

    private static object GetDefault(Type type)
    {
        return type.IsValueType ? Activator.CreateInstance(type) : null;
    }

    private static void InChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as BaseConverter<Tin, Tout>;
        control.Output = (Tout)((d as IValueConverter).Convert(e.NewValue, typeof(Tout), null, null));
    }

    private static void OutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as BaseConverter<Tin, Tout>;
        //control.Input = (Tin)((d as IValueConverter).ConvertBack(e.NewValue, typeof(Tin), null, null));
    }        
}

    /// <summary>Converts Double to and from GridLength.</summary>
public sealed class DoubleToGridLengthConverter : BaseConverter<double?, GridLength?>
{
    /// <summary>The GridUnit dependency property.</summary>
    public static readonly DependencyProperty GridUnitProperty;

    static DoubleToGridLengthConverter()
    {
        GridUnitProperty = DependencyProperty.Register("GridUnit",
                                                       typeof(GridUnitType),
                                                       typeof(DoubleToGridLengthConverter),
                                                       new PropertyMetadata(GridUnitType.Auto, UnitChanged));
    }

    /// <summary>Gets or sets the type of grid unit to be used in the conversions.</summary>
    public GridUnitType GridUnit
    {
        get { return (GridUnitType)GetValue(GridUnitProperty); }
        set { SetValue(GridUnitProperty, value); }
    }

    protected override GridLength? Convert(double? value, object parameter, string language)
    {
        return value == null || !value.HasValue
               ? new GridLength()
               : new GridLength(value.Value, this.GridUnit);
    }

    protected override double? ConvertBack(GridLength? value, object parameter, string language)
    {
        return value == null || !value.HasValue
               ? default(double?)
               : value.Value.Value;
    }

    private static void UnitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as DoubleToGridLengthConverter;
        control.Output = control.Convert(control.Input, null, null);
    }
}

And if you want to use my AspectRatio state trigger, here it is:

    /// <summary>Aspect ratios.</summary>
public enum AspectRatio
{
    /// <summary>Portrait.</summary>
    Portrait,

    /// <summary>Landscape.</summary>
    Landscape
}

/// <summary>A state trigger based on the aspect ratio of the window.</summary>
public class WindowAspectRatioTrigger: StateTriggerBase
{
    /// <summary>The target orientation.</summary>
    private static readonly DependencyProperty OrientationProperty;

    static WindowAspectRatioTrigger()
    {
        OrientationProperty = DependencyProperty.Register("Orientation",
                                                          typeof(AspectRatio),
                                                          typeof(WindowAspectRatioTrigger),
                                                          new PropertyMetadata(AspectRatio.Landscape, new PropertyChangedCallback(DesiredAspectRatioChanged)));
    }

    public WindowAspectRatioTrigger()
    {
        this.OnAspectRatioChanged(this.Orientation);
        Window.Current.SizeChanged += Current_SizeChanged;
    }

    /// <summary>Gets or sets the desired aspect ratio for the trigger.</summary>
    public AspectRatio Orientation
    {
        get { return (AspectRatio)GetValue(OrientationProperty); }
        set { SetValue(OrientationProperty, value); }
    }

    private async void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
    {
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            SetActive(IsActive(e.Size, this.Orientation));
        });
    }

    private static void DesiredAspectRatioChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as WindowAspectRatioTrigger;
        control.OnAspectRatioChanged((AspectRatio)e.NewValue);
    }

    private static bool IsActive(Size windowSize, AspectRatio aspectRatio)
    {
        var currentOrientation = windowSize.Width >= windowSize.Height
                                 ? AspectRatio.Landscape
                                 : AspectRatio.Portrait;

        return aspectRatio == currentOrientation;
    }

    private async void OnAspectRatioChanged(AspectRatio aspectRatio)
    {
        var dimensions = Window.Current.Bounds;
        var size = new Size(dimensions.Width, dimensions.Height);

        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            SetActive(IsActive(size, aspectRatio));
        });
    }
}
answered on Stack Overflow Nov 27, 2019 by Miguel Ferreira

User contributions licensed under CC BY-SA 3.0