Question

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.

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top