Search code examples
c#wpfanimationbrush

Continuous background brush animation


I want to make a continuous brush animation whenever the mouse is over my usercontrol.

I already have this:

    private void MainControl_MouseEnter(object sender, MouseEventArgs e)
    {            
        BeginColorAnimation(Background, BackgroundHover);
    }

    private void BeginColorAnimation(Brush from, Brush to)
    {            
        BrushAnimation anim = new BrushAnimation()
        {
            From = from,
            To = to,
            Duration = AnimationDuration
        };
        
        BackgroundBorder.BeginAnimation(Border.BackgroundProperty, anim);                        
    }        

    private void MainControl_MouseLeave(object sender, MouseEventArgs e)
    {            
        BeginColorAnimation(BackgroundHover, Background);
    }

The BrushAnimation class can be found here

The problem i have right now is that if i move the mouse quickly over the control, the Background of my control shifts from the Background brush to the BackgroundHover brush (or viceversa) instantly and i don't want that.

How can i make this to look like a continuous change between brushes?

EDIT 1

Here you can see a video of the problem i have.

Let me explain myself:

Imagine we have two brushes, one black and one blue (or any other color). When the mouse is over the control i want to change the background of my color from black to blue, and when the mouse exits the control i want to change the background back to black. So what happens if the mouse is over the control and exits halfway the animation? It looks like if was bouncing from one brush to another. I want to avoid that.

I tried using:

private void MainControl_MouseEnter(object sender, MouseEventArgs e)
{            
    BeginColorAnimation(BackgroundBorder.Background, BackgroundHover);
}

private void MainControl_MouseLeave(object sender, MouseEventArgs e)
{            
    BeginColorAnimation(BackgroundBorder.Background, Background);
}

But then i receive: 'System.InvalidOperationException' This freezable cannot be frozen.

EDIT 2

Ended up using a SolidColorBrush instead of a Brush, and using the ColorAnimation class.

public bool BackgroundFrozen = true;

        private void MainControl_MouseEnter(object sender, MouseEventArgs e)
        {            
            BeginColorAnimation((SolidColorBrush)BackgroundBorder.Background, BackgroundHover);
        }

        private void BeginColorAnimation(SolidColorBrush from, SolidColorBrush to)
        {
            if(BackgroundFrozen)
            {
                BackgroundBorder.Background = BackgroundBorder.Background.CloneCurrentValue();
                BackgroundFrozen = false;
            }

            ColorAnimation anim = new ColorAnimation()
            {                
                From = from.Color,
                To = to.Color,
                Duration = AnimationDuration
            };
            
            BackgroundBorder.Background.BeginAnimation(SolidColorBrush.ColorProperty, anim);
        }        

        private void MainControl_MouseLeave(object sender, MouseEventArgs e)
        {            
            BeginColorAnimation((SolidColorBrush)BackgroundBorder.Background, Background);
        }

For some reason the Border Background property is frozen at first, so i used a BackgroundFrozen bool to "Unfreeze" it with CloneCurrentValue() when the animation runs for first time.

Result here


Solution

  • READ "EDIT 2" AS IT IS PART OF THE SOLUTION

    As suggested by someone, i removed From = from, and make a "dynamic" duration for the animation taking into account the from and the end values.

    Result:

    start is the original value
    end is the target value
    actual is something between start and end (could be the same)

    private ColorAnimation GetColorAnimation(SolidColorBrush start, SolidColorBrush end, SolidColorBrush actual)
            {
                ColorAnimation anim = new ColorAnimation()
                {                
                    To = end.Color,
                    Duration = new Duration(TimeSpan.FromMilliseconds(GetAnimationDuration(start, end, actual)))
                };
    
                return anim;
            }
    

    GetAnimationDuration returns a double that contains the duration in milliseconds

    private double GetAnimationDuration(SolidColorBrush start, SolidColorBrush end, SolidColorBrush actual)
            {
                Color s = start.Color;
                Color e = end.Color;
                Color a = actual.Color;
    
                double Rdif = Math.Abs(s.R - e.R);
                double Gdif = Math.Abs(s.G - e.G);
                double Bdif = Math.Abs(s.B - e.B);            
    
                //Console.WriteLine("Rdif: {0}, Gdif: {1}, Bdif: {2}", Rdif, Gdif, Bdif);
    
                double Rp = Math.Abs(a.R - e.R) / Rdif;
                double Gp = Math.Abs(a.G - e.G) / Gdif;
                double Bp = Math.Abs(a.B - e.B) / Bdif;
    
                double p = 0;
                int c = 0;
    
                if(!double.IsNaN(Rp))
                {
                    p += Rp;
                    c += 1;
                }
    
                if (!double.IsNaN(Gp))
                {
                    p += Gp;
                    c += 1;
                }
    
                if (!double.IsNaN(Bp))
                {
                    p += Bp;
                    c += 1;
                }
    
                p /= c;
    
                //Console.WriteLine("Rp: {0}, Gp: {1}, Bp: {2}", Rp, Gp, Bp);
    
                double r = p * AnimationDuration;
    
                //Console.WriteLine("Result: {0}", r);
    
                return r;
            }
    

    And it's called like this:

    BackgroundBorder.Background.BeginAnimation(SolidColorBrush.ColorProperty, GetColorAnimation(startColor, endColor, (SolidColorBrush)BackgroundBorder.Background));
    

    Test results from GetAnimationDuration() with a duration of 500 ms

    ---------------------------
    Mouse enter
    ---------------------------
    R: 0, G: 0, B: 255
    Rdif: 255, Gdif: 0, Bdif: 255
    Rp: 1, Gp: NaN, Bp: 1
    Result: 500
    ---------------------------
    Mouse leave (after having passed half of the animation (approximately))
    ---------------------------
    R: 193, G: 0, B: 182
    Rdif: 255, Gdif: 0, Bdif: 255
    Rp: 0,756862745098039, Gp: NaN, Bp: 0,286274509803922
    Result: 260,78431372549