WPF StoryBoard doesn't begin if Stop() is called after processing - c#

I have a button containing an image, and I want that image rotates while some code is executed, then stop at the end of this processing.
My code almost works if I begin the storyboard, but when I add the line sb.Stop(), the animation never begins.
Here is my code:
private void refreshPostIt(int postItIndex)
{
Button btn = // Button to rotate
Storyboard sb = new Storyboard();
DoubleAnimation rotate = new DoubleAnimation();
rotate.From = 0;
rotate.To = 360;
rotate.RepeatBehavior = RepeatBehavior.Forever;
RotateTransform rt = new RotateTransform();
btn.RenderTransformOrigin = new Point(0.5, 0.5);
btn.RenderTransform = rt;
Storyboard.SetTarget(rotate, btn);
Storyboard.SetTargetName(rotate, btn.Name);
Storyboard.SetTargetProperty(rotate, new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));
sb.Children.Add(rotate);
sb.Begin(this, true);
// Some code which can take several seconds
sb.Stop();
}
I'm stuck.

I have a solution that works for me.
private void refreshPostIt()
{
// Button btn; is defined somewhere else
Storyboard sb = new Storyboard();
DoubleAnimation rotate = new DoubleAnimation();
rotate.From = 0;
rotate.To = 360;
rotate.RepeatBehavior = RepeatBehavior.Forever;
RotateTransform rt = new RotateTransform();
btn.RenderTransformOrigin = new Point(0.5, 0.5);
btn.RenderTransform = rt;
Storyboard.SetTarget(rotate, btn);
Storyboard.SetTargetName(rotate, btn.Name);
Storyboard.SetTargetProperty(rotate, new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));
sb.Children.Add(rotate);
sb.Begin(btn, true);
// Do your stuff here (Not in the UI Thread)
// Maybe use a semaphore to lock the sb.Stop(btn)
sb.Stop(btn);
}
whole example code:
public partial class Window1 : Window
{
Button btn;
Storyboard sb;
public Window1()
{
btn = new Button();
InitializeComponent();
this.grid.Children.Add(btn);
refreshPostIt();
}
private void refreshPostIt()
{
// Button btn; is defined somewhere else
sb = new Storyboard();
DoubleAnimation rotate = new DoubleAnimation();
rotate.From = 0;
rotate.To = 360;
rotate.RepeatBehavior = RepeatBehavior.Forever;
RotateTransform rt = new RotateTransform();
btn.RenderTransformOrigin = new Point(0.5, 0.5);
btn.RenderTransform = rt;
Storyboard.SetTarget(rotate, btn);
Storyboard.SetTargetName(rotate, btn.Name);
Storyboard.SetTargetProperty(rotate, new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));
sb.Children.Add(rotate);
sb.Begin(btn, true);
BackgroundWorker asd = new BackgroundWorker();
asd.DoWork += new DoWorkEventHandler(asd_DoWork);
asd.RunWorkerCompleted += new RunWorkerCompletedEventHandler(asd_RunWorkerCompleted);
asd.RunWorkerAsync();
}
void asd_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (sb != null)
{
sb.Stop(btn);
}
}
void asd_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
}
}
Ok, I want to explain what I think is your problem here. When you use the UI-Thread to do your "work" it can't update your UI and though won't animate the rotating, when its done with your "work", between starting and stopping the animation, and finally would have time to update the UI, you call to stop animating. That leads to not showing any animation. You need to put your work in a different thread to prevent this from happening.
Hope that helps.

Related

WPF MediaElement Opacity is not set after executing Storyline

I'm using DoubleAnimation and Storyboard to control Opacity of MediaElement. The animation itself works fine, but if I call Disappear and playVid after several seconds, the Opacity of the player remains 0! What's the problem?
public void playVid(string source, bool isMainVid)
{
player.Opacity = 1;
player.Play(); //player.Opacity is 0 here!
}
public void Disappear()
{
DoubleAnimation fadeOut = new DoubleAnimation
{
To = 0,
Duration = new Duration(TimeSpan.FromMilliseconds(1000))
};
fadeOut.Completed += (s, e) =>
{
player.Stop();
};
var storyboard = new Storyboard();
storyboard.Children.Add(fadeOut);
Storyboard.SetTargetName(fadeOut, player.Name);
Storyboard.SetTargetProperty(fadeOut, new PropertyPath(OpacityProperty));
storyboard.Begin(mainGrid, HandoffBehavior.SnapshotAndReplace); //mainGrid is player's parent
}
Use FillBehavior equal to Stop, but also set the Opacity of your player to the final opacity value (in the Completed handler). Otherwise, it will be reset to its value before the animation.
var fadeOut = new DoubleAnimation
{
To = 0,
Duration = new Duration(TimeSpan.FromMilliseconds(1000)),
FillBehavior = FillBehavior.Stop
};
fadeOut.Completed += (s, e) =>
{
player.Stop();
player.Opacity = 0;
};
See this post for other approaches.

Resizing animation on SizeChanged event doesn't work as it should

I want a resizing animation when stackpanel visibility is set to visible state, but instead of that, I'm getting neverending flickering of border containing stackpanel.I don't think I'm doing it wrong.
Stack panel contains an instance of TextBlock.
private void MyBorder_SizeChanged_1(object sender, SizeChangedEventArgs e)
{
if (!first)
{
DoubleAnimation anim = new DoubleAnimation();
anim.From = e.PreviousSize.Height;
anim.To = e.NewSize.Height;
anim.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTarget(anim, MyBorder);
Storyboard.SetTargetProperty(anim, new PropertyPath(Border.HeightProperty));
Storyboard st = new Storyboard();
st.Children.Add(anim);
st.Begin();
}
first = false;
}
private void MyBorder_Tap_1(object sender, GestureEventArgs e)
{
if (MyPanel.Visibility == Visibility.Collapsed)
MyPanel.Visibility = Visibility.Visible;
else
MyPanel.Visibility = Visibility.Collapsed;
}
The problem is that when you animate the height of the border it will trigger the SizeChanged event so you have a loop: size changed event>animate height>size changed event> ..
Also when the sized changed event is fired, the size change has already taken placed so even if that was working you will get a little flicking when it go back to do the animation.
Finally using HEight in an animation force a rendering update so that is not hardware accelerated.
Probably the best would be to either do a Translate Transform or a Scale Transform.
For exemple you could do a scale transform between 0 and 1 directly on MyPanel in the tap event.
This might help you to. It is a work around
private void MyBorder_SizeChanged_1(object sender, SizeChangedEventArgs e)
{
if (!first)
{
DoubleAnimation anim = new DoubleAnimation();
anim.From = e.PreviousSize.Height;
anim.To = e.NewSize.Height;
anim.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTarget(anim, MyBorder);
Storyboard.SetTargetProperty(anim, new PropertyPath(Border.HeightProperty));
Storyboard st = new Storyboard();
st.Children.Add(anim);
st.Completed += st_Completed;
MyBorder.SizeChanged -= MyBorder_SizeChanged_1;
st.Begin();
}
first = false;
}
void st_Completed(object sender, EventArgs e)
{
MyBorder.SizeChanged += MyBorder_SizeChanged_1;
}
private void MyBorder_Tap_1(object sender, GestureEventArgs e)
{
if (MyPanel.Visibility == Visibility.Collapsed)
MyPanel.Visibility = Visibility.Visible;
else
MyPanel.Visibility = Visibility.Collapsed;
}
With reference to the fact that border is getting resized even in the storyboard its better to remove the event of size changed. Try animating the parent container on chld size changed some thing kind of this
I've solved this problem. Silly my, I thought that Measure method of StackPanel is private, and didn't bother to make sure about it, here's solution code for expanding StackPanel on Click.
private void MyBorder_Tap_1(object sender, GestureEventArgs e)
{
if (MyPanel.Visibility == Visibility.Collapsed)
{
MyPanel.Visibility = Visibility.Visible;
MyPanel.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
DoubleAnimation anim = new DoubleAnimation();
anim.From = MyBorder.ActualHeight;
anim.To = MyBorder.ActualHeight + MyPanel.DesiredSize.Height;
anim.Duration = new Duration(TimeSpan.FromSeconds(0.25));
Storyboard.SetTarget(anim, MyBorder);
Storyboard.SetTargetProperty(anim, new PropertyPath(Border.HeightProperty));
Storyboard st = new Storyboard();
st.Children.Add(anim);
st.Begin();
}
else
{
MyPanel.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
DoubleAnimation anim = new DoubleAnimation();
anim.From = MyBorder.ActualHeight;
anim.To = MyBorder.ActualHeight - MyPanel.DesiredSize.Height;
anim.Duration = new Duration(TimeSpan.FromSeconds(0.25));
Storyboard.SetTarget(anim, MyBorder);
Storyboard.SetTargetProperty(anim, new PropertyPath(Border.HeightProperty));
Storyboard st = new Storyboard();
st.Children.Add(anim);
st.Completed += (a,b) => { MyPanel.Visibility = Visibility.Collapsed; };
st.Begin();
}
}

Cannot set Opacity property on ScatterViewItem after an animation has been performed

I am currently removing an element from the user interface by fading it out. This works as expected.
public void HideShape()
{
if (this.TangibleShape != null)
{
DoubleAnimation animation = new DoubleAnimation();
animation.From = 1.0;
animation.To = 0.0;
animation.AutoReverse = false;
animation.Duration = TimeSpan.FromSeconds(1.5);
Storyboard s = new Storyboard();
s.Children.Add(animation);
Storyboard.SetTarget(animation, this.TangibleShape.Shape);
Storyboard.SetTargetProperty(animation, new PropertyPath(ScatterViewItem.OpacityProperty));
s.Begin(this.TangibleShape.Shape);
s.Completed += delegate(object sender, EventArgs e)
{
// call UIElementManager to finally hide the element
UIElementManager.GetInstance().Hide(this.TangibleShape);
};
}
}
The problem is that I want to set the opacity to 1 again in some cases but the TangibleShape.Shape (it's a ScatterViewItem) ignores the command. If I fade out again, the element becomes visible and immediately starts fading out. I don't know how to fix this problem. Does anyone have a hint for me?
public void HideShape()
{
if (this.TangibleShape != null)
{
DoubleAnimation animation = new DoubleAnimation();
animation.From = 1.0;
animation.To = 0.0;
animation.AutoReverse = false;
animation.Duration = TimeSpan.FromSeconds(1.5);
animation.FillBehavior = FillBehavior.Stop; // needed
Storyboard s = new Storyboard();
s.Children.Add(animation);
Storyboard.SetTarget(animation, this.TangibleShape.Shape);
Storyboard.SetTargetProperty(animation, new PropertyPath(ScatterViewItem.OpacityProperty));
s.Completed += delegate(object sender, EventArgs e)
{
// call UIElementManager to finally hide the element
UIElementManager.GetInstance().Hide(this.TangibleShape);
this.TangibleShape.Shape.Opacity = 0.0; // otherwise Opacity will be reset to 1
};
s.Begin(this.TangibleShape.Shape); // moved to the end
}
}
Answer found here: http://social.msdn.microsoft.com/Forums/en-US/7d33ca82-2c02-4004-8b37-47edf4cca34e/scatterviewitem-and-

Storyboard animation

I made a small WPF application to slide UserControls in and out, the problem I have is when a UserControl animated to slide in, then I click the button to slide another UserControl in, they both slide out - I'm not sure why this is?
public UserControl1()
{
InitializeComponent();
}
public void SlideIn(UserControl uc)
{
ThicknessAnimation tAnimation = new ThicknessAnimation();
tAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.5));
tAnimation.From = new Thickness(500, 0, -500, 0);
tAnimation.To = new Thickness(0);
tAnimation.DecelerationRatio = 0.9;
Storyboard.SetTargetName(tAnimation, uc.Name);
Storyboard.SetTargetProperty(tAnimation, new PropertyPath(MarginProperty));
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(tAnimation);
storyboard.Begin(uc);
this.Content = uc;
}
public void SlideOut(UserControl uc)
{
ThicknessAnimation tAnimation = new ThicknessAnimation();
tAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.5));
tAnimation.To = new Thickness(-500, 0, 500, 0);
tAnimation.DecelerationRatio = 0.9;
Storyboard.SetTargetName(tAnimation, uc.Name);
Storyboard.SetTargetProperty(tAnimation, new PropertyPath(MarginProperty));
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(tAnimation);
storyboard.Begin(this);
}
private void button1_Click(object sender, RoutedEventArgs e)
{
SlideOut(userControl1);
UserControl2 uc2 = new UserControl2();
SlideIn(uc2);
}
Do you set the Name property of the UserControl somewhere? Or to be more precise, do you give the second UserControl a different Name than the first one? Your animations get their target set by
Storyboard.SetTargetName(tAnimation, uc.Name);
so names should be different. Alternatively you may call
Storyboard.SetTarget(tAnimation, uc);
to specify the target.

WPF Can the animation reverse be invoked explicitly?

Animations in WPF provide the boolean parameter AutoReverse. Is it possible to make a call to the functionality implemented by AutoReverse = "true"?
My goal is to spare some troublesome reverse animation and especially spare alot of code. The reverse must not happen directly after the animation ended.
Example of using AutoReverse and True to animate only the reverse animation, but this does not work as required - it still animates the actual animation and the reverse animation.
TranslateTransform transform = new TranslateTransform(0.0, 0.0);
myBox.RenderTransform = transform;
sb = new Storyboard();
Duration dur = new Duration(TimeSpan.FromSeconds(0.5));
DoubleAnimation shiftAnimation = new DoubleAnimation(100.0, dur);
shiftAnimation.AutoReverse = true;
sb.Children.Add(shiftAnimation);
Storyboard.SetTarget(shiftAnimation, myBox);
Storyboard.SetTargetProperty(shiftAnimation, new PropertyPath("RenderTransform.X"));
sb.Seek(TimeSpan.FromSeconds(0.5));
sb.Begin();
you could set the timeclock to 100% of the animationduration so when the animation starts, it will skip the non-reversed part!
Edit:
if Duration is set to 0:0:1.5 you apply a new timeline to your animation set to 0:0:1.5. Autoreverse has to be set to on and then you yust have to start the animation.
Edit 2:
TranslateTransform transform = new TranslateTransform(0.0, 0.0);
myBox.RenderTransform = transform;
sb = new Storyboard();
Duration dur = new Duration(TimeSpan.FromSeconds(0.5));
DoubleAnimation shiftAnimation = new DoubleAnimation(100.0, dur);
shiftAnimation.AutoReverse = true;
sb.Children.Add(shiftAnimation);
Storyboard.SetTarget(shiftAnimation, myBox);
Storyboard.SetTargetProperty(shiftAnimation, new PropertyPath("RenderTransform.X"));
sb.Begin();
sb.Pause();
sb.Seek(sb.Duration.TimeSpan);
sb.Resume();
I have found that the following code works nicely:
private bool reverse=false;
TimeSpan animationDuration = TimeSpan.FromMilliseconds(500);
Storyboard storyboard1 = new Storyboard();
private void prepareStoryBoard(Button btn)
{
btn.RenderTransform = new CompositeTransform();
DoubleAnimation animationShrink = new DoubleAnimation() { To = 0,
Duration =animationDuration , FillBehavior = FillBehavior.HoldEnd };
storyboard1.Children.Add(animationShrink);
Storyboard.SetTarget(animationShrink, btn);
Storyboard.SetTargetProperty(animationShrink,
"(Button.RenderTransform).(CompositeTransform.ScaleX)");
}
private void toggleAnimate()
{
if(reverse==false)
{
storyboard1.AutoReverse = false;
storyboard1.Begin();
reverse=true;
}
else
{
storyboard1.AutoReverse = true;
storyboard1.Seek(animationDuration);
storyboard1.Resume();
reverse = false;
}
}
The first time toggleAnimate() is called, the animation is executed in the normal direction, the second time toggleAnimate() is invoked the animation is reversed.

Categories