In WPF animation set property BeginTime to a static resource - wpf

What I want to do is define all the BeginTimes of my Animation using a resource.
For example, I want:
<sys:TimeSpan x:key="SomeResource">... </sys:TimeSpan>
...
<DoubleAnimation BeginTime={StaticResource SomeResource}/>
Obviously sys:TimeSpan is not the correct type to use. How do I define my resource so I can reference it as a resource when defining my animations?
I also want to do this purely in XAML.
Thanks.

System.TimeSpan is the correct type to use since is this is the type of BeginTime. You can also do the same for Duration (but using the System.Windows.Duration type instead).
Here is an example using a StaticResource in an animation (after 2 seconds, fade in for 1 second):
<Button Content="Placeholder"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Opacity="0.5">
<Button.Resources>
<sys:TimeSpan x:Key="FadeInBeginTime">0:0:2</sys:TimeSpan>
<Duration x:Key="FadeInDuration">0:0:1</Duration>
</Button.Resources>
<Button.Style>
<Style>
<Style.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseEnter">
<BeginStoryboard x:Name="FadeInBeginStoryBoard">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
To="1"
BeginTime="{StaticResource FadeInBeginTime}"
Duration="{StaticResource FadeInDuration}" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="UIElement.MouseLeave">
<StopStoryboard BeginStoryboardName="FadeInBeginStoryBoard" />
</EventTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Assuming you have declared the sys namespace as:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Hope this helps!

Related

One Storyboard for multiple triggers

Context: I have two Storyboards in my UserControl. One of them is for sliding the UserControl in and one is for sliding it out.
All I'm doing for the slide in/out is setting the margin to a negative value for sliding out and to zero for sliding in.
Now, I want the sbShowLeftMenu storyboard to be executed when the UserControl's visibility is set to Visible. Also, I want to be able to manually slide the UserControl in/out using the Buttons BtnHide & BtnShow.
Now if the UserControl becomes visible the sbShowLeftMenu is activated and the UserControls gets moved in. Switching the visibility between Collapsed and Visible this behaviour continues, as I want it to.
Now if I hit the BtnHide to move the UserControl out of viewport everything works fine until I start switching the Visibility of the UserControl again. Now the Storyboard doesn't work anymore. I can still move the UserControls in/out with the Buttons but the 'Visible' Trigger does not start the Storyboard.
Here are gif examples of the behavior:
With the click on 'Database Search' I set the Visibility of the UserControl to Visible (because its bound to the 'IsExpanded' property of the ExpanderControl) and it works just fine:
Here I demonstrate what happens after I manually click the 'BtnHide':
This is the code for the UserControl:
<UserControl.Resources>
<Style x:Key="TextBlockStyle">
<Setter Property="TextBlock.FontSize" Value="10"></Setter>
<Setter Property="TextBlock.Margin" Value="1"></Setter>
<Setter Property="TextBlock.VerticalAlignment" Value="Center"></Setter>
</Style>
<Storyboard x:Key="sbShowLeftMenu">
<ObjectAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetName="BtnShow" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0.01" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetName="BtnHide" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0.01" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ThicknessAnimation Storyboard.TargetProperty="Margin" Storyboard.TargetName="pnlLeftMenu" From="-650,0,0,0" To="0,0,0,0" DecelerationRatio=".9" Duration="0:0:1" />
</Storyboard>
<Storyboard x:Key="sbHideLeftMenu">
<ObjectAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetName="BtnHide" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0.01" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetName="BtnShow" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0.01" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ThicknessAnimation Storyboard.TargetProperty="Margin" Storyboard.TargetName="pnlLeftMenu" From="0,0,0,0" To="-650,0,0,0" AccelerationRatio=".9" Duration="0:0:1" />
</Storyboard>
</UserControl.Resources>
<UserControl.Template>
<ControlTemplate>
<Grid Background="Red">
<StackPanel Panel.ZIndex="2" Name="pnlLeftMenu" Orientation="Horizontal" HorizontalAlignment="Left" Margin="-650,0,0,0" Height="500">
<!-- Content -->
<Border>The Content is in here</Border>
<Grid>
<Button x:Name="BtnShow" Height="25" Width="25" VerticalAlignment="Top" HorizontalAlignment="Left" >
<Button.Content>
<Path Stroke="Black"
StrokeThickness="2"
Data="M 0,0 L 0.5,0.5 L 0,1"
Stretch="Uniform"></Path>
</Button.Content>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource sbShowLeftMenu}"></BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
<Button x:Name="BtnHide" Height="25" Width="25" VerticalAlignment="Top" HorizontalAlignment="Left" Visibility="Collapsed" >
<Button.Content>
<Path Stroke="Black"
StrokeThickness="2"
Data="M 1,1 L 0.5,0.5 L 1,0"
Stretch="Uniform"></Path>
</Button.Content>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource sbHideLeftMenu}"></BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
</StackPanel>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsVisible"
Value="True"
my:TriggerTracing.TriggerName="BoldWhenMouseIsOver"
my:TriggerTracing.TraceEnabled="True">
<Trigger.EnterActions>
<BeginStoryboard Name="sbShowLeftMenu" Storyboard="{StaticResource sbShowLeftMenu}"/>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Name="xy" Storyboard="{StaticResource sbHideLeftMenu}"/>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Template>
You seem to have some complex logic which is interfering with the operations of the story boards.
I would recommend that you not try do this in triggers but instead in code behind. Create a state machine operation and then open/close/make visible/make invisible depending on the state.
The following code is what I use for similar logic of moving a panel based on boolean state via storyboards. You can expand it to handle visibility as well with other states.
private bool moveRight = true; // Start out on the left side, then move right.
public void MoveRight()
{
try
{
if (moveRight)
{
(Resources["MoveToOpen"] as Storyboard)?.Begin(this, false);
(Resources["FlipArrowClose"] as Storyboard)?.Begin(this, false);
}
else
{
(Resources["MoveToClose"] as Storyboard)?.Begin(this, false);
(Resources["FlipArrowOpen"] as Storyboard)?.Begin(this, false);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
moveRight = !moveRight;
}

When setting Storyboard.TargetName on custom control in trigger, throws exception

I have custom button control FancyButton (created for reuse obviously) like this below and some code behind (mainly dependency properties):
<Button x:Class="Views.Controls.FancyButton"
...... >
<Button.ContentTemplate>
<DataTemplate>
....
</DataTemplate>
</Button.ContentTemplate>
</Button>
Below, a usage of this control, trigger should start to switch popup property on mouse up but instead of it I'm facing exception that says
'Popup' name cannot be found in the name scope of
'Views.Controls.FancyButton'.
<controls:FancyButton x:Name="ChooseButton" ServerConnection="{Binding CurrentServer}">
<Button.Triggers>
<EventTrigger SourceName="ChooseButton" RoutedEvent="UIElement.PreviewMouseLeftButtonUp">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="Popup" Storyboard.TargetProperty="(IsOpen)">
<DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</controls:FancyButton >
<Popup Placement="Bottom" Name="Popup">
..........
</Popup>
Previous solution - when used Button control directly - was working as expected but I had a XAML duplication.
What does it change and how to make it work?
Solved!
Instead of using Storyboard.TargetName attached property I used Storyboard.Target and proper binding as below:
<Button.Triggers>
<EventTrigger SourceName="ChooseButton" RoutedEvent="UIElement.PreviewMouseLeftButtonUp">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.Target="{Binding ElementName=popup1}" Storyboard.TargetProperty="(IsOpen)">
<DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Cheers

DataBind Duration property of a DoubleAnimation

In short my quetion is: Can I use databinding for the duration property of a storyboard?
My intention was to design a circular progressbar where i have a simple counter. Now based on the counter I wanted to change the duration of the animation. Is there any way I can achieve this by databinding or is there any other way.
I am using an Arc to create the progressbar effect as you can see below. When the DependencyProperty PauseAnimation becomes false, my animation starts. When it is set to true, it resets(I couldnt find a way to Pause the animation so I am resetting it to start from begining). The below code runs perfectly but I am having trouble setting the duration of the storyboard. I want to change that based on a property in my control. Can I achieve this?
<ed:Arc x:Name="AnimatingArc" ArcThickness="2" ArcThicknessUnit="Pixel" StartAngle="0" EndAngle="360" Fill="{DynamicResource ApplicationPrimaryColour}" HorizontalAlignment="Left" Height="25" Stretch="None" Stroke="Transparent" VerticalAlignment="Top" Width="25" Margin="8,47,0,0">
<ed:Arc.Style>
<Style TargetType="{x:Type ed:Arc}">
<Style.Triggers>
<DataTrigger Binding="{Binding PauseAnimation}" Value="false">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="StartAngle" From="0" To="360" Duration="0:0:30" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="StartAngle" To="0" Duration="0:0:10" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</ed:Arc.Style>

Reuse existing Storyboard

This one possible way to do this:
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="myAnimatedBrush"
Storyboard.TargetProperty="Color"
From="Red" To="Blue" Duration="0:0:7" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
But let's say I have:
<Storyboard x:Name="name">
<ColorAnimation
Storyboard.TargetName="myAnimatedBrush"
Storyboard.TargetProperty="Color"
From="Red" To="Blue" Duration="0:0:7" />
</Storyboard>
and want to reuse it a few times.
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
//
// <---> what whould I put here??
//
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
I'm only interested in a XAML, not c#.
Edit:
After I used suggestions from answers I got an error:
Attribute {StaticResource myStoryboard} value is out of range.
Make it a resource and use StaticResource to call it.
[considering all resources are defined in App.xaml]
<Application.Resources>
<Storyboard x:Key="MyStoryboard">
....
</Storyboard>
</Application.Resources>
Then, at the instance of the button
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource MyStoryboard}"
x:Name="MyStoryboard_Begin"/>
</EventTrigger>
<Button.Triggers>
NOTE: the x:Name is not necessary, but is useful if you want to make sure this storyboard is stopped before running another storyboard: in another trigger use StopStoryboard.
You would use a StaticResource stored in a ResourceDictionary.
A ResourceDictionary can either be in a dedicated file, or defined within the concerning container; for instance, you can use a global, dedicated resources file to load use and throughout the application (an example of this is loading Themes in WPF), or locally, providing "private" resources exposed (though likely still accessible with fully qualified paths, or some kind of voodoo, I'm unsure) to the concerning control, say.
So, to define it...
<UserControl.Resources>
<Storyboard x:Key="myStoryboard">
<ColorAnimation
Storyboard.TargetName="myAnimatedBrush"
Storyboard.TargetProperty="Color"
From="Red" To="Blue" Duration="0:0:7" />
</Storyboard>
</UserControl.Resources>
And to utilise it...
<... Storyboard="{StaticResource myStoryboard}" />

How do I bind to a color in a WPF ColorAnimation?

I would like to do something that is seemingly quite simple, but I cannot figure out how to do it. I have a ColorAnimation that is triggered when the MouseEnter event occurs. It simply changes the background color of a Border from one color to another color.
Unfortunately, I can't figure out how to put anything but hardcoded colors into this ColorAnimation. So it looks currently like this:
<Style x:Key="MouseOverStyle">
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="0:0:0.5" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="Red" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
However, I'd like do something either like this:
<SolidColorBrush x:Key="MyEventColor" Color="{Binding EventColor}" />
<Style x:Key="MouseOverStyle">
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="0:0:0.5" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="{StaticResource MyEventColor}" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
Or like this:
<Style x:Key="MouseOverStyle">
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="0:0:0.5" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="{Binding EventColor}" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
When I try to do either of those, an exception gets thrown. For the first, it throws an exception telling me essentially that the "Color" property can't take a SolidColorBrush value...which makes sense...but it certainly doesn't help me out because the ColorAnimation won't let me animate the "(Border.Background).(SolidColorBrush)" property...it will only let me animate the "(Border.Background).(SolidColorBrush.Color)" property....
The exception on the second example basically tells me that it "Cannot freeze this Storyboard timeline tree for use across threads" ...so it sounds like the ColorAnimation is trying to do this binding in some other thread than the UI thread or something? Whatever it's trying to do...it isn't working.
How the heck can I do such a simple task?
For the first one, you could use {StaticResource MyColor} with MyColor defined as such:
<Color x:Key="MyColor">#FF00FF00</Color>
However, this doesn't solve your problem: you can't bind to animation properties since those properties need to be frozen (unchangeable) for the animation to work. Either try to remove your dependence on a binding, or recreate the storyboard with the correct color from code behind when the color changes.

Resources