IneedHelp
IneedHelp

Reputation: 1758

How to fix simple MouseOver animation of a WPF control?

I am animating a simple MouseOver event using triggers and storyboards.

The context of my project requires the control's opacity to increase when the user moves the mouse over it, and to decrease when the user moves the mouse away from it.

Now, I've got no problem going from 30% opacity to 100% and from 100% back to 30%, but what I do have problems with are intermediary values/states.

For example: if the user keeps the mouse over the control until the control reaches only 80% opacity and then moves the mouse away, the opacity decrease animation starts from 100% opacity.

How can I make it so that the control resumes increasing/decreasing opacity from the last value it reached?

note: I have tried various methods, but unworthy to mention them since they didn't give the expected result.

EDIT: Apparently what I am aiming for is the default behaviour in Blend, but for some reason it didn't work the first time.

Upvotes: 1

Views: 315

Answers (2)

Anders
Anders

Reputation: 1643

There are several factors that need to be taken into account. One is if an animation does not finish, like you mention, then the new start value of the next animation should start from the current opacity.

Secondly then the speed of the animation should also be adjusted if the previous animation did not finish, otherwise the following animation will seem slow.

I've made this attachable behavior for you where you are able to animate the opacity of any UIElement. I've tested it in Blend and in Visual Studio, where it works as requested. Attach like this:

<Button> <i:Interaction.Behaviors> <behaviors:MouseOverGlowBehavior /> </i:Interaction.Behaviors> </Button>

The Behavior:

class MouseOverGlowBehavior : Behavior<UIElement>
{
    private const double DEFAULT_PASSIVE = 0.3d, DEFAULT_ACTIVE = 1d;

    private readonly DoubleAnimation enterAnimation, exitAnimation;

    private TimeSpan _duration = TimeSpan.FromMilliseconds(600);
    private double _span;

    public TimeSpan Duration
    {
        get { return _duration; }
        set { _duration = value; }
    }
    public double PassiveOpacity
    {
        get { return exitAnimation.To.Value; }
        set
        {
            if (exitAnimation.To.Value == value) return;

            if (value > 100) value = 100;
            else if (value < 0) value = 0;

            exitAnimation.To = value;
            _span = Math.Abs(ActiveOpacity - value);

            var target = AssociatedObject;
            if (target != null)
                target.Opacity = value;
        }
    }
    public double ActiveOpacity
    {
        get { return enterAnimation.To.Value; }
        set
        {
            if (enterAnimation.To.Value == value) return;

            if (value > 100) value = 100;
            else if (value < 0) value = 0;

            enterAnimation.To = value;
            _span = Math.Abs(value - PassiveOpacity);
        }
    }

    public MouseOverGlowBehavior()
    {
        _span = Math.Abs(DEFAULT_ACTIVE - DEFAULT_PASSIVE);
        enterAnimation = new DoubleAnimation(DEFAULT_ACTIVE, Duration);
        exitAnimation = new DoubleAnimation(DEFAULT_PASSIVE, Duration);
    }

    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.Opacity = PassiveOpacity;
        AssociatedObject.MouseEnter += MouseEnter;
        AssociatedObject.MouseLeave += MouseLeave;
    }
    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.MouseEnter -= MouseEnter;
        AssociatedObject.MouseLeave -= MouseLeave;
        AssociatedObject.Opacity = 1d;
    }

    private void MouseEnter(object sender, MouseEventArgs e)
    {
        //Increase Opacity animation
        TimeSpan duration;
        if (!CalculateDuration(ActiveOpacity, out duration)) return;
        enterAnimation.Duration = duration;

        //Start animation
        AssociatedObject.BeginAnimation(UIElement.OpacityProperty, enterAnimation);
    }
    private void MouseLeave(object sender, MouseEventArgs e)
    {
        //Decrease Opacity animation
        TimeSpan duration;
        if (!CalculateDuration(PassiveOpacity, out duration)) return;
        exitAnimation.Duration = duration;

        //Start animation
        AssociatedObject.BeginAnimation(UIElement.OpacityProperty, exitAnimation);
    }

    /// <summary>
    /// This method adjusts the adjustedDuration to take into account if previous animation did not reach it's assigned value
    /// </summary>
    /// <param name="endOpacity">Target opacity</param>
    /// <param name="adjustedDuration">Duration adjusted by current <see cref="UIElement.Opacity"/></param>
    /// <returns>True if animation is nessesary, otherwise false</returns>
    private bool CalculateDuration(double endOpacity, out TimeSpan adjustedDuration)
    {
        adjustedDuration = Duration;
        var actualSpan = Math.Abs(endOpacity - AssociatedObject.Opacity);
        if (actualSpan == 0) return false;

        var delta = actualSpan / _span;
        if (delta == 1) return true;

        adjustedDuration = TimeSpan.FromMilliseconds(Duration.TotalMilliseconds * delta);
        return true;
    }
}

Upvotes: 1

MDoobie
MDoobie

Reputation: 282

Well one solution could be that you animate the object from code so you can store the percent value (e.g. 80%) while the animation of MouseOver state is running. And then when the cursor is moved away, you can start the decrease animation from the stored value.

Upvotes: 0

Related Questions