Problem with conditional trigger (or animation) - c#

I have this code:
<UserControl.Resources>
<Storyboard x:Key="OnMouseEnterToDocumentsMenu">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="Documents">
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0.8"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="OnMouseLeaveFromDocumentsMenu">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="Documents">
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0.3"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="OnMouseEnterToPeopleMenu">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="People">
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0.8"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="OnMouseLeaveFromPeopleMenu">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="People">
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0.3"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Storyboard>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter" SourceName="Documents">
<BeginStoryboard Storyboard="{StaticResource OnMouseEnterToDocumentsMenu}"/>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave" SourceName="Documents">
<BeginStoryboard Storyboard="{StaticResource OnMouseLeaveFromDocumentsMenu}"/>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseEnter" SourceName="People">
<BeginStoryboard Storyboard="{StaticResource OnMouseEnterToPeopleMenu}"/>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave" SourceName="People">
<BeginStoryboard Storyboard="{StaticResource OnMouseLeaveFromPeopleMenu}"/>
</EventTrigger>
</EventTrigger>
</UserControl.Triggers>
Purpose
I'm animation a Menu. Menu elements are TextBlocks, I'm simulating a "Hover fading".
Problem
The code I posted animates the opacity once the mouse enter or leave. I have another method (code behind) that sets to "1" the opacity of the menu element once the user clicks it on the menu (and load the appropriate user control on its respective container). I guess this method is working, BUT once the MouseLeave animation start it fades the opacity again no matter if that element was selected or not (If its opacity was 1 not 0.8).
Needs
Is there a way to specify that the MouseLeave trigger fires only when the Source Opacity is not 1. Or to execute the storyboard only if the TargetProperty is not 1.
Thanks.
PS
Is there a way to prevent code duplication given that the animation storyboards are the same (for focus and for fade) and that more menus will come. I duplicate it because I need to put the TargetName and is different but I suppose there is another "cleaner" way.

This behavior is due to dependency property value precedence. I haven't found a xamlish way to do this. But with code-behind here is how:
Subscribe to the storyboard's completed event.
Look for the menuItems that has been selected and call BeginAnimation on it.
You can find more infomation on this here.
private void Storyboard_Completed(object sender, EventArgs e)
{
foreach (var menuItem in this.file.Items)
{
var item = (MenuItem)menuItem;
if (item.IsChecked)
{
item.BeginAnimation(UIElement.OpacityProperty, null);
item.Opacity = 1;
}
}
}
I have looked for IsChecked to say if a menuItem is selected. Use your own methodology if this doesnt suit you.
For question 2 add this to resources and you can get rid of all individual triggers and duplicate storyboards:
<Window.Resources>
<Storyboard x:Key="HoverOn">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.8"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="HoverOff" Completed="Storyboard_Completed">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="0.3"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Style TargetType="{x:Type MenuItem}">
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter" >
<BeginStoryboard Storyboard="{StaticResource HoverOn}"/>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard Storyboard="{StaticResource HoverOff}"/>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>

Related

How to restart WPF animation/storyboard?

I have an hotkey that flashes a prompt text over the UI and then disappears. But the issue is, my storyboard flashes only once in its lifetime which also means it fires the Completed event once.
When I press the hotkey, the grid that holds the TextBlock becomes visible, but the animation no longer plays and the completed no longer fires.
I need those to work so that each time the grid becomes visible, the animation restarts from the start and then fires the completed event which will set the visibility of the grid to false.
Here is the code:
<Grid Visibility="{Binding InfoTextVisible, ElementName=MyMainWindow}">
<Grid.Triggers>
<EventTrigger RoutedEvent="Grid.Loaded">
<BeginStoryboard>
<Storyboard Completed="InfoTextAnimation_Completed" FillBehavior="Stop">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Duration="0:0:1" Completed="DoubleAnimationUsingKeyFrames_Completed">
<SplineDoubleKeyFrame KeyTime="0:0:00.00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="0:0:01.00" Value="1"/>
<SplineDoubleKeyFrame KeyTime="0:0:01.50" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<TextBlock Text="{Binding InfoText, ElementName=MyMainWindow}" Name="timoti" Foreground="White" FontSize="50" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
New code:
<Grid Visibility="{Binding InfoTextVisible, ElementName=MyMainWindow}">
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard x:Name="SpinStoryboard">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Duration="0:0:1">
<SplineDoubleKeyFrame KeyTime="0:0:00.00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="0:0:01.00" Value="1"/>
<SplineDoubleKeyFrame KeyTime="0:0:01.50" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:02.00" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="SpinStoryboard"></StopStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding InfoText, ElementName=MyMainWindow}" Name="timoti" Foreground="White" FontSize="50" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>

How to make Hamburger Menu Transition Animation in XAML only

I want to create the Hamburger Menu Transition Animation in XAML only.
I only found tutorials and examples of how to do this with code-behind.
But this is something I want to avoid at all costs.
This is how the animation looks like:
Animation (no direct GIF possible at the moment)
One example I found
XAML:
<Window.Resources>
<Storyboard x:Key="OpenMenu">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="rectangle">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="43.5"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="rectangle1">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="-43.5"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="rectangle2">
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Width)" Storyboard.TargetName="GridMenu">
<EasingDoubleKeyFrame KeyTime="0" Value="70"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="200"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="CloseMenu">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="rectangle">
<EasingDoubleKeyFrame KeyTime="0" Value="43.5"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="rectangle1">
<EasingDoubleKeyFrame KeyTime="0" Value="-43.5"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="rectangle2">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Width)" Storyboard.TargetName="GridMenu">
<EasingDoubleKeyFrame KeyTime="0" Value="200"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="70"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
code-behind:
private void ButtonMenu_Click(object sender, RoutedEventArgs e)
{
if(StateClosed)
{
Storyboard sb = this.FindResource("OpenMenu") as Storyboard;
sb.Begin();
}
else
{
Storyboard sb = this.FindResource("CloseMenu") as Storyboard;
sb.Begin();
}
StateClosed = !StateClosed;
}
Is there anyway to achive the same result using XAML-only.
I dont want any code-behind for this animation...
I know how to create each Storyboard for the animation itself.
But the same button has to execute two different Storyboards,
depending on the state of the menu?
Here is an example using a ToggleButton instead. Since the ToggleButton has built in Checked / Unchecked events, you can use those in EventTriggers to run your storyboards.
<ToggleButton ... >
<!-- Your menu icon XAML/rectangles go here -->
<ToggleButton.Triggers>
<EventTrigger RoutedEvent="ToggleButton.Checked">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource OpenMenu}" />
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="ToggleButton.Unchecked">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource CloseMenu}" />
</EventTrigger.Actions>
</EventTrigger>
</ToggleButton.Triggers>
</ToggleButton>

How to have a storyboard use a border in a calendar control template

I have edited the CalendarItem Template of the Calendar control. I have added my own border to this template.
I am implementing the Calendar with my CalendarItemStyle.
I normally have a StoryBoard like this
<Storyboard x:Key="sbBorderHighlighted">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="border">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
and i call it like this
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard x:Name="sbBorderHighlighted_BeginStoryboard" Storyboard="{StaticResource sbBorderHighlighted}"/>
</EventTrigger>
So if this event trigger is used on the Calendar Control how do I have to change the StoryBoard so it targets the border in my template?

Can we create single Storyboard for Multiple buttons in WPF XAML?

<Window.Resources>
<Storyboard x:Key="ButtonEffect_01" AutoReverse="True" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.Color)"
Storyboard.TargetName="btnAdd">
<EasingColorKeyFrame KeyTime="0" Value="Black"/>
<EasingColorKeyFrame KeyTime="0:0:0.4" Value="#FFF3FF00"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Button x:Name="btnAdd" Content="Add" Width="69" Height="27">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ei:ControlStoryboardAction Storyboard="{StaticResource ButtonEffect_01}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
I have 3 More Buttons btnDelete, btnUpdate, btnBack can i use same Storyboard for all these buttons? Any binding method?
You can share animation to multiple objects,but a check of animation status is needed.
For prevent errors you can stop animation before assign new target:
private void MouseEnter(object sender, PointerRoutedEventArgs e)
{
ButtonEffect_01.Stop();
ButtonEffect_01.SetValue(Storyboard.TargetNameProperty, (sender as Button).Name);
ButtonEffect_01.Begin();
}
Source: http://msdn.microsoft.com/en-us/library/windows/apps/dn376886.aspx
To avoid specifying the target you can define your storyboard inside the Style of the Buttons:
<Window.Resources>
<Style TargetType="Button">
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<Storyboard AutoReverse="True" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.Color)">
<EasingColorKeyFrame KeyTime="0" Value="Black"/>
<EasingColorKeyFrame KeyTime="0:0:0.4" Value="#FFF3FF00"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>

my storyboard skips some keyframes

I would like to swing an ellipse to one place and then shrink it. After the first storyboard stops, I intend to stretch the ellipse with the ellipse. However the shrinking process doesn't work at all. Hard to firgure out why~ Any help or tips will be appreciated. Thanks~
<Window.Resources>
<Storyboard x:Key="Swing" Duration="0:0:7">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="Leg" AutoReverse="True" RepeatBehavior="Forever">
<EasingDoubleKeyFrame KeyTime="0" Value="27.081"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="-25.783"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)" Storyboard.TargetName="Leg">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:6" Value="489.676"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="Leg">
<EasingDoubleKeyFrame KeyTime="0:0:6" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:7" Value="0.2"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="Swing2" BeginTime="0:0:7">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="Leg">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="0.2"/>
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard x:Name="Swing_BeginStoryboard" Storyboard="{StaticResource Swing}"/>
<BeginStoryboard x:Name="Swing_BeginStoryboard1" Storyboard="{StaticResource Swing2}"/>
</EventTrigger>
</Window.Triggers>
<Grid x:Name="LayoutRoot">
<Ellipse x:Name="Leg" Fill="#FFF4F4F5" HorizontalAlignment="Left" Margin="156,204,0,147" Stroke="Black" Width="33" RenderTransformOrigin="0.464,-0.014">
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="27.081"/>
<TranslateTransform/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
</Grid>
If I change the second storyboard as below, the ellipse will be shinking from the beginning.
<Storyboard x:Key="Swing2">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="Leg">
<EasingDoubleKeyFrame KeyTime="0:0:7" Value="0.2"/>
<EasingDoubleKeyFrame KeyTime="0:0:9" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>

Categories