How to stop an animation WPF? - c#

How to stop an animation so it won't produce Completed event. Here's simple example
<Window x:Class="WpfApplication5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="248" Width="318">
<Grid>
<Border Width="20" Height="20" Background="Red" MouseEnter="Border_MouseEnter" MouseLeave="Border_MouseLeave" x:Name="border" />
</Grid>
</Window>
And backing code:
private void Border_MouseEnter(object sender, MouseEventArgs e)
{
var a = new DoubleAnimation { To = 0, Duration = TimeSpan.FromMilliseconds(4000) };
a.Completed += (obj, args) => MessageBox.Show("Boom!");
border.BeginAnimation(Border.OpacityProperty, a);
}
private void Border_MouseLeave(object sender, MouseEventArgs e)
{
border.BeginAnimation(Border.OpacityProperty, null);
border.Opacity = 1;
}
If I move mouse out before rectangle becomes white it still will display popup window after some time. How to prevent this? Let's assume that Border_MouseLeave and Border_MouseEnter methods doesn't know about each other (they can't pass animation instance variable to each other).

you can use this:
<Border Width="20" Height="20" Background="Red" x:Name="border" >
<Border.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard Name="Ali">
<Storyboard>
<DoubleAnimation To="0" Duration="0:0:4" Completed="com" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<StopStoryboard BeginStoryboardName="Ali"/>
</EventTrigger>
</Border.Triggers>
</Border>
and :
private void com(object sender, EventArgs e)
{
MessageBox.Show("boom!");
}

You could use Property or Data Trigger's EnterActions and ExitActions properties or as #Ali said correctly use Begin and Stop storyboard.

Related

Why do mouse events not work with same events in parent object?

I have two borders that are replacing each other via animation, triggered by MouseEnter and MouseLeave events. And I also have a button on one of the borders with MouseEnter="Test_MouseEnter" MouseDown="Test_MouseDown".
XAML:
<UserControl.Resources>
<Storyboard x:Key="FirstCardStoryboard">
<DoubleAnimation
Duration="0:0:0.2"
Storyboard.TargetName="FirstBorder"
Storyboard.TargetProperty="Opacity"/>
</Storyboard>
<Storyboard x:Key="SecondCardStoryboard">
<DoubleAnimation
Duration="0:0:0.2"
Storyboard.TargetName="SecondBorder"
Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</UserControl.Resources>
<Grid MouseEnter="Grid_MouseEnter" MouseLeave="Grid_MouseLeave">
<Border x:Name="FirstBorder" Width="170" Height="210" CornerRadius="15" BorderThickness="2" Background="#FF323236" BorderBrush="Cyan">
<StackPanel VerticalAlignment="Center">
<Button x:Name="Test" Width="130" Height="30" MouseEnter="Test_MouseEnter" Click="Test_Click">
</Button>
</StackPanel>
</Border>
<Border x:Name="SecondBorder" Width="170" Height="210" CornerRadius="15" BorderThickness="2"
Background="#FF323236" BorderBrush="Cyan" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
</Grid>
</Border>
</Grid>
And the C# side:
public partial class ProductControl : UserControl
{
public ProductControl()
{
InitializeComponent();
}
private void Grid_MouseEnter(object sender, MouseEventArgs e)
{
Storyboard storyboard = (this.Resources["FirstCardStoryboard"] as Storyboard);
DoubleAnimation animation = storyboard.Children.First() as DoubleAnimation;
animation.To = 1;
storyboard.Begin();
Storyboard storyboardSecond = (this.Resources["SecondCardStoryboard"] as Storyboard);
DoubleAnimation animationSecond = storyboardSecond.Children.First() as DoubleAnimation;
animationSecond.To = 0;
storyboardSecond.Begin();
}
private void Grid_MouseLeave(object sender, MouseEventArgs e)
{
Storyboard storyboard = (this.Resources["FirstCardStoryboard"] as Storyboard);
DoubleAnimation animation = storyboard.Children.First() as DoubleAnimation;
animation.To = 0;
storyboard.Begin();
Storyboard storyboardSecond = (this.Resources["SecondCardStoryboard"] as Storyboard);
DoubleAnimation animationSecond = storyboardSecond.Children.First() as DoubleAnimation;
animationSecond.To = 1;
storyboardSecond.Begin();
}
private void Test_MouseEnter(object sender, MouseEventArgs e)
{
}
private void Test_Click(object sender, RoutedEventArgs e)
{
}
}
Then I put breakpoints on Test_MouseEnter and Test_MouseDown methods, but it seems that they do not call, when I move mouse over the button or click on it.
Is this because of the Grid with MouseEnter="Grid_MouseEnter" MouseLeave="Grid_MouseLeave" or is there another reason of this? If the Grid stops events, is the a way to pass on this events?
I tested Click="Test_Click" on the button, but this also doesn't work.
I found the problem. I was changing only opacity of the borders, but they were still there. So the Second Border handled all the events. So I also need to change Visibility of the borders in animation.
Or as #Clemens said I can change just IsHitTestVisible.

C#/WPF Handling active Storyboard Animations when spamming the 'Toggle'

Basically I have this animation where when you click the button(s), it toggles the grid either revealing or hiding the 2nd column (which contains Button2) ... My problem is that when you spam-click the button, the animation gets queued so it needs to finish the first animation before doing the second one. The only workaround I have is to disable the button while the animation is active then re-enabling them once the animation is completed. But I'm trying to find a way where the active animation gets interrupted somehow and instead uses the current width of the grid to start the 2nd animation.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_toggle = false;
GridBodyWidthOne = new GridLength(1, GridUnitType.Star);
GridBodyWidthZero = new GridLength(0, GridUnitType.Star);
GridBody0Width = GridBodyWidthOne;
GridBody1Width = GridBodyWidthZero;
storyboard = this.FindResource("expandBody0") as Storyboard;
storyboard.FillBehavior = FillBehavior.Stop;
storyboard.Completed += (object o, EventArgs ea) => {
GridBody0.Width = GridBodyWidthOne;
GridBody1.Width = GridBodyWidthZero;
GridBody0.BeginAnimation(ColumnDefinition.WidthProperty, null);
GridBody1.BeginAnimation(ColumnDefinition.WidthProperty, null);
GridSplitter0.IsEnabled = false;};
storyboard = this.FindResource("retractBody0") as Storyboard;
storyboard.FillBehavior = FillBehavior.Stop;
storyboard.Completed += (object o, EventArgs ea) => {
GridBody0.Width = GridBodyWidthOne;
GridBody1.Width = GridBodyWidthOne;
GridBody0.BeginAnimation(ColumnDefinition.WidthProperty, null);
GridBody1.BeginAnimation(ColumnDefinition.WidthProperty, null);
GridSplitter0.IsEnabled = true;};
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ToggleMainBody1();
}
private void ToggleMainBody1()
{
double maxWidth = GridBody0.ActualWidth + GridBody1.ActualWidth;
double width0 = GridBody0.ActualWidth / maxWidth;
double width1 = GridBody1.ActualWidth / maxWidth;
GridBody0Width = new GridLength(width0, GridUnitType.Star);
GridBody1Width = new GridLength(width1, GridUnitType.Star);
if (!_toggle)
RevealMainBody1();
else
HideMainBody1();
_toggle = !_toggle;
}
private void HideMainBody1()
{
//storyboard = this.FindResource("retractBody0") as Storyboard;
//storyboard.Stop(this);
storyboard = this.FindResource("expandBody0") as Storyboard;
storyboard.Begin(this);
}
private void RevealMainBody1()
{
//storyboard = this.FindResource("expandBody0") as Storyboard;
//storyboard.Stop(this);
storyboard = this.FindResource("retractBody0") as Storyboard;
storyboard.Begin(this);
}
as for why I have FillBehavior set to 'Stop', I have a problem with the GridSplitter if it's set to 'HoldEnd'. 'HoldEnd' actually works well but not when I adjust it via the GridSplitter. So basically, the problem occurs when you double-click the toggle button (or trigerring the toggle twice without having the first animation finish).
<Window x:Class="ColumnAdjustmentV2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ColumnAdjustmentV2"
xmlns:g="clr-namespace:ColumnAdjustmentV2"
mc:Ignorable="d"
Title="MainWindow" Height="650" Width="800" Loaded="Window_Loaded" SizeChanged="Window_SizeChanged" StateChanged="Window_StateChanged" >
<Window.Resources>
<Storyboard x:Key="expandBody0">
<!--<g:GridLengthAnimation BeginTime="0:0:0" Duration="0:0:0.5" Storyboard.TargetName="GridBody0" Storyboard.TargetProperty="Width" From="{Binding Path=GridBody0Width}" To="{Binding Path=GridBodyWidthOne}" />-->
<g:GridLengthAnimation BeginTime="0:0:0" Duration="0:0:0.5" Storyboard.TargetName="GridBody1" Storyboard.TargetProperty="Width" From="{Binding Path=GridBody1Width}" To="{Binding Path=GridBodyWidthZero}" />
</Storyboard>
<Storyboard x:Key="retractBody0">
<!--<g:GridLengthAnimation BeginTime="0:0:0" Duration="0:0:0.5" Storyboard.TargetName="GridBody0" Storyboard.TargetProperty="Width" From="{Binding Path=GridBody0Width}" To="{Binding Path=GridBodyWidthOne}" />-->
<g:GridLengthAnimation BeginTime="0:0:0" Duration="0:0:0.5" Storyboard.TargetName="GridBody1" Storyboard.TargetProperty="Width" From="{Binding Path=GridBody1Width}" To="{Binding Path=GridBodyWidthOne}" />
</Storyboard>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="500" />
<RowDefinition Height="100" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="GridBody0" Width="{Binding Path=GridBody0Width, Mode=TwoWay}" />
<ColumnDefinition x:Name="GridBodyDivider" Width="Auto" />
<ColumnDefinition x:Name="GridBody1" Width="{Binding Path=GridBody1Width, Mode=TwoWay}" />
</Grid.ColumnDefinitions>
<Button x:Name="Button1" Grid.Column="0" Content="Button1" Click="Button_Click" />
<GridSplitter x:Name="GridSplitter0" Grid.Column="1" Background="#FFCC0099" HorizontalAlignment="Center" Margin="0" Width="4" VerticalAlignment="Stretch" LayoutUpdated="GridSplitter_LayoutUpdated" MouseDoubleClick="GridSplitter_MouseDoubleClick" PreviewKeyDown="GridSplitter_PreviewKeyDown" PreviewMouseUp="GridSplitter_PreviewMouseUp" />
<Button x:Name="Button2" Grid.Column="2" Content="Button2" Click="Button_Click" />
</Grid>
<Grid Grid.Row="1">
<Button x:Name="Button3" Content=""/>
</Grid>
</Grid>
https://www.youtube.com/watch?v=2iE8cZC6EFQ
I have prepared a sample project that I think may be helpful for you: https://github.com/Drreamer/AnimationReverse
In this example I resize a button from min to max width using animation. To start animation or to run it in reverse direction simply click the button.
<Grid x:Name="rootGrid">
<Button x:Name="button" MinWidth="40" Width="40" Content="Expand"
local:MainWindow.AnimationDuration="0:0:2" HorizontalAlignment="Left" Click="button_Click" >
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="button"
Storyboard.TargetProperty="Width"
From="{Binding ActualWidth, ElementName=button}"
Duration="{Binding Path=(local:MainWindow.AnimationDuration), ElementName=button}">
<DoubleAnimation.To>
<MultiBinding Converter="{StaticResource ToWidthConverter}">
<Binding ElementName="button" Path="Tag" />
<Binding ElementName="button" Path="MinWidth" />
<Binding ElementName="rootGrid" Path="ActualWidth" />
</MultiBinding>
</DoubleAnimation.To>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
Here I use binding to dynamically set the To and Duration properties. I store information about current animation direction in the Tag property. The Duration is calculated dynamically from the Button.Click event based on the current element width. The goal here is to make sure that animation is always aplied with the same speed.
private void button_Click(object sender, RoutedEventArgs e) {
double currentPosition = (button.ActualWidth - button.MinWidth) / (rootGrid.ActualWidth - button.MinWidth);
if (button.Tag is bool && (bool)button.Tag)
button.Tag = false;
else {
currentPosition = 1 - currentPosition;
button.Tag = true;
}
SetAnimationDuration(button, new Duration(TimeSpan.FromSeconds(5 * currentPosition)));
}

How to count how many times storyboard animation looped?

I have code like this
<Storyboard x:Key="AdvMarquee" Completed="Storyboard_Completed">
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)" From="-25" To="0" BeginTime="0:00:00" Duration="0:00:01" />
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)" From="0" To="25" BeginTime="0:00:03" Duration="0:00:01" />
</Storyboard>
<Style x:Key="AnimationImageStyle" TargetType="StackPanel">
<Setter Property="Canvas.Top" Value="200" />
<Style.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard Storyboard="{StaticResource AdvMarquee}"/>
</EventTrigger>
</Style.Triggers>
</Style>
And Applied this Animation style with this code
<Canvas x:Name="Advertise" Background="{x:Null}" Margin="10,0,0,0" >
<StackPanel Style="{StaticResource AnimationImageStyle}">
<Button Click="Advertise_Click" Style="{StaticResource AdvertisementBtnStyle}">
<TextBlock Name="AdvText" Text="This is Animated Text" Padding="10, 0, 10, 0"/>
</Button>
</StackPanel>
</Canvas>
I've tried to use Completed Event on Storyboard to calculate how many times Storyboard animation executed.
Before this, I tried to add RepeatBehavior="Forever" on Storyboard but it just loop forever and didn't run completed event.
and now, when I remove RepeatBehavior="Forever", it complete it's progress, count up, but it doesn't run again.
how can I solve this problem?
still have no idea cuz I'm really new to work with xaml wpfform.
My Storyboard_Completed is just like this.
int count = 0;
private void Storyboard_Completed( object sender, EventArgs e )
{
count++;
}
Put event handler for CurrentStateInvalidated for the last animation and you will have the possibility to get current iteration:
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)" From="0" To="25" BeginTime="0:00:03" Duration="0:00:01" CurrentStateInvalidated="DoubleAnimation_CurrentStateInvalidated"/>
int cnt=0;
private void DoubleAnimation_CurrentStateInvalidated(object sender, EventArgs e)
{
var ac = sender as AnimationClock;
cnt = (ac.Parent as ClockGroup).CurrentIteration;
}
Storyboard_Completed you will not need.

Silverlight; Change Grid's background on mouseover

Simply i want to change the Grid's background color (in Silverlight) when the mouse enters and reset it when the mouse leaves.
So I tried different ways but no success. Here is what I have tried:
1: using EventTriggers:
<Grid.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard Storyboard="{StaticResouce mouseEnter}"/>
</EventTrigger>
</Grid.Triggers>
this doesn't work and say:
The member "IsMouseOver" is not recognized or is not accessible
2. using Style.Triggers
I tried setting some simple triggers in an Style with TargetType="Grid" but in Silverlight it seems there is no way to make Style.Triggers in XAML. Here is the code:
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
</Style.Triggers>
</Style>
</Grid.Style>
But it says:
The attachable property 'Triggers' was not found in type 'Style'.
3. using interaction libraries
I also used Interactivity.dll and interaction.dll but they didnt' work too.
Can anyone help how to change the grid background when the mouse enters in Silverlight?
There are three possible solutions:
First solution: Using VisualSates: Changing a Background on MouseOver in Silverlight can be done via VisualStates.
Here is an example:
<UserControl class="MyUserControlWithVisualStates">
<Grid x:Name="RootGrid" Background="UglyRed">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Disabled"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation To="Green"
Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
Storyboard.TargetName="RootGrid"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused"/>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<OtherGridContent ... />
</Grid>
</UserControl>
and code behind:
public partial class MyUserControlWithVisualStates : UserControl
{
private bool m_isMouseOver;
public MyUserControlWithVisualStates()
{
InitializeComponent();
RootGrid.MouseEnter += OnRootGridMouseEnter;
RootGrid.MouseLeave += OnRootGridMouseLeave;
}
private void UpdateVisualStates()
{
if ( m_isMouseOver )
VisualStateManager.GoToState( this, "MouseOver", true );
else
VisualStateManager.GoToState( this, "Normal", true );
}
private void OnRootGridMouseLeave( object sender, MouseEventArgs e )
{
m_isMouseOver = false;
UpdateVisualStates();
}
private void OnRootGridMouseEnter( object sender, MouseEventArgs e )
{
m_isMouseOver = true;
UpdateVisualStates();
}
}
Second solution: Changing properties via codebehind: The MouseEnter and MouseLeave event handlers can just change the grid's background color.
public partial class MyUserControl : UserControl
{
private bool m_isMouseOver;
public MyUserControl()
{
InitializeComponent();
RootGrid.MouseEnter += OnRootGridMouseEnter;
RootGrid.MouseLeave += OnRootGridMouseLeave;
}
private void UpdateBackground()
{
if (m_isMouseOver)
((SolidColorBrush) RootGrid.Background).Color = Colors.Red;
else
((SolidColorBrush) RootGrid.Background).Color = Colors.Green;
}
private void OnRootGridMouseLeave( object sender, MouseEventArgs e )
{
m_isMouseOver = false;
UpdateBackground();
}
private void OnRootGridMouseEnter( object sender, MouseEventArgs e )
{
m_isMouseOver = true;
UpdateBackground();
}
}
Third solution: Using triggers and actions in xaml:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
<Grid x:Name="TheGrid" Background="Blue">
<Grid.Resources>
<SolidColorBrush x:Key="MouseOverBrush" Color="Green"/>
<SolidColorBrush x:Key="NormalBrush" Color="Red"/>
</Grid.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter" SourceName="TheGrid">
<ei:ChangePropertyAction
TargetName="TheGrid"
PropertyName="Background"
Value="{StaticResource MouseOverBrush}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave" SourceName="TheGrid">
<ei:ChangePropertyAction
TargetName="TheGrid"
PropertyName="Background"
Value="{StaticResource NormalBrush}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>

text highlighting animation in windows 8 apps

i want to create an animation where each word of a line changes its foreground color from black to white after some intervals.
initially all the words are set to black.
i have used this code:
DispatcherTimer text1timer = new DispatcherTimer();
text1timer.Interval = TimeSpan.FromMilliseconds(440);
text1timer.Tick += text1timer_Tick;
text1timer.Start();
void text1timer_Tick(object sender, object e)
{
text1timer.Tick -= text1timer_Tick;
txt1.Foreground = new SolidColorBrush(Colors.White);
text1timer.Stop();
text1timer.Tick += text2timer_Tick;
text1timer.Start();
}
private void text2timer_Tick(object sender, object e)
{
text1timer.Tick -= text2timer_Tick;
txt2.Foreground = new SolidColorBrush(Colors.White);
text1timer.Stop();
text1timer.Tick += text3timer_Tick;
text1timer.Start();
}
private void text3timer_Tick(object sender, object e)
{
text1timer.Tick -= text3timer_Tick;
txt3.Foreground = new SolidColorBrush(Colors.White);
text1timer.Stop();
text1timer.Tick += text4timer_Tick;
text1timer.Start();
}
and so on but i have more than 100 words and i will have to make more than 100 events of the timer.is there any other solution?
You can use StoryBoard for the desired functionality.Check the following codes.
<Page.Resources>
<Storyboard x:Name="TextForegroundSb" RepeatBehavior="Forever">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Tag)" Storyboard.TargetName="textBlock">
<DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="Red"/>
<DiscreteObjectKeyFrame KeyTime="0:0:0.2" Value="Green"/>
<DiscreteObjectKeyFrame KeyTime="0:0:0.4" Value="Blue"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Page.Resources>
Here is the Textblock
<TextBlock x:Name="textBlock" TextWrapping="Wrap" Text="TextBlock" FontSize="48" Tag="Red" FontWeight="Bold" Foreground="{Binding Tag, RelativeSource={RelativeSource Mode=Self}}" FontFamily="Global User Interface" />
Also you can modify the time by changing DiscreteObjectKeyFrame KeyTime property.
For playing the storyboard on a button click use this code.
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" <br/>
xmlns:Core="using:Microsoft.Xaml.Interactions.Core" <br/>
xmlns:Media="using:Microsoft.Xaml.Interactions.Media" <br/>
<Button Content="Start sb" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="915,285,0,0" Height="119" Width="276">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="Click">
<Media:ControlStoryboardAction Storyboard="{StaticResource TextForegroundSb}"/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
Hope this helps.
Thanks..
Use storyboard! there are instructions online and on MSDN.

Categories