Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows 8 - Animating custom property in code-behind

Tags:

c#

windows-8

wpf

Basically, I want to make bunch of Shapes and make them animated. So I came up with following custom class:

public class FunkyShape : DependencyObject
{
    public double Animator
    {
        get { return (double)GetValue(AnimatorProperty); }
        set { SetValue(AnimatorProperty, value); }
    }

    public static readonly DependencyProperty AnimatorProperty =
        DependencyProperty.Register("Animator", typeof(double), typeof(FunkyShape), 
        new PropertyMetadata(0, new PropertyChangedCallback(Animator_Changed)));

    private static void Animator_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        double delta = (double)e.NewValue - (double)e.OldValue;

        ((FunkyShape)d).ProcessDelta((double)e.NewValue, delta);
    }

    private void ProcessDelta(double val, double delta)
    {
        Holder.Width = val;
        Holder.Height = val;

        // Keep shape centered
        HolderPosition.X = delta / 2;
        HolderPosition.Y = delta / 2;
    }

    private Shape Holder;
    public TranslateTransform HolderPosition
    {
        get { return (TranslateTransform)Holder.RenderTransform; }
    }


    public FunkyShape(Canvas playground, Shape shapeToInit)
    {
        Holder = shapeToInit;

        Holder.Width = 10;
        Holder.Height = 10;
        Holder.Fill = new SolidColorBrush(Colors.Blue);
        Holder.HorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment.Center;
        Holder.RenderTransform = new TranslateTransform()
        {
            X = 500,
            Y = 500
        };
        Holder.RenderTransformOrigin = new Point(0.5, 0.5);

        // init done
        playground.Children.Add(Holder);

        Animate();
    }

    public void Animate()
    {
        DoubleAnimation g1 = GrowAnimation();

        Storyboard sb = new Storyboard();
        Storyboard.SetTarget(g1, this);

        // CAN'T FIND ANIMATOR PROPERTY
        Storyboard.SetTargetProperty(g1, "Animator");

        sb.Children.Add(g1);

        sb.Begin(); // THROWS EXCEPTION
    }

    private static DoubleAnimation GrowAnimation()
    {
        DoubleAnimation growAnimation = new DoubleAnimation();
        growAnimation.Duration = TimeSpan.FromMilliseconds(3000);
        growAnimation.From = 0;
        growAnimation.To = 100;
        growAnimation.AutoReverse = true;
        growAnimation.EnableDependentAnimation = true;
        growAnimation.RepeatBehavior = new RepeatBehavior(5);
        return growAnimation;
    }
}

However, when I try making an instance of the class and adding it to the canvas, I get Exception - Storyboard.Being() throws it and tells me that it can't find Animator property.

So - what am I doing wrong?

EDIT: After 3 code changes - it is still not working; I get "Cannot resolve TargetProperty Animator on specified object" error. So if somebody knows the answer - please help out by modifying the code. Thanks!

EDIT: OK, after 24 hours of banging head against the wall there is some progress - if I add shape through XAML it animates, but if I add it through code behind (Canvas.Children.Add), it doesn't work. Let me see if I can figure out why.

like image 220
nikib3ro Avatar asked Oct 15 '12 06:10

nikib3ro


4 Answers

OK,

I've found the workaround for what is obviously a bug within the framework (although I'm sure some MS employee will post response and say it's a feature/it-is-by-design). Several things need to be done:

  1. Add default/parameter-less constructor
  2. Change base class of FunkyShape to UserControl.
  3. Open up XAML view of the Page class where you want to add shapes
  4. Add one instance of FunkyShape as a child within the Canvas XAML (<tm:FunkyShape /> for example). IT WON'T WORK WITHOUT THIS.
  5. Make an instance of FunkyShape in code-behind, add it to canvas, start animation and enjoy seeing it works
  6. Switch to less buggy technology.
like image 186
nikib3ro Avatar answered Sep 23 '22 10:09

nikib3ro


In Windows 8 you cannot animate custom properties without also setting the enabledependentanimation property to true. This is because non-deterministic animations are disabled by default.

Reference: http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.media.animation.pointanimation.enabledependentanimation.aspx

like image 35
Jerry Nixon Avatar answered Sep 25 '22 10:09

Jerry Nixon


Yes, you must define this property as a dependency property, not just a regular CLR property. This involves quite a bit of simple boiler plate code. See thus blog post for a complete example:

http://timheuer.com/blog/archive/2012/03/07/creating-custom-controls-for-metro-style-apps.aspx

like image 25
ColinE Avatar answered Sep 23 '22 10:09

ColinE


OK, I had this problem too, but I didn't want to include a public parameterless constructor in my class, so I found another way.

Basically, the issue is that WinRT is a native platform, and it can't do reflection on .NET code. That's why the build process for WinRT apps generates metadata about the types used in XAML (you can find the relevant code in obj/(Debug|Release)/XamlTypeInfo.g.cs).

If a type is never used in XAML, no metadata about this type is generated, which means (among other things) that you can't animate the properties of the type.

If you're writing a class library, you can just include a XAML resource dictionary and declare a dummy instance of the type; it will cause metadata to be generated. However, it requires that the type has a public parameterless constructor, which might not be desirable.

So there is another solution: provide the metadata yourself. There are a several interfaces to implement, and they have many members, so it can be quite tedious to do manually. Fortunately, you don't have to! Here's what you can do:

  • add a public parameterless constructor to the class (temporarily)
  • create a XAML ResourceDictionary and declare an instance of the class in it (as described above)
  • copy the XamlTypeInfo.g.cs file into your project (I renamed it to XamlTypeInfo.cs)
  • replace the call to the constructor with throw new NotImplementedException()
  • delete the ResourceDictionary file
  • remove the public parameterless constructor

And you're done, the animation now works properly.

The process is still quite tedious, so it would be nice to have a tool to do the work for us...


EDIT: much easier solution: apply the [Bindable] attribute to the class. It makes the metadata generator take the type into account even if it's not used in XAML. (ignore the fact that the doc says it's for C++ types; it works just fine on C# classes as well)

like image 27
Thomas Levesque Avatar answered Sep 23 '22 10:09

Thomas Levesque