I am trying to animate a control so that it's visibility is set to visible then animating the opacity from 0 to 1
However nothing happens, then after 1 second the control is show with an opacity of 1... I cannot see what i am doing wrong
This is the code i have tried
<Grid x:Name="layout_root" Margin="10">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Filtering">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:1">
<VisualTransition.GeneratedEasingFunction>
<ElasticEase EasingMode="EaseInOut"/>
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Disabled"/>
<VisualState x:Name="Enabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="0:0:0" Duration="0:0:0" Storyboard.TargetName="filter_control" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetName="filter_control" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox x:Name="filter_control" Margin="0,0,0,10" Text="Filtering" Visibility="Collapsed" Opacity="0"/>
<ListView Grid.Row="1" ItemsSource="{Binding Posts}">
<ListView.View>
<GridView>
<GridViewColumn Width="100" Header="Date" DisplayMemberBinding="{Binding Date, StringFormat={}{0:dd/MM/yyyy}}"/>
<GridViewColumn Width="100" Header="Text" DisplayMemberBinding="{Binding Text}"/>
<GridViewColumn Width="100" Header="Value" DisplayMemberBinding="{Binding Value, StringFormat=F2}"/>
</GridView>
</ListView.View>
</ListView>
<Button Grid.Row="1" Content="v" FontFamily="Marlett" FontSize="14" VerticalAlignment="Top" HorizontalAlignment="Left" Click="ShowFilterClick"/>
</Grid>
As to the question of what you're doing wrong or why you see the behavior that you see: the storyboard for the Enabled state is the storyboard that the VSM uses while the VSGroup is in that state. You specify a transition storyboard for the group, though, and the VSM applies that when transitioning between states. So, when you put the VSGroup into the Enabled state, the VSM first plays the transition storyboard then uses the steady-state storyboard that you specify for the Enabled state. The transition storyboard is 1 sec, and that's why you're seeing the 1 sec delay and then the pop.
Something like the following is probably what you want. Note that the transition storyboard does the action/animation that you want, and the state storyboards just state the final values at which the animated properties should be held. Also, I apply the easing function to the double animation rather than to the entire VisualTransition -- it doesn't make sense to try to interpolate Visibility with an easing function.
<VisualStateGroup x:Name="Filtering">
<VisualStateGroup.Transitions>
<VisualTransition From="Disabled" To="Enabled" GeneratedDuration="0:0:1">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0:0:0" Storyboard.TargetName="filter_control" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Duration="0:0:1" Storyboard.TargetName="filter_control" Storyboard.TargetProperty="(UIElement.Opacity)" To="1">
<DoubleAnimation.EasingFunction>
<ElasticEase EasingMode="EaseInOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</VisualTransition>
<!-- you could also have a transition from Enabled to Disabled -->
</VisualStateGroup.Transitions>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0:0:0" Storyboard.TargetName="filter_control" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Duration="0:0:0" Storyboard.TargetName="filter_control" Storyboard.TargetProperty="(UIElement.Opacity)" To="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Enabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0:0:0" Storyboard.TargetName="filter_control" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Duration="0:0:0" Storyboard.TargetName="filter_control" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
The Visibility enum is not an inherently animatable property. Generally only numeric properties are truly animatable, since WPF can fill in the spaces between keyframes. For example, it knows that an opacity halfway between the value of 0 and 1 is 0.5. It knows every possible value based on the current time.
If you animate from Visibility.Collapsed to Visibility.Visible over 1 second, it has no idea what to do at the 0.5 second mark or any other point in between. It only knows you're changing an enum from 1 value to another. If your transition time is 1 second, it waits till that second is up and then changes the value, so you never get to see the opacity animation happening.
You can try using FluidLayout. You enable it like so:
<VisualStateGroup x:Name="Filtering" ei:ExtendedVisualStateManager.UseFluidLayout="True">
You can also enable it using a toggle in the Blend UI.
FluidLayout animates layout changes for you. Collapsing or expanding an element affects the layout, so it can automatically animate those layout changes.
Related
I have a textblock which should only show 2 lines of the text, while it is unselected. As soon as it gets selected, I want it to expand smoothly.
I started with something like:
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="Second"
Storyboard.TargetProperty="(TextBlock.MaxHeight)"
To="50.0" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
But the issue here is, that I don't know how big the text is.
You should be able to use From='0' instead, which would start the animation with a value of 0 and end with whatever the value of MaxHeight is. However, that raises a different problem, as MaxHeight defaults to infinity, which would make the animation far too fast. Adding an ObjectAnimationUsingKeyframes at the start that sets MaxHeight to ActualHeight might work to resolve this. Something like this:
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyframes
Storyboard.TargetName='Second'
Storyboard.TargetProperty='(TextBlock.MaxHeight)'>
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{Binding TargetName=Second, Path=ActualHeight}" />
</ObjectAnimationUsingKeyframes>
<DoubleAnimation
Storyboard.TargetName="Second"
Storyboard.TargetProperty="(TextBlock.MaxHeight)"
From="0" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
You can use DoubleAnimation to implement this. I have implemented this in a sample application.
<Window.Resources>
<Storyboard x:Key="OnGotFocus">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="textBox" Storyboard.TargetProperty="(FrameworkElement.Height)">
<EasingDoubleKeyFrame KeyTime="0:0:2">
<EasingDoubleKeyFrame.Value>
<System:Double>NaN</System:Double>
</EasingDoubleKeyFrame.Value>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="OnLostFocus">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="textBox" Storyboard.TargetProperty="(FrameworkElement.Height)">
<EasingDoubleKeyFrame KeyTime="0">
<EasingDoubleKeyFrame.Value>
<System:Double>NaN</System:Double>
</EasingDoubleKeyFrame.Value>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="30" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="UIElement.GotFocus" SourceName="textBox">
<BeginStoryboard Storyboard="{StaticResource OnGotFocus}" />
</EventTrigger>
<EventTrigger RoutedEvent="UIElement.LostFocus" SourceName="textBox">
<BeginStoryboard x:Name="OnLostFocus_BeginStoryboard" Storyboard="{StaticResource OnLostFocus}" />
</EventTrigger>
</Window.Triggers>
Your code for textbox should be :
<TextBox x:Name="textBox"
Height="30"
HorizontalAlignment="Right"
Text="Hello World" />
This will animate the textbox to a specified height as soon as it gets focussed. I have added an animation to collapse it as well when it loses focus.
Hope this helps you.
I am a little bit confused of states and animations in WPF.
I would like to make a usercontrol. This usercontrol will contains (inside the main grid) 2 another grid. One of them would be HEADER and second one will be CONTENT. If user click on header, content will expand, otherwise will be collapsed. And i would like to animation that expanding (Slide down the content from the header).
Basicaly i would like to do that by states (for future purpose). Problem is, if i add the states and i am using the slide efect with transformation, the content of this grid (CONTENT GRID) is transformed as well. So i would like to use the states with modifying just the height of the element. If u modify only the element, no animation appear and it just change its height at once.
The hierarchy looks like:
--- wrapper grid
------ header grid
--------- content of header
------ content grid
--------- content of content grid (like buttons, labels, etc)
The visualstates looks like:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:1"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Expanded"/>
<VisualState x:Name="Collapsed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="grid">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<x:Double>0</x:Double>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Any advice to see the expanding and collapsing the grid with modifying height by states?
With animation only it works perfect, but its better for me to do it with states, how i said, for future purpose.
Maybe i found a answer. For now it works but i am trying to understanding that changes.
code of visualstates in here:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Expanded">
<Storyboard>
<DoubleAnimation EnableDependentAnimation="True" Storyboard.TargetName="grid" Storyboard.TargetProperty="(FrameworkElement.Height)" From="0" To="366" Duration="0:0:0.600" />
</Storyboard>
</VisualState>
<VisualState x:Name="Collapsed">
<Storyboard>
<DoubleAnimation EnableDependentAnimation="True" Storyboard.TargetName="grid" Storyboard.TargetProperty="(FrameworkElement.Height)" From="366" To="0" Duration="0:0:0.600" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
The important line is:
<DoubleAnimation EnableDependentAnimation="True" Storyboard.TargetName="grid" Storyboard.TargetProperty="(FrameworkElement.Height)" From="366" To="0" Duration="0:0:0.600" />
I found its needed to have EnableDependantAnimation true.
Now it works as a charm, but i do not like that FROM TO set. But its possibly the best i can do in here.
I have VisualStateManager to control when the State occurs, the control is enabled:
Here is the property of the state (string):
states:StateManager.VisualStateProperty="{Binding SomeProp}"
Here the VisualStateManager:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualState x:Name="MyName">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.IsEnabled)" Storyboard.TargetName="MyTextBox">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="HerName">
<Storyboard>
...
</Storyboard>
</VisualState>
<VisualState x:Name="This">
<Storyboard>
...
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Here my text box:
<TextBox Name="MyTextBox" />
My question is: What happens when I add the TextBox the following line:
IsEnable= {Binding isProp}// isProp = bool
The way I see it, it eliminates the IsEnable of the TextBox and not refers to him, only to State.
Is this true? And is there a way they both work?
In your case, the Animation will take precedence over the binding, but only as long as the Animation's timeline is running. That is, when the visual state is "MyName", the animation will control the IsEnabled property; otherwise, the binding will.
You may be interested in this list of Dependency Property Value Precedence. The binding counts as a "Local value" and is of lower precedence than the animation.
The follow XAML represents an object I am trying to build in Expression Blend. I am having trouble with the DataTrigger in the StackPanel - the application does not go to Empty when the trigger matches the data. Further explanation is after this code:
<DataTemplate x:Key="SampleTemplate">
<StackPanel x:Name="SampleStack" Style="{StaticResource DefaultSampleStyle}" Width="64" Height="60">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0">
<Storyboard>
<ColorAnimation Duration="0" To="#FFDFE04B" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="SampleStack" d:IsOptimized="True"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Empty">
<Storyboard>
<ColorAnimation Duration="0" To="#FF4B6FE0" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="SampleStack" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<VisualStateManager.CustomVisualStateManager>
<ei:ExtendedVisualStateManager/>
</VisualStateManager.CustomVisualStateManager>
<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding IsActive}" Value="False">
<ei:GoToStateAction StateName="Empty" UseTransitions="False"/>
</ei:DataTrigger>
</i:Interaction.Triggers>
<TextBlock x:Name="StartOn" Text="{Binding StartOn, StringFormat=hh:mm}"/><TextBlock x:Name="textBlock" Text="-" />
<TextBlock x:Name="EndOn" Text="{Binding EndOn, StringFormat=hh:mm}"/>
</StackPanel>
</DataTemplate>
If I use an EventTrigger with a Loaded value, the Empty state is correctly applied based on the IsActive binding.
If I use the existing DataTrigger and change a Property on the Stackpanel, such as Height, based on the binding of IsActive this also works.
Am I doing something fundamentally wrong in the XAML? Do you need a more complete example of the XAML to understand the issue?
do you need the GoToStateAction?
I guess, the problem is the Binding "at startup". I added a dispatcher and threw the NotifyPropertyChanged again after one second. Then it works. Propably you can workaround it like this. You wait till the control is loaded and then throw the PropertyChanged again. This is not a nice way and similar to your idea (If I use an EventTrigger with a Loaded value,...)
I would recommend you to use a DataStateBehaviour. If you hav a boolean to decide in which satte you have to go, this is great. It is a behaviour where you can bind the condition to a property and then set a true and a false state.
It would look like this (I did a few adjustments just for testing at my computer):
<DataTemplate x:Key="SampleTemplate">
<StackPanel x:Name="SampleStack" Width="64" Height="60" Background="White">
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding IsChecked}" Value="True" TrueState="Empty" FalseState="Base"/>
</i:Interaction.Behaviors>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Empty">
<Storyboard>
<ColorAnimation Duration="0" To="Red" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="SampleStack" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Base"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<VisualStateManager.CustomVisualStateManager>
<ei:ExtendedVisualStateManager/>
</VisualStateManager.CustomVisualStateManager>
<TextBlock x:Name="StartOn" Text="Test"/>
</StackPanel>
</DataTemplate>
As you can see I added a second state to the VisualStateGroup (There is now empty and base). I would recommend this not only because the DataStateBehaviour needs at least two states in one group. If you have only one state, you have no chance to change the state of this group back to normal, e.g.
I hope this answer helps you.
BR,
TJ
I've video player with two button: Play and Pause.
I want to use only one button. when user clicks on Play, the button appearance will changed to Pause and vice versa.
What is the better approach to achieve that task without using cs code behind?
I've tried to use DataTrigger to my IsPlaying property, but with no much success....
Here is my code:
<Window.Resources>
<Viewbox x:Key="viewboxSource" >
<Viewbox.Triggers>
<DataTrigger Binding="{Binding IsPlaying}" Value="True">
<Setter Property="Path">
<Setter.Value>
<Path Stroke="Black" StrokeThickness="1" Fill="AliceBlue">
<Path.Data>
<GeometryGroup>
<EllipseGeometry Center="100,100" RadiusX="100" RadiusY="100"/>
</GeometryGroup>
</Path.Data>
</Path>
</Setter.Value>
</Setter>
</DataTrigger>
</Viewbox.Triggers>
</Viewbox>
</Window.Resources>
<StackPanel>
<Button Content="{StaticResource viewboxSource}"></Button>
</StackPanel>
But I gut an error that says " 'Path' member is not valid because it does not have a qualifying type name " .
Can anyone can help or give me a better solution?
These kind of behaviour fits toggle button patern.
Make a style in your resources
<Style x:Key="PlayToggleButtonStyle" TargetType="ToggleButton" >
and then define a templeate in it
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
What is the most important here is to use VisualStateManager.
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Disabled"/>
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver"/>
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimation Duration="0" To="2" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="border" />
<ColorAnimation Duration="0:0:0.2" To="#FF392929" Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[0].(GradientStop.Color)" Storyboard.TargetName="border"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
I use 2 animation. One moves button for 2 pixels and second change the gradient which gives a nice experience.
The only drawback is you need to use storyboards to handle these states. You need to add a Path object which I called Geometry nad mainupulate it.
<Storyboard Storyboard.TargetName="Geometry"
Storyboard.TargetProperty="Data">
<ObjectAnimationUsingKeyFrames>
<DiscreteObjectKeyFrame KeyTime="0" Value=""/> <!-- place the data here (how your button looks like) -->
</ObjectAnimationUsingKeyFrames>
</Storyboard>
But IMHO the better solution is to place 2 Path object in the template that on is over another and change the opacity of the top-most one.
<Storyboard>
<DoubleAnimation Storyboard.TargetName="TopGeometry" Storyboard.TargetProperty="Opacity" Duration="0:0:0.5" To="0.0">
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode="EaseIn"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
You would have a nice transition between these two states. What is more, no data is needed f.e IsPLaying property.