2

I'm trying to find a generic way to run an animation each time a property of type double is updated.

This has to be single solution which works for all double values. It means that I don't want to write a proprietory AttachedProperty for each UIElement property (One for Opacity and then another one for Height).

A pseudo-example of what I would like to accomplish:

<TextBlock x:Name="pageTitle" Text="title example" attached:AnimatedPropertyPath="(UIElement.Opacity)" Opacity="{Binding Path=TitleOpacity}" />

The attached property should listen to any change in opacity, cancel it and instead run an animation which makes it change gradually.

My question:

  1. Does this exact syntax make sense? is it doable?
  2. Is there a way to cancel the Opacity property immediate change by the binding and run the animation instead?
  3. Any links to examples would be highly appreciated as I couldn't find any myself.

I want to avoid using DataTriggers because it requires too much xaml. It would be best embedded as an attached property exactly like the peudo xaml above.

Uri Abramson
  • 6,005
  • 6
  • 40
  • 62
  • Yes it is doable. You create a `Style` for your `TextBlock`, then you create `DataTriggers` in that `Style` and then you can use `DataTrigger.EnterAction` and `DataTrigger.ExitAction` or if you require more flexible approach create `Converter` to handle changes of your `TextBlock`. – XAMlMAX May 07 '14 at 12:00
  • I don't want to use DataTrigger.EnterAction because its just too cumbersome. I would like it to be as automated as possible and not having to wire up tens of lines in xaml to get such a simple idea to work. – Uri Abramson May 07 '14 at 12:09
  • You need to edit your post cause you state 'I don't want to write a proprietory AttachedProperty' and then you wrote 'It would be best embedded as an attached property'. You need to decide. – XAMlMAX May 07 '14 at 12:12
  • Edited. I meant that I don't want to write a separate attached property for each UIElement property. A single one which is capable of handling all would be ok. – Uri Abramson May 07 '14 at 12:15
  • kk, I know when I write my `Style` I set it for the lowest common denominator i.e. `frameworkElement` and then I use `DataTriggers` to handle such behavoiur. **Tip** `Style` is going to be applied to every single Control in your xaml if you declare it like this ` – XAMlMAX May 07 '14 at 12:19
  • Could you be more specific and provice an example of such a style which can animate any double value? (same style should work for opacity changes and for Height property) – Uri Abramson May 07 '14 at 12:23
  • Anyway, Style would not be the best solution because some of my elements already have styles and I would not want override it nor to make it inherit from your style. – Uri Abramson May 07 '14 at 12:24

1 Answers1

5

My question:

  • Does this exact syntax make sense? is it doable?

Does it have to be an Attached Property? Would you be fine with using a Behavior?

  • Is there a way to cancel the Opacity property immediate change by the binding and run the animation instead?

Maybe with some hacks(not that I know of any). Again is this is an absolute must to have to intercept and cancel a normal DP action?

  • Any links to examples would be highly appreciated as I couldn't find any myself.

Well if you can tweak your requirement slightly, I can give you an example:

So if your requirement is to animate any double DP when it's bound-to value changes, we can do it with a Behavior

public class AnimateBehavior : Behavior<UIElement> {
  public static readonly DependencyProperty ToAnimateProperty =
    DependencyProperty.Register("ToAnimate", typeof(DependencyProperty),
      typeof(AnimateBehavior), new FrameworkPropertyMetadata(null));

  public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register("Value", typeof(double),
      typeof(AnimateBehavior),
      new FrameworkPropertyMetadata(0.0d, FrameworkPropertyMetadataOptions.None, ValueChangedCallback));

  public DependencyProperty ToAnimate {
    get { return (DependencyProperty) GetValue(ToAnimateProperty); }
    set { SetValue(ToAnimateProperty, value); }
  }

  public double Value {
    get { return (double) GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
  }

  private static void ValueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    var item = d as AnimateBehavior;
    if (item == null || item.AssociatedObject == null) {
      return;
    }
    var newAnimation = new DoubleAnimation((double) e.NewValue, new Duration(new TimeSpan(0, 0, 1)));
    item.AssociatedObject.BeginAnimation(item.ToAnimate, newAnimation);
  }
}

Now in xaml:

<TextBlock Text="Hello">
  <i:Interaction.Behaviors>
    <local:AnimateBehavior ToAnimate="{x:Static TextBlock.OpacityProperty}" Value="{Binding ValueYouWantToBindToOpacity}" />
  </i:Interaction.Behaviors>
</TextBlock>

Now with this approach you can animate any DP of that control that has a double type value. Like Opacity, FontSize ...

Main difference here to your original requirement is we do not bind the Value to the element. We instead have it bound to the Behavior. Now when this changes, we detect it in the behavior and via the AssociatedObject property of the behavior, apply the animation on the actual item.

We also satisfy your requirement to satisfy multiple double DP types by providing the property to animate when value changes via a DP to the behavior.

if you want to go even more generic, you can ofc make the Behavior accept a duration and type of animation too to have it even more generic.

Alternate for DP identifying property:

if you absolutely want to pass in "Opacity" and not the DP, then try something like this:

public static readonly DependencyProperty ToAnimateProperty =
  DependencyProperty.Register("ToAnimate", typeof(PropertyPath),
    typeof(AnimateBehavior), new FrameworkPropertyMetadata(null));

public PropertyPath ToAnimate
{
  get { return (PropertyPath)GetValue(ToAnimateProperty); }
  set { SetValue(ToAnimateProperty, value); }
}

so we made ToAnimate a PropertyPath

and in the ValueChanged function

private static void ValueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) {
  var item = d as AnimateBehavior;
  if (item == null || item.AssociatedObject == null) {
    return;
  }
  var sb = new Storyboard();
  var newAnimation = new DoubleAnimation((double) e.NewValue, new Duration(new TimeSpan(0, 0, 1)));
  Storyboard.SetTarget(newAnimation, item.AssociatedObject);
  Storyboard.SetTargetProperty(newAnimation, item.ToAnimate);
  sb.Children.Add(newAnimation);
  sb.Begin();
}

we create a Storyboard and use the PropertyPath with this you can have:

<TextBlock Text="Hello">
  <i:Interaction.Behaviors>
    <local:AnimateBehavior ToAnimate="Opacity" Value="{Binding ValueYouWantToBindToOpacity}" />
  </i:Interaction.Behaviors>
</TextBlock>

I'd still prefer the DP over this method.

Community
  • 1
  • 1
Viv
  • 17,170
  • 4
  • 51
  • 71
  • That's really nice! Thanks a lot! – Uri Abramson May 07 '14 at 14:03
  • Your solution is great, really. Now, I'm still interested in making it even shorter. How about still using an attached property but to change it so it would be written as followed: attached:AnimateProperty="{AnimatePropertyData Path=(UIElement.Opacity), Value={Binding OpacityValue}}" and then somehow resolve the property path of the owner..? Do you get where I'm going here? – Uri Abramson May 07 '14 at 14:06
  • There are 2 things bothering me with your solution: 1. Binding to a static DependencyProperty is not standard (at least to my knowledge its not..) 2. I would prefer to have it inlined with the definition of the object, as in, inside the same line like an attached property syntax. – Uri Abramson May 07 '14 at 14:10
  • Attached properties aren't meant for these things, it's a single value you pass in them or a complex type which you can't then instantiate in xaml. Behavior's are what are designed to give you this level of control with their Associated object. – Viv May 07 '14 at 14:17
  • "1. Binding to a static DependencyProperty is not standard (at least to my knowledge its not..)" It's the type and it never changes, binding to a strong type is better than typing a string and reflecting it at run-time with no type safety. Always try to prefer strong type bindings where you can even if it's your own enums – Viv May 07 '14 at 14:18
  • "2. I would prefer to have it inlined with the definition of the object, as in, inside the same line like an attached property syntax" I'd prefer for VS to write all my code but somethings just aint possible yet :) For your requirement you could use multiple AP's and then sync values across them. One it's shabby cos they are detached but also have a dependency among-st them. AP's get way over-usedand more so wrong than right. Xaml is meant to be declarative, long line counts are part of it. – Viv May 07 '14 at 14:22
  • I see what your saying, but from a developers point of view, its much clearer to bind to "Opacity" rather than to its x:Static OpacityProperty. This feels less standard, and would take more time for other developers in the company to get used to. As for what you said about Attached properties not meant for these types of tasks - I did come actoss some examples with attached properties animating Visibility changes (turns them into fade-in/out). its just declared as ..attch:AnimateVisibility.IsActive="True" so they can be used this way. – Uri Abramson May 07 '14 at 14:29
  • "its much clearer to bind to "Opacity" rather than to its x:Static OpacityProperty. This feels less standard, and would take more time for other developers in the company to get used to." I'm sorry but i'd have to disagree^3 on this one. Any dev who has ever written a single line of code-behind to create an animation knows they animate the static DP OpacityProperty than Opacity(`BeginAnimation(UIElement.OpacityProperty, ...);` not `BeginAnimation("Opacity", ...);` or `BeginAnimation(tb.Opacity, ...);`). If someone aint familiar with it, they need to get familiar with WPF firstly. – Viv May 07 '14 at 14:31
  • " I did come actoss some examples with attached properties animating Visibility changes (turns them into fade-in/out). its just declared as ..attch:AnimateVisibility.IsActive="True" so they can be used this way." again as i mentioned you will find AP's get way overused and quite often when there are better meant to be used solutions. In WPF you can skin a cat 100's of way's. Just cos you can doesnt often make them better than another method. In your case you have multiple variables and it aint a start/stop action either. So factors change quite a bit in this case – Viv May 07 '14 at 14:33
  • "Any dev who has ever written a single line of code-behind to create an animation knows they animate the static DP Opacityproperty" - well, in your example, its not written in the code-behind. When TargetProperty of an animation is refered in XAML, it uses the (UIElement.Opacity) syntax. That's ok either way. Your answer is great I'm going to use that :) thanks! – Uri Abramson May 07 '14 at 14:36
  • @UriAbramson np. If you absolutely must, I've added an alternate to my answer as an edit showing you how to be able to pass "Opacity". Remember to use tht with care. It's not the approach i'd use myself, but you can decide for yourself. – Viv May 07 '14 at 14:56
  • Thanks! exactly what I wanted to change! Just out of curiousity, why would you not take that approach yourself? this is how MS decided to do it with Animation storyboards afterall... – Uri Abramson May 07 '14 at 14:59
  • @UriAbramson Multiple reasons. If you use the DP approach, you cant by mistake pass in "Opacity or {x:Static OpacityProp}" or any other typos. The project would not build and you got a compile error. Passing a string, you got a run-time error and the exception would get thrown in the Behavior when the Storyboard is trying to use the value. More complicated the code gets, harder to spot the actual problem compared to direct compile time xaml errors. Secondly performance, while it might be minor optimisation, we create and use a Storyboard only for this which we did not have to with the DP – Viv May 07 '14 at 16:28