DoubleAnimation / Storyboard completes twice and resets animation value - c#

While scaling a panel using a scale transform the application needs to reset the panel back to its original size. For this purpose a reset button starts a double animation that animates the scale transform from it's start value to 1 which means the panel will have it original value.
Visually the panel is scaled back to orignal size, but after the animation finishes the storyboard's complete event is raised twice, and once both of those events has been raised the value of the scale transform is set back to the value that it had before the animation.
private void ResetButton_Click(object sender, RoutedEventArgs e)
{
if (!isReseting)
{
isReseting = true;
this.doubleAnimation = new DoubleAnimation(1, new Duration(new TimeSpan(0,0,0, 1)), FillBehavior.Stop);
this.resetStoryboard = new Storyboard();
resetStoryboard.Children.Add(doubleAnimation);
Storyboard.SetTarget(doubleAnimation, zoomSliderControl);
Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath(RangeBase.ValueProperty));
resetStoryboard.RepeatBehavior = new RepeatBehavior(1);
resetStoryboard.Completed += new EventHandler(ResetStoryboardCompleted);
resetStoryboard.Begin();
}
}
private void ResetStoryboardCompleted(object sender, EventArgs e)
{
if (resetStoryboard != null)
{
//resetStoryboard.Stop(zoomSliderControl);
//resetStoryboard.Remove(zoomSliderControl);
}
resetStoryboard = null;
doubleAnimation = null;
isReseting = false;
}
For example, if the value of the Slider control (named zoomSliderControl) is 1.5 before the animation, then it animates back to 1 as expected, but once the completed event of resetStoryBoard has been raised twice it is set back to 1.5 again.
I've tried debugging the application, and it's right after the second ResetStoryboardCompleted method has termined that the value is set to its original value so I'm guessing that I haven't configured the storyboard or animation correctly.

Appearently the default behaviour of storyboards is to revert to the original value once they have finished or(?) stopped. So the fix to this issue was to set the value of zoomSliderControl to the desired value upon storyboard completion.

Related

Hover effect CSS in a Button or Panel C# [duplicate]

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! Is there any better way to do this? Thank you in advance!
My need:
There are 3 buttons.
When you hover the mouse over one of them, it expands and when you mouse out from that button, it returns to its initial size.
private void button1_MouseHover(object sender, EventArgs e)
{
button1.BackColor = Color.White;
button1.Width = 130;
button1.BringToFront();
}
private void button1_MouseLeave(object sender, EventArgs e)
{
button1.BackColor = Color.Red;
button1.Width = 75;
}
private void button2_MouseHover(object sender, EventArgs e)
{
button2.BackColor = Color.Gray;
button2.Width = 130;
button2.BringToFront();
}
private void Form1_MouseLeave(object sender, EventArgs e)
{
button2.BackColor = Color.Red;
button2.Width = 75;
}
private void button3_MouseHover(object sender, EventArgs e)
{
button3.BackColor = Color.DimGray;
button3.Width = 130;
button3.BringToFront();
}
private void button3_MouseLeave(object sender, EventArgs e)
{
button3.BackColor = Color.Red;
button3.Width = 75;
}
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);
}
};
}
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).
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

Fading effect on Splash sreen

I am trying to make a fading effect on my splash screen, on a WPF application.
The Opacity of the image object is initially 0. This code would modify the Opacity from 0 (min) to 1 (max), but the line img_waves.Opacity just doesn't work. The image opacity remains 0.
private void Splash_ContentRendered(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(3000);
for (double x = 0; x<=1; x+=0.01d)
{
System.Threading.Thread.Sleep(15);
//MessageBox.Show(x.ToString());
img_waves.Opacity = x;
}
this.Close();
}
But, if I activate the line ´MessageBox.Show(x.ToString());´
as you can see on this image:
The code works, but I have to keep clicking on the message boxes.
My ask is: Why? Why doesn't work without the MessageBox.Show?
Because you're blocking the GUI thread. It never gets a chance to redraw the form. When you add the message box, the message queue is pumped, which allows the drawing.
The simplest way to deal with this would be like this:
private async void Splash_ContentRendered(object sender, EventArgs e)
{
await Task.Delay(3000);
for (double x = 0; x<=1; x+=0.01d)
{
await Task.Delay(15);
img_waves.Opacity = x;
}
this.Close();
}
Do note that this means the form can still be interacted with during the animation. This shouldn't be a problem for a splashscreen, but it could cause you trouble in a "real" form. Still, make sure the form can't be closed during the animation - that could cause exceptions :)
There's also other ways to force the message queue to be pumped, but it's usually frowned upon.
All that said, you're using WPF - why are you doing the animation manually like this? Can't you just handle it as an animation effect in WPF, natively? There's a sample on MSDN.
Whilst I agree with #Luaan explanation as to why as an alternative solution to your loop you can use Storyboard with DoubleAnimation on Opacity property
private void Splash_ContentRendered(object sender, EventArgs e)
{
var sb = new Storyboard();
var da = new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(1.5)));
da.BeginTime = TimeSpan.FromSeconds(3);
Storyboard.SetTargetProperty(da, new PropertyPath("Opacity"));
Storyboard.SetTarget(da, img_waves);
sb.Children.Add(da);
sb.Completed += (s1, e1) => this.Close();
sb.Begin();
}

Unable to reset ProgressBar

I am experimenting a behavior which makes me crazy.
I have a ProgressBar which represents the evolution of an import in database (in percents, from 0 to 100).
After the import is done (ProgressBar.Value = 100.0), I open a log window with a code which looks like this :
RadWindow window = new RadWindow()
{
//Set some properties
};
window.Closed += Log_Closed;
window.ShowDialog();
After the RadWindow is closed, I want to reset the ProgressBar. As you can see I use the function Log_Closed whose code is bellow :
private void Log_Closed(object sender, EventArgs e)
{
//pbImport.Value = pbImport.Minimum; (didn't work)
pbImport.Value = 0;
}
Note : pbImport is my progress bar.
The instruction in Log_Closed has no effect.
Before instruction :
After instruction :
Obviously, the progress bar is not updated in UI. I can't understand this. Thank you for your help.
Animations hold onto properties, in order to reset them in code, you have to remove the animation first so that the property is "released".
See https://msdn.microsoft.com/en-us/library/aa970493%28v=vs.110%29.aspx for information on how to set a property after an animation in WPF.
Resetting the progress Bar can be achieved by using an "if" loop and incrementing the progress bar.
You can set a bool value for the database process and then simply:
private void Log_Closed(object sender, EventArgs e)
{
//pbImport.Value = pbImport.Minimum; (didn't work)
pbImport.Value = 0;
if (database)
{
pbImport.Increment(100);
}
}
From Microsoft's documentation -
To remove a specific AnimationClock from a list of clocks, use the Controller property of the AnimationClock to retrieve a ClockController, then call the Remove method of the ClockController. This is typically done in the Completed event handler for a clock. Note that only root clocks can be controlled by a ClockController; the Controller property of a child clock will return null. Note also that the Completed event will not be called if the effective duration of the clock is forever. In that case, the user will need to determine when to call Remove.
In the example below I demonstrate setting up an event handler that runs when the animation is complete and removes the clock controller there, then set the ProgressBar value back to 0.
void RunAnimation()
{
Duration duration = new Duration(TimeSpan.FromSeconds(10));
DoubleAnimation doubleanimation = new DoubleAnimation(100.0, duration);
doubleanimation.Completed += ProgressBarCompleted;
ProgBar.BeginAnimation(ProgressBar.ValueProperty, doubleanimation);
}
private void ProgressBarCompleted(object sender, EventArgs e)
{
var clock = (AnimationClock)sender;
clock.Controller.Remove();
ProgBar.Value = 0;
}
Note: ProgBar is defined in a .xaml file like
<ProgressBar Margin="0,0,0,0"
Padding="0,0,0,0"
x:Name="ProgBar"
Width="800"
HorizontalAlignment="Right"
Foreground="LightGray"/>

How to stop animation at particular state

I'm creating a fadeIn/fadeOut animation from code behind. I was trying to stop animation at a particular blink time and it is working fine. But I want to make sure that when my animation stops it should stop in fadeOut state. Below is my code:
public void AddAnimation(long blinkDuration = 0)
{
var fadeIn = new DoubleAnimation(0.3, 1, TimeSpan.FromSeconds(1), FillBehavior.HoldEnd)
{
BeginTime = TimeSpan.FromSeconds(0)
};
var fadeOut = new DoubleAnimation(1.0, 0.3, TimeSpan.FromSeconds(1), FillBehavior.HoldEnd)
{
BeginTime = TimeSpan.FromSeconds(0.5)
};
storyboard = new Storyboard();
Storyboard.SetTarget(fadeIn, this);
Storyboard.SetTarget(fadeOut, this);
Storyboard.SetTargetProperty(fadeIn, new PropertyPath("(Opacity)"));
Storyboard.SetTargetProperty(fadeOut, new PropertyPath("(Opacity)"));
storyboard.RepeatBehavior = blinkDuration == 0
? RepeatBehavior.Forever
: new RepeatBehavior(new TimeSpan(0, 0, Convert.ToInt32(blinkDuration)));
storyboard.Children.Add(fadeIn);
storyboard.Children.Add(fadeOut);
storyboard.Begin();
}
My question is how I will make my icon state fadeOut on Storyboard Stop after blink interval.
I have fixed it. I changed my Storyboard.Begin call and registered storyboard.Completed event, on which I'm calling storyboard.Stop().
Below is my changed code:
storyboard.Completed += StoryboardCompleted;
storyboard.Begin(this, true);
void StoryboardCompleted(object sender, EventArgs e)
{
storyboard.Stop(this);
}
You can use a FillBehaviour property value of HoldEnd which will keep the last value (or the To value) that you set in your Storyboard. This way, you can ensure what the end value will be. The only problem that you may have using this property is that you may need to run your animation several times.
To do that, you can attach a handler to the Completed Event and re-run the Storyboard after it finishes if a certain condition is met (number of times run, or seconds passed, or data has arrived, etc.):
private void OnStoryboardCompleted(object sender, EventArgs e)
{
if (someConditionIsTrue) storyboard.Start();
}

Silverlight UI lag - Crosshair on Graph

I've got a telerik radchart that's displaying a beautiful and incredibly-useful-to-end-user line chart.
One of the requirements for this chart was to have a x-value crosshair on the graph (e.g. when a user hovers at any point over the graph, a horizontal line appears, and where this line intersects the actual graph line, a value is displayed in another area of the screen).
In a previous iteration, I used flotJS to do my graphing & crosshair'ing, worked great, and was blazing fast. In converting this to silverlight, I've seen a tremendous amount of lag, and I'd like to know if anyone has any ideas on improving performance.
I currently expose the chart's MouseEnter/MouseLeave events to hide/show the horizontal line. This is done via a System.Windows.Visibility property sitting in my viewmodel (which my crosshair's visibility property is bound to). I then use the MouseMove method to actually calculate the x position.
Here is the code that runs this (first in my chart view's code behind)
private void rad_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
var plotAreaPanel = this.rad.DefaultView.ChartArea.ChildrenOfType<ClipPanel>().FirstOrDefault();
var position = e.GetPosition(plotAreaPanel);
var x = rad.DefaultView.ChartArea.AxisX.ConvertPhysicalUnitsToData(position.X);
(this.DataContext as ViewModels.DateCountsViewModel).XVolume = x;
}
private void rad_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
(this.DataContext as ViewModels.DateCountsViewModel).XVolumeVisibility = System.Windows.Visibility.Visible;
}
private void rad_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
(this.DataContext as ViewModels.DateCountsViewModel).XVolumeVisibility = System.Windows.Visibility.Collapsed;
}
Then in my viewmodel, in the setter for XVolume (which is being Set in the MouseMove method above), this code:
public double XVolume
{
get { return xVolume; }
set
{
this.xVolume = value;
decimal currentX = (decimal)Math.Round(value, 0);
var vol = this.VolumeCollection.Where(x => x.XValue == currentX).FirstOrDefault() as Models.ChartModel;
this.Volume = (vol == null) ? 0 : (int)vol.YValue;
RaisePropertyChanged("XVolume");
}
}
With heavy usage, the line lags up to several inches behind my pointer. I tried throttling my mouse events outlined here, but it's still really laggy.
[Note regarding link above: It uses the CompositionTarget.Rendering event, which is fired just before a frame is rendered. Therefore, to throttle the effect of the mouse event, we set a flag to indicate that we are waiting for a change to be rendered, then reset this flag just before rendering occurs... not allowing simultaneous rendering]
What can I do to mitigate this lag?

Categories