I'm trying to animate the ScaleY property of a LayoutTransform based on a DataTrigger bound to a boolean on my ViewModel class. The animation happens when the value is first seen to be false by the DataTrigger (when the application first starts) and when i first change it to true in a checkbox's checked event but not when i set it to false in the same checkbox's unchecked event.
A simplified version of what i'm doing is listed below.
The ViewModel class is very simple, containing a single boolean DependencyProperty called Selected.
public class VM : DependencyObject
{
public bool Selected
{
get { return (bool)GetValue(SelectedProperty); }
set { SetValue(SelectedProperty, value); }
}
// Using a DependencyProperty as the backing store for Selected. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedProperty =
DependencyProperty.Register("Selected", typeof(bool), typeof(VM), new UIPropertyMetadata(false));
}
The Window.xaml contains a button and a checkbox. When the checkbox is checked, i set the ViewModel's 'Selected' property to true and false when it is unchecked. Here's the code for both the xaml and it's code-behind.
<Window x:Class="DataTriggers.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:y="clr-namespace:DataTriggers"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<y:VM x:Key="VM"/>
<Style TargetType="Button" x:Key="but">
<Style.Triggers>
<DataTrigger Binding="{Binding Selected}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1"
To="0"
Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleY)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding Selected}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1"
To="1"
Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleY)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<Button Style="{StaticResource but}" DataContext="{StaticResource VM}">
<Button.LayoutTransform>
<ScaleTransform></ScaleTransform>
</Button.LayoutTransform>
me
</Button>
<CheckBox Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked"/>
</StackPanel>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
VM vm = this.FindResource("VM") as VM;
vm.Selected = true;
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
VM vm = this.FindResource("VM") as VM;
vm.Selected = false;
}
}
I know that the DataTrigger fires when the property is false because if i change the DoubleAnimation to a simple Setter operating on the Opacity property then i see the correct results. So it would seem to be a problem with how I'm using the DoubleAnimation.
Any help would be appriciated.
This is ODD behavior but i decided to refactor the 'False' case into the DataTrigger's ExitActions like this -
<DataTrigger Binding="{Binding Selected}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1"
To="1"
Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleY)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1"
To="0"
Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleY)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
That works as intended. I don't know what the difference is between the two cases but at least it's an answer.
Related
So i have this Storyboard:
<Storyboard x:Key="animate">
<DoubleAnimation BeginTime="0:0:0" Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:2.0"/>
</Storyboard>
My binding value:
public bool IsFound
{
get { return _isFound; }
set
{
_isFound= value;
NotifyPropertyChanged();
}
}
And my Grid that get this Storyboard:
<Grid name="myGrid">
....
<Grid>
if(IsFound)
{
Storyboard storyboard = Resources["animate"] as Storyboard;
if (storyboard != null)
storyboard.Begin(myGrid);
}
So i am looking for something pure XAML instead of checking this IsFound in code behind.
You can use a DataTrigger:
<Style TargetType="Grid" x:Key="MyAnimatedGrid">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFound}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard StoryBoard="{StaticResource animate}" />
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
I've faced with the problem trying handle a RoutedEvent in DataTemplate. My code is below.
<DataTemplate x:Key="AdditionalTemplate">
<Grid>
...
</Grid>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="local:EditorView.HideView">
<BeginStoryboard>
<Storyboard>
...
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate> "HideView" event set in code behind
"HideView" event set in code behind such a way:
public static readonly RoutedEvent HideViewEvent = EventManager.RegisterRoutedEvent("HideView", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(EditorView));
As result nothing happens after event calling.
Do you have any ideas?
You need to create a property in your view model and replace your event trigger by DataTrigger:
<DataTrigger Binding="{Binding MyProperty}">
<BeginStoryboard>
<Storyboard>
...
</Storyboard>
</BeginStoryboard>
</DataTrigger>
Changed your code in a way when your event raising changes this property "MyProperty". This will activate your data trigger and storyboard will run.
I have a button as follows:
<Button x:Name ="Btn_Import" Grid.Row="33" Grid.Column="15" Grid.ColumnSpan="36" Grid.RowSpan="36" >
<Button.Template>
<ControlTemplate>
<Grid RenderTransformOrigin="0.5,0.5" x:Name="bg">
<Image x:Name ="import_image" Source="{Binding ImportBtnBaseImagePath}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="import_image" Property="Source" Value="{Binding ImportBtnOverImagePath}" />
</Trigger>
<Trigger Property="ButtonBase.IsPressed" Value ="True">
<!-- press effect -->
<Setter TargetName="bg" Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="0.9" ScaleY="0.9"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
<Button.Triggers>
<EventTrigger RoutedEvent="PreviewMouseDown" >
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Studio" Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:2" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Completed">
<i:InvokeCommandAction Command="{Binding NavigateCommand}" CommandParameter="ImportButtonClickParmeters" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
I want this button to triger an animation on some other control to fade out for 2 seconds, and then once the animation is completed to navigate to some other view through 'NavigateCommand'. But I get the following error:
Additional information: Specified value of type
'System.Windows.Interactivity.EventTrigger' must have IsFrozen set to
false to modify.
Your issue depends on a well know bug. Unluckly I found that the common solution does not properly work in this case.
Anyway if you wish to keep your application MVVM compliant, I suggest you to create a "fake" animation, whose task is to execute a command. Of course this animation has to be the last one in your storyboard.
This is the CommandFakeAnimation code:
public class CommandFakeAnimation : AnimationTimeline
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandFakeAnimation), new UIPropertyMetadata(null));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(CommandFakeAnimation), new PropertyMetadata(null));
public CommandFakeAnimation()
{
Completed += new EventHandler(CommandAnimation_Completed);
}
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
public object CommandParameter
{
get
{
return GetValue(CommandParameterProperty);
}
set
{
SetValue(CommandParameterProperty, value);
}
}
private void CommandAnimation_Completed(object sender, EventArgs e)
{
if (Command != null && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
}
}
protected override Freezable CreateInstanceCore()
{
return new CommandFakeAnimation();
}
public override Type TargetPropertyType
{
get
{
return typeof(Object);
}
}
public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
{
return defaultOriginValue;
}
}
As you can see you can apply this animation to whatever dependecy property that you wish, since it does not change its value. It just execute a command when it is completed.
Now we can use the new animation in the XAML:
<Button.Triggers>
<EventTrigger RoutedEvent="PreviewMouseDown">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:2" />
<local:CommandFakeAnimation Duration="0:0:0" Command="{Binding Path=YourCommand, Mode=OneWay}"
CommandParameter="{Binding Path=YourParameter, Mode=OneWay}"
Storyboard.TargetProperty="Opacity" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
I hope it can help you.
I am trying to develop a menu that looks like windows media center menu. So I made a usercontrol as a container which will contain menuitems (each menuitems is a usercontrol).I want to hide or display the container after a click on a button.So in click event of the button, i set to true a dependencyproperty "DisappearProperty" (located in the container). But this method doesn't work.
to disappear the container, i trigger a storyboard based on "DisappearProperty" DependencyProperty. here the code :
//The container
<UserControl x:Class="MyUserControls.WMCBorder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Name="UC"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<ControlTemplate x:Key="ctContainer">
<ControlTemplate.Resources>
<Storyboard x:Key="SBDisappear">
<DoubleAnimation Storyboard.TargetName="borderZooming"
Storyboard.TargetProperty="ScaleX"
From="1" To="4.0"
Duration="0:0:0.1"/>
<DoubleAnimation Storyboard.TargetName="borderZooming"
Storyboard.TargetProperty="ScaleY"
From="1" To="4.0"
Duration="0:0:0.1"/>
<DoubleAnimation Storyboard.TargetName="container"
Storyboard.TargetProperty="Opacity"
From="1"
To="0"
Duration="0:0:0.1"/>
</Storyboard>
<Storyboard x:Key="SBDisplay">
<DoubleAnimation Storyboard.TargetName="borderZooming"
Storyboard.TargetProperty="ScaleX"
From="4" To="1.0"
Duration="0:0:0.1"/>
<DoubleAnimation Storyboard.TargetName="borderZooming"
Storyboard.TargetProperty="ScaleY"
From="4" To="1.0"
Duration="0:0:0.1"/>
<DoubleAnimation Storyboard.TargetName="container"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:0.1"/>
</Storyboard>
</ControlTemplate.Resources>
<Border x:Name="container">
<Border.RenderTransform>
<ScaleTransform x:Name="borderZooming" ScaleX="0.1" ScaleY="0.1"/>
</Border.RenderTransform>
<Grid>
<ContentPresenter />
</Grid>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=DisappearProperty}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource SBDisappear}"/>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding Path=DisplayProperty}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource SBDisplay}"/>
</DataTrigger.EnterActions>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Resources>
<Grid>
</Grid>
//Code behind of the container
public partial class WMCBorder : UserControl
{
public WMCBorder()
{
InitializeComponent();
}
public bool Disappear
{
get { return (bool)GetValue(DisappearProperty); }
set { SetValue(DisappearProperty, value); }
}
// Using a DependencyProperty as the backing store for Disappear. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisappearProperty =
DependencyProperty.Register("Disappear", typeof(bool), typeof(WMCBorder), new PropertyMetadata(false));
public bool Display
{
get { return (bool)GetValue(DisplayProperty); }
set { SetValue(DisplayProperty, value); }
}
// Using a DependencyProperty as the backing store for Disappear. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayProperty =
DependencyProperty.Register("Display", typeof(bool), typeof(WMCBorder), new PropertyMetadata(false));
}
//The click event of the Hide button
private void Button_Click(object sender, RoutedEventArgs e)
{
wmcb.Disappear = true;
}
//The click event of the Hide button
private void Button2_Click(object sender, RoutedEventArgs e)
{
wmcb.Display= true;
}
Thanks in advance.
As I received no response so far, so I simplified the question on this post
Been trying to create an animation to dynamically adjust height. I found this info that helped but when I try to use it I get an error: 'System.Windows.Media.Animation.DoubleAnimation' cannot use default destination value of 'NaN'.
If I specify the height I get that error.
Style:
<Style x:Key="bdrSlideIn"
TargetType="{x:Type Border}">
<Style.Resources>
<Storyboard x:Key="storyBoardIn">
<DoubleAnimation BeginTime="00:00:00"
From="0"
Duration="00:00:00.65"
Storyboard.TargetName="{x:Null}"
Storyboard.TargetProperty="(FrameworkElement.Height)"
DecelerationRatio="1" />
</Storyboard>
<Storyboard x:Key="storyBoardOut">
<DoubleAnimation BeginTime="00:00:00"
To="0"
Duration="00:00:00.65"
Storyboard.TargetName="{x:Null}"
Storyboard.TargetProperty="(FrameworkElement.Height)"
AccelerationRatio="1" />
</Storyboard>
</Style.Resources>
<Style.Triggers>
<DataTrigger Binding="{Binding SearchExecuted}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource storyBoardIn}"
Name="SlideStoryboard" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource storyBoardOut}" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
Border:
<Border VerticalAlignment="Top"
Style="{StaticResource bdrSlideIn}">
<WPFToolKit:DataGrid Name="dgSearchResults"
ItemsSource="{Binding SearchResults}"
MaxHeight="280"
VerticalAlignment="Top">...
If you want to keep Height dynamic then you can't animate Height directly: As you've seen, unless you explicitly assign it WPF will try to interpolate to NaN.Instead, give your element a LayoutTransform <ScaleTransform/>, and animate the ScaleX and ScaleY parameters of that transformation.
You could always create an attached property for the height that does nothing other than set the height property on the target control, that way you can animate using To on your attached property.
public class AnimatedPanelBehavior
{
public static double GetAnimatedHeight(DependencyObject obj)
{
return (double)obj.GetValue(AnimatedHeightProperty);
}
public static void SetAnimatedHeight(DependencyObject obj, double value)
{
obj.SetValue(AnimatedHeightProperty, value);
}
public static readonly DependencyProperty AnimatedHeightProperty =
DependencyProperty.RegisterAttached("AnimatedHeight", typeof(double), typeof(AnimatedPanelBehavior), new UIPropertyMetadata(0d, new PropertyChangedCallback((s, e) =>
{
FrameworkElement sender = s as FrameworkElement;
sender.Height = (double)e.NewValue;
})));
}
Then to animate it you would use a normal animation, just tried it now and it works fine but I've not investigated any further than "it works".
<DoubleAnimation Storyboard.TargetProperty="(local:AnimatedPanelBehavior.AnimatedHeight)" To="100" Duration="0:0:5"/>
use AnimatedHeight instead of height on anything that you want to be able to animate.
Since your TargetProperty is Height, you can just set a default value of Height and it will work. In my case as soon as I have put a number for Height on the actual control itself,
<TextBlock Height="30" ..
<TextBlock Style ..
...
<StoryBoard ..
and then had the animation (which were to make toggle the height) it worked fine.