I am trying to delay an animation of a custom control based on a binding value. In the example below, I’d like the animation to start 5 seconds after the “SelectedAndHit” visual state is selected. However, it doesn’t seem possible to use template binding within the VisualStateManage.
Is TemplateBinding supported within the VisualStateManager? Is there any workaround?
<local:ButtonEx x:Name="Button01" AnimationBeginTime="00:00:05" />
public TimeSpan AnimationBeginTime
{
get { return (TimeSpan)base.GetValue(ButtonEx.AnimationBeginTimeProperty); }
set { base.SetValue(ButtonEx.AnimationBeginTimeProperty, value); }
}
public static readonly DependencyProperty AnimationBeginTimeProperty =
DependencyProperty.Register("AnimationBeginTime", typeof(TimeSpan), typeof(ButtonEx), new PropertyMetadata(TimeSpan.Zero));
<Style TargetType="local:ButtonEx">
<!-- ... -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ButtonEx">
<Grid x:Name="Container" RenderTransformOrigin="0.5, 0.5">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="SelectedAndHit">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundColorSelectedAndHit}" />
</ObjectAnimationUsingKeyFrames>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="GridScaleTransform"
Storyboard.TargetProperty="(ScaleTransform.ScaleX)"
To="1.2" BeginTime="{TemplateBinding AnimationBeginTime}" Duration="00:00:00.300" AutoReverse="True">
<DoubleAnimation.EasingFunction>
<ExponentialEase EasingMode="EaseIn" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation
Storyboard.TargetName="GridScaleTransform"
Storyboard.TargetProperty="(ScaleTransform.ScaleY)"
To="1.2" BeginTime="{TemplateBinding AnimationBeginTime}" Duration="00:00:00.300" AutoReverse="True">
<DoubleAnimation.EasingFunction>
<ExponentialEase EasingMode="EaseIn" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RenderTransform>
<ScaleTransform x:Name="GridScaleTransform" />
</Grid.RenderTransform>
<!-- ... -->
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I confirm that WinRT doesn't support Binding/TemplateBinding within a style. To workaround the issue I wrote code to manually update the BeginTime of the Storyboard. That way I had full control on when the Storyboard begins.
I would look at Interactivity. I've personally used the EventTrigger with GoToStateAction, which was enough for my purposes. From looking at MSDN it looks like you might be able to make use of TimerTrigger and GoToStateAction to create the effect you're looking for. TimerTrigger has dependency properties for setting the delay of the action to fire.
Related
Not sure if am framing the question right. So please bear with me if I'm expecting something absurd. I am building a C#/XAML win10 UWP application following the MVVM pattern.
I've some visual states defined for handling wider screens and also some for running some animations. The problem am facing is that, when the visual state to run the animation is called using the VisualStateManager's GoToState method, the setters effected by the VisualState containing the adaptive triggers are lost.
Here's the sample code:
//Defining my grid here
<Grid x:Name="gridNewDrawing" Margin="4">
<Button x:Name="Confirm" Click="Button_Confirm_Click" Width="180" MaxWidth="220" Height="36" HorizontalAlignment="Left" Style="StaticResource StyleButtonGeneral}"/>
<Button x:Name="Cancel" Click="Button_Cancel_Click" Width="180" MaxWidth="220" Height="36" HorizontalAlignment="Left" Style="StaticResource StyleButtonGeneral}"/>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="WideLayoutTrigger">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="640" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="gridNewDrawing.Margin" Value="16" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="AnimationState">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Cancel" Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
When the width is above 640px; the margin on the gridNewDrawing switches to 16; but when I explicitly call the animation using
GoToState("AnimationState")
The margin on the grid defaults to 4 once again. Is there any way I can have the changes made by the adaptivetrigger persist when setting other visual styles?
The margin of the grid changes to default again because your VisualState are in same VisualStateGroup. You can set the AnimationState in another VisualStateGroup to maintain changes made by the AdaptiveTrigger.
See the Remarks of VisualStateGroup class.
The set of visual states within each VisualStateGroup should be mutually exclusive in the group. In other words, the control should be using exactly one of the visual states from each of its defined VisualStateGroup groups at all times. Whenever there's a case where a control is intended to be simultaneously in two states, make sure that the two states are in different groups.
So apply the following code should get what you want:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="WindowStates">
<VisualState x:Name="WideLayoutTrigger">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="640" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="gridNewDrawing.Margin" Value="16" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="OtherStates">
<VisualState x:Name="AnimationState">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="Cancel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
I'm trying to animate a button so the image enlarges when the user touches it and goes back to normal when he releases It but somehow the XAML Defined StoryBoard throws a nullpointer when called in C#. My code snippets are down here, I've got no clue at all what creates this problem.
XAML:
<UserControl x:Class="Mavie_Base.GUIElements.MenuButton"
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"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="200" d:DesignWidth="140">
<UserControl.Resources>
<Storyboard x:Name="Enlarge">
<DoubleAnimation Duration="0:0:1" From="60" To="66" Storyboard.TargetProperty="Height" Storyboard.TargetName="Rectangle"/>
</Storyboard>
<Storyboard x:Name="Normalize">
<DoubleAnimation Duration="0:0:1" From="66" To="60" Storyboard.TargetProperty="Height" Storyboard.TargetName="Rectangle"/>
</Storyboard>
</UserControl.Resources>
....
</UserControl>
C#:
private void IconContainer_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Enlarge.Begin();
}
private void IconContainer_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
Normalize.Begin();
}
+1 #thumbmunkeys
Though if it were me, it sounds like something you might want to add other stuff to later for interaction so I'd just let functionality already built in to handle that and then have room to add to it later if you want to add other stuff to its functionality. I'd also think (not positive, would have to check) if you go tinkering with the height property it may offset other artifacts layed out on the screen as it adjusts causing for a jumpy UI that wouldn't be very pleasing to the user.
Again, if it were me, I would probably just do something like this instead. First, instead of fiddling with the Height/Width properties that might push other parts of the screen around, hit the ScaleTransform.ScaleY (and ScaleX respectively, you'll want to play with the Values I'm sure to get the sizes you want.) for your size changes.
I'd also probably go ahead and make it into a "lookless" and stripped down Button for other options later and to handle my events like Pressed. Something like;
<Style x:Key="GrowOnPressedThingy" TargetType="Button">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="Container"
RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform />
<TranslateTransform />
</TransformGroup>
</Grid.RenderTransform>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
<!-- Just left for reference -->
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver"/>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Container"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
<EasingDoubleKeyFrame KeyTime="0:0:0.01" Value="1.05" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Container"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
<EasingDoubleKeyFrame KeyTime="0:0:0.01" Value="1.05" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled"/>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused"/>
<VisualState x:Name="Unfocused" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentControl x:Name="contentPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
IsTabStop="False"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This way you could just do a quick... ;
<Button Content="whatever/image/path/blah.png"
Style="{StaticResource GrowOnPressedThingy}"/>
...wherever you need it, and you've got it setup to go easily add other neat UI tricks you might think of in the future. Just a shot in the dark, but maybe worth considering.
Anyway, hope this helps. Cheers
Try casting the targetproperty:
Storyboard.TargetProperty="(FrameworkElement.Height)"
I am building Windows Store Application (Winrt, Metro, Windows 8 app). I try give images in gridview animation: after image is loaded from web and opened it populates its cell.
I have storyboard for that:
<Storyboard x:Name="PopupImageStoryboard">
<DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(FrameworkElement.Width)" Storyboard.TargetName="image">
<EasingDoubleKeyFrame KeyTime="0" Value="100"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="240"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="image">
<EasingDoubleKeyFrame KeyTime="0" Value="100"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="240"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
And I have such C# code for handling event that image loaded and opened:
private void Image_ImageOpened(object sender, RoutedEventArgs e)
{
Storyboard.SetTarget(PopupImageStoryboard, sender as Image);
PopupImageStoryboard.Begin();
}
It does not work, I can not change target and rerun same storyboard while it is running. But I want multiple images run this animation simultaneously. Can you recommend any solution for animation reuse ?
You should remove this from each of the child animations:
Storyboard.TargetName="image"
Then also I think a single Storyboard might not be possible to be used on two target elements at the same time, so the solution for that would be to put it in a DataTemplate, e.g.
<Page.Resources>
<DataTemplate
x:Name="myStoryboard">
<Storyboard ... />
</DataTemplate>
</Page.Resources>
Then in code you would say
var sb = (Storyboard)myStoryboard.LoadContent();
Storyboard.SetTarget(sb, sender as Image);
sb.Begin();
BTW - do not animate Width and Height properties. This is almost certainly not the best idea in your case. You should animate your RenderTransform properties instead, e.g. targeting ScaleTransform's ScaleX and ScaleY properties. If an animation is a dependent - it will cause layout updates in each frame which is very inefficient and will degrade your animation frame rate.
*EDIT
A better solution then would look like this:
<Page.Resources>
<DataTemplate
x:Name="myStoryboardTemplate">
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="ScaleX"
From="0.5"
To="1.0"
Duration="0:0:0.4">
<DoubleAnimation.EasingFunction>
<BackEase
Amplitude="2"
EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation
Storyboard.TargetProperty="ScaleY"
From="0.5"
To="1.0"
Duration="0:0:0.4">
<DoubleAnimation.EasingFunction>
<BackEase
Amplitude="2"
EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</DataTemplate>
</Page.Resources>
...
<Image
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="620"
Height="300"
Source="Assets/SplashScreen.png"
RenderTransformOrigin="0.5,0.5"
Stretch="Fill">
<Image.RenderTransform>
<ScaleTransform
x:Name="imageScaleTransform" />
</Image.RenderTransform>
</Image>
...
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var sb = (Storyboard)myStoryboardTemplate.LoadContent();
Storyboard.SetTarget(sb, imageScaleTransform);
sb.Begin();
}
I'm trying to skin a ScrollViewer in WPF using XAML but have been faced with a challenge. Normally my ScrollBars are hidden, but when the user start panning the screen they become visible. I have implemented this feature using ScrollChanged as follows:
<!-- Storyboards -->
<Storyboard x:Key="uiStoryboardFader">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)">
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1.0" />
<EasingDoubleKeyFrame KeyTime="0:0:2.0" Value="1.0" />
<EasingDoubleKeyFrame KeyTime="0:0:2.3" Value="0.0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<!-- Style Declaration -->
<!-- Control Template -->
<ScrollBar x:Name="PART_VerticalScrollBar" Opacity="0.0" />
<ScrollBar x:Name="PART_HorizontalScrollBar" Opacity="0.0" />
<!-- Control Template Triggers -->
<EventTrigger RoutedEvent="ScrollViewer.ScrollChanged">
<BeginStoryboard Storyboard="{StaticResource uiStoryboardFader}"
Storyboard.TargetName="PART_VerticalScrollBar" />
<BeginStoryboard Storyboard="{StaticResource uiStoryboardFader}"
Storyboard.TargetName="PART_HorizontalScrollBar" />
</EventTrigger>
<!-- Closing -->
What this does is change the opacity of the entire ScrollViewer, even though I set the TargetName to "PART_VerticalScrollBar" and "PART_HorizontalScrollBar". However when I manually set the TargetName inside the Storyboard declaration, everything works as expected. Is there anyway I can make this work by assigning the TargetName inside BeginStoryboard?
I take it you just do not want to copy-paste the animation? What you try to do will probably never work out, the value of that property is not inherited and you need two storyboards anyway, you cannot start the same storyboard twice (let alone on different controls).
This is how you can at least retain one instance of the animation:
<DoubleAnimationUsingKeyFrames x:Key="Animation"
Storyboard.TargetProperty="(UIElement.Opacity)">
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1.0" />
<EasingDoubleKeyFrame KeyTime="0:0:2.0" Value="1.0" />
<EasingDoubleKeyFrame KeyTime="0:0:2.3" Value="0.0" />
</DoubleAnimationUsingKeyFrames>
<!-- ... -->
<EventTrigger RoutedEvent="ScrollViewer.ScrollChanged">
<BeginStoryboard>
<Storyboard Storyboard.TargetName="PART_VerticalScrollBar">
<StaticResourceExtension ResourceKey="Animation" />
</Storyboard>
</BeginStoryboard>
<BeginStoryboard>
<Storyboard Storyboard.TargetName="PART_HorizontalScrollBar">
<StaticResourceExtension ResourceKey="Animation" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
I'm trying to get states to change with the visual state manager through code.
I am using:
Microsoft.Expression.Interactivity.Core.ExtendedVisualStateManager.GoToElementState(this.LayoutRoot, "stateRegistration", true);
But it doesn't seem to want to work, I have create an event handler and also a listener but it there's no state changed when using that code.
Can anyone help me out.
XAML CODE (Code Snippet):
<Grid x:Name="LayoutRoot" Width="897" Height="699">
<VisualStateManager.VisualStateGroups>
<VisualState x:Name="stateRegistration">
<Storyboard>
<DoubleAnimation Duration="0" To="870" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="RegisterContent" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="880" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="HomeContent" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="btnRegRegister" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="-10" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="btnRegRegister" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
Thanks.
There is strange behavior with the VisualStateManager: its states must be situated not in the control, but in the child control.
It means, that the GoToState method should be called with the this parameter instead of the this.LayoutRoot parameter, but definitions of state groups must be situated inside the Grid:
VisualStateManager.GoToState(this, "stateRegistration", true);
I don't know where to get the ExtendedVisualStateManager class so I use the default one.
Also if any animation of the state storyboard fails - all animations are cancelled. So try this code sample with two animations, it must work:
<Grid x:Name="LayoutRoot" Width="897" Height="699">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="stateRegistration">
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="btnRegRegister" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="-10" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="btnRegRegister" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Button x:Name="btnRegRegister" Content="Some button" Click="btnRegRegister_Click" VerticalAlignment="Center" HorizontalAlignment="Center">
<Button.RenderTransform>
<CompositeTransform TranslateX="0" TranslateY="0" />
</Button.RenderTransform>
</Button>
</Grid>