Re-Triggering WPF animations - wpf

I've the following WPF XAML that tries to animate the visibility of 2 rectangles depending on the IsChecked property of a checkbox. So Checked means the blue box appears and the red box disappears after a second, Unchecked means the red box appears and the blue box disappears, and Undetermined means none are visible.
I'm using storyboards but I'm missing something. It work's for the first trigger but some of the animated properties get stuck.
<DataTemplate x:Key="dt">
<DockPanel>
<CheckBox x:Name="ckToggle" Content="Toggle" DockPanel.Dock="Top" IsThreeState="True"/>
<StackPanel Orientation="Horizontal">
<Rectangle x:Name="a" Visibility="Hidden" Width="100" Height="100" Fill = "Red"/>
<Rectangle x:Name="b" Visibility="Hidden" Width="100" Height="100" Fill = "Blue"/>
</StackPanel>
</DockPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=ckToggle, Path=IsChecked}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="a" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="00:00:01" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="b" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:01" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
</DataTrigger.ExitActions>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=ckToggle, Path=IsChecked}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="b" Storyboard.TargetProperty="Visibility" >
<DiscreteObjectKeyFrame KeyTime="00:00:01" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="a" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:01" Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<ContentControl ContentTemplate="{StaticResource dt}"/>
If anyone can help, thanks
Bryan

The problem you have is a property of a wpf storyboard. It´s the case that when you begin a storyboard it´ll never stop until you tell it to to that. So the first storyboard you call is still running when you call the second one and you get in trouble. The way to get out there is the following:
First combine the two storyboards for each trigger in one (You can put multiple animations in one storyboard, you don´t have to do that but it makes things easier..). Now add a name to each of the two remaining BeginStoryboard calls and add StopStoryboard calls with the same names to each dataTrigger in the exitActions block. I ended up with the following code that (hopefully) works as you want:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="dt">
<DockPanel>
<CheckBox x:Name="ckToggle" Content="Toggle" DockPanel.Dock="Top" IsThreeState="True"/>
<StackPanel Orientation="Horizontal">
<Rectangle x:Name="a" Visibility="Hidden" Width="100" Height="100" Fill = "Red"/>
<Rectangle x:Name="b" Visibility="Hidden" Width="100" Height="100" Fill = "Blue"/>
</StackPanel>
</DockPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=ckToggle, Path=IsChecked}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="st1">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="a" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="00:00:01" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="b" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:01" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="st1"/>
</DataTrigger.ExitActions>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=ckToggle, Path=IsChecked}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard Name="st2">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="b" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="00:00:01" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="a" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:01" Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="st2"/>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<ContentControl ContentTemplate="{StaticResource dt}"/></Window>

Related

Animate an image inside of a tooltip of a button

For the application I am working on, I need an animation.
The problem is, that the animation is in a tooltip.
So I have a button and whenever I move over the button the tooltip comes up. The tooltip shows a short information text (Textblock) and after about two seconds he shall expand and show an image.
Since I am an XAML-only developer, which means I have no clue about C# and therefore I have no idea about binding to a viewmodel, I am looking for a way to do this with triggers and storyboards.
I have already achieved that effect with an Event Trigger, who reacts to the Routed Event Loaded. The problem is, that this works "only" once, when the Tooltip is loaded the first time.
Here is my code:
<Button Width="100" Height="100">
<Button.ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Here is some text!"></TextBlock>
<Image Grid.Row="1" Visibility="Collapsed" Source="MyPicture.png">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<EventTrigger RoutedEvent="Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetProperty="Visibility"
Duration="0"
BeginTime="0:0:2">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Grid>
</Button.ToolTip>
</Button>
Is there a better Routed Event than "Loaded" or do I have to make my own Routed Event? Can I link the Event to the Button, like "When Button IsMouseOver is true do this with the image"?
Or is there a complete better way?
Nevertheless, solutions regarding a way with C# are also welcomed.
Thanks for any advices,
Gerrit
A DataTrigger on the ToolTip's Visibility, with Enter and Exit actions should work. Note the explicit <ToolTip>...</ToolTip>
<Button Width="100" Height="100">
<Button.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Here is some text!"></TextBlock>
<Image Grid.Row="1" Visibility="Collapsed" Source="MyPicture.png">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible, RelativeSource={RelativeSource AncestorType=ToolTip}}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetProperty="Visibility"
Duration="0"
BeginTime="0:0:2">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetProperty="Visibility"
Duration="0">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Grid>
</ToolTip>
</Button.ToolTip>
</Button>
Since you do not set the FillBehavior property in your animation, it is set to HoldEnd (i.e. the animation holds its value after it reaches the end of its active period). So you need just an "opposite" animation to reset the Visibility value:
<Image Grid.Row="1" Visibility="Collapsed" Source="MyPicture.png">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<EventTrigger RoutedEvent="Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetProperty="Visibility"
Duration="0"
BeginTime="0:0:2">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Unloaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetProperty="Visibility"
Duration="0"
BeginTime="0:0:0">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
So the second animation will begin when the image is unloaded, has no duration and resets the Visibility value.

Can I set a different property at the end of a Storyboard/Animation in WPF?

I want to make an image flash a set number of times, when I set its Visibility to Visible. And when it's finished flashing, I want it to make itself Collapsed again.
Here's a simple version of what I'm trying to do:
<Image Visibility="Collapsed"
Margin="0,0,3,0"
Width="24"
Source="blah"
Height="24">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<Trigger Property="Visibility"
Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard RepeatBehavior="5x"
x:Name="flashingStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity"
Duration="0:0:1"
FillBehavior="Stop">
<DiscreteDoubleKeyFrame Value="0"
KeyTime="0:0:0" />
<DiscreteDoubleKeyFrame Value="1"
KeyTime="0:0:0.5" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
Can I do this in XAML?
If you know how many times it will be flashed then you can set BeginTime correspondingly the number of times your flashing comes along.
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames RepeatBehavior="5x" Storyboard.TargetProperty="Opacity" Duration="0:0:1">
<DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.5" />
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="0:0:5" Duration="0:0:0" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>

Fade out animation on DockPanel

<DockPanel Name="MyPanel" IsVisibleChanged="MyPanel_IsVisibleChanged">
<DockPanel.Triggers>
<EventTrigger RoutedEvent="IsVisibleChanged"> // error here
</EventTrigger>
</DockPanel.Triggers>
</DockPanel>
Above is my dockpanel xmal code. Because IsVisibleChanged is not a RoutedEvent I can not add in the EventTrigger this code:
<Storyboard x:Key="hideMe">
<DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="0:0:2" To="0.0"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:2" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="showMe">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="0:0:5" To="0.75"/>
</Storyboard>
I try to give a fade out animation to my dockpanel.
Instead of using an event trigger, I would use a normal trigger to check the value of the Visibility property of the DockPanel.
You can create a style on the DockPanel to do it, like this:
<DockPanel Name="MyPanel">
<DockPanel.Style>
<Style TargetType="DockPanel">
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<!-- Set storyboard to run when DockPanel is set visible here: -->
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<!-- Set storyboard to run when DockPanel is no longer set visible here: -->
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</DockPanel.Style>
</DockPanel>

Bold changed cells in DataGrid

I would like to bold changed DataGrid cells when data source gets updated. I found that <EventTrigger RoutedEvent="Binding.TargetUpdated"> is the event I need. However I cannot get the Storyboard to work with the FontWeight property.
Here is what I am trying:
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<Int32Animation Duration="00:00:05"
Storyboard.TargetProperty="(Int32)(DataGridCell.FontWeight)"
From="400" To="700" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Can someone please recommend how to get the above fixed or propose new smarter way of bolding changed cells in DataGrid?
This works for me...
I have a resource...
<Style TargetType="TextBlock" x:Key="ElementStyle">
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames
Duration="00:00:01"
Storyboard.TargetProperty="(TextElement.FontWeight)">
<DiscreteObjectKeyFrame
KeyTime="00:00:00"
Value="{x:Static FontWeights.Thin}" />
<DiscreteObjectKeyFrame
KeyTime="00:00:00.5"
Value="{x:Static FontWeights.Heavy}" />
<DiscreteObjectKeyFrame
KeyTime="00:00:01"
Value="{x:Static FontWeights.UltraBold}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
Assign this to relevant column,
<toolkit:DataGridTextColumn
Binding="{Binding Quantity, NotifyOnTargetUpdated=True}"
ElementStyle="{StaticResource ElementStyle}" />
EDIT
Bcause the code above makes the default values also Bold, we have another way to do this wherein ONLY the updates done by user would trigger boldness in the cell.
Style
<Style TargetType="Controls:DataGridCell"
BasedOn="{StaticResource {x:Type Controls:DataGridCell}}"
x:Key="CellBoldStyle">
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.SourceUpdated">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames
Duration="00:00:01" Storyboard.TargetProperty
="(TextBlock.FontWeight)">
<DiscreteObjectKeyFrame KeyTime="00:00:00.5"
Value="{x:Static FontWeights.Normal}" />
<DiscreteObjectKeyFrame KeyTime="00:00:01"
Value="{x:Static FontWeights.Bold}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
Column
<Controls:DataGridTextColumn
Binding="{Binding Side, Mode=TwoWay,
NotifyOnTargetUpdated=True,
NotifyOnSourceUpdated=True}"
CellStyle="{StaticResource CellBoldStyle}" />
Limitations
Virtualized cells would loose the bold highlighted effect when you scroll them out of your scroll view.
Even your TextBox shows Bold value . (I dont know if this is a limitation for you!)

WPF TextBlock text changed notify

I have a screen contain about 15-20 TextBlocks each one bind to a different property, at first all the TextBlocks are empty the text update come from other client.
The thing I want to do is to animate flashing text for 3 seconds when ever text change.
I used the below storyboard to make that happen:
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseEnter">
<BeginStoryboard >
<Storyboard Duration="0:0:03">
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:00.5" Value="{x:Static Visibility.Hidden}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:01" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:01.5" Value="{x:Static Visibility.Hidden}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:02" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:02.5" Value="{x:Static Visibility.Hidden}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:03" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
Using the mouse enter event the text flash is fine but using the Binding.TargetUpdated event didn't trigger anything.
Anyone know about event that raise when the TextBlock text is changed ?
did you set the NotifyOnTargetUpdated property to true
<TextBlock Text="{Binding Path=YourProperty, NotifyOnTargetUpdated=True}" TargetUpdated="OnTargetUpdated"/>
Already a bit old, but here the solution in pure xaml:
<TextBlock VerticalAlignment="Center"
Text="{Binding ErrorMsg, NotifyOnTargetUpdated=True}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation BeginTime="0:0:0"
Duration="0:0:1"
From="0.0"
Storyboard.TargetProperty="Opacity"
To="1.0" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>

Resources