I am trying to learn .NET
programming. As a part of my learning, I tried to make some effects on buttons. It is working... but not as smooth as I imagined! I
So first off, you don't want to do the exact same thing 3 times. Create a single method to add the appropriate handlers for a button, and then just write the code once to handle any given button.
Note that you can go into the expand/contract tick handlers and use the percentComplete
value to set the height as well, to move the color along a spectrum (this would involve some mathematics of colors to do though) or to alter any other aspect of the button. If you're really motivated to generalize it you could add a parameter to the method of Action<double>
that does something to the object based on the given percent progress.
public void AddAnimation(Button button)
{
var expandTimer = new System.Windows.Forms.Timer();
var contractTimer = new System.Windows.Forms.Timer();
expandTimer.Interval = 10;//can adjust to determine the refresh rate
contractTimer.Interval = 10;
DateTime animationStarted = DateTime.Now;
//TODO update as appropriate or make it a parameter
TimeSpan animationDuration = TimeSpan.FromMilliseconds(250);
int initialWidth = 75;
int endWidth = 130;
button.MouseHover += (_, args) =>
{
contractTimer.Stop();
expandTimer.Start();
animationStarted = DateTime.Now;
button.BackColor = Color.DimGray;
};
button.MouseLeave += (_, args) =>
{
expandTimer.Stop();
contractTimer.Start();
animationStarted = DateTime.Now;
button.BackColor = Color.Red;
};
expandTimer.Tick += (_, args) =>
{
double percentComplete = (DateTime.Now - animationStarted).Ticks
/ (double)animationDuration.Ticks;
if (percentComplete >= 1)
{
expandTimer.Stop();
}
else
{
button.Width = (int)(initialWidth +
(endWidth - initialWidth) * percentComplete);
}
};
contractTimer.Tick += (_, args) =>
{
double percentComplete = (DateTime.Now - animationStarted).Ticks
/ (double)animationDuration.Ticks;
if (percentComplete >= 1)
{
contractTimer.Stop();
}
else
{
button.Width = (int)(endWidth -
(endWidth - initialWidth) * percentComplete);
}
};
}
When you modify a controls properties, it takes effect instantaneously. What you desire is something that is usually known as some type of fade or tweening. There might be libraries out there to do this, but if you wanted to write this yourself for fun, you can use a Timer object, and on each tick update the color.
What you would do is set a color as the TargetColor somewhere(this is a variable or property you make up), and then start a timer that ticks maybe every 10 milliseconds. In each tick, you look at the start time, and how long has passed since then. If you want the animation to take place of a full second, then that is 1000 milliseconds. So during each tick, you look at the amount of time that has passed, maybe 200 milliseconds, then divide 200/1000 to get the fraction of time that you have gone into the animation. Then you look at a difference between the Start and Target Color, multiply that difference by the fraction, and add the result to the start color. In other words, 200 milliseconds into an animation that last 1000 milliseconds, means you are 20% into the animation. Thus you want to set the color to be whatever color is 20% from the start color towards the end color.
There's alot you could do to refine this. Perhaps having a subclass Button control that encapsulates the timer and exposes functions/properties to track start/end color, animation transition time, etc. Most animated UI features like this let you specify how long the animation should last, and then it interpolates the inbetween states as it transitions. This is the origin of the term tweening, as it comes from transitioning from one state to another by inbetweening
If you are using WinForms, animations are going to be rather painful and you will have to handle them yourself via Timer
objects.
If you are getting into .NET and want to make cool-looking applications with animatons and styling, I highly recommend you look at WPF instead. It can do animations very easily though C# or XAML.
While it is still possible in WinForms, it will take far more development time where as those features are built into WPF already (and optimized).