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.
Related
In WPF app I am trying to animate a border colour change on MouseEnter event of a TextBox.
I searched for a while and followed different tutorials, but everything seems to end up the same way:
When the mouse enters the colour of the border changes to what I have set in the animation "From"
Then nothing happens, no animation at all
When mouse leaves after a period longer then the animation duration the colour changes to what I have set in the animation "To"
If the mouse leaves before the animation duration, the colour of the border changes to some colour "in between"
From this I figured that the animation is happening, but it is not showing it as it animates...
The code is here:
private void txtSpeakMe_MouseEnter(object sender, MouseEventArgs e)
{
ColorAnimation ca = new ColorAnimation();
ca.From = (Color)ColorConverter.ConvertFromString("#0066FF");
ca.To = (Color)ColorConverter.ConvertFromString("#FF0000");
ca.Duration = TimeSpan.FromSeconds(3);
txtSpeakMe.BorderBrush.BeginAnimation(SolidColorBrush.ColorProperty, ca);
}
Any ideas on why it is not showing the animation as it is happening? I tried animation in XAML using MS tutorials, the same effect - it animates but it is not showing the process of animation until mouse leaves...
It may be easier to use a Trigger in the Xaml to perform this animation, Triggers have a EnterActions and ExitActions so you could use the IsMouseOver event to start/stop the animation
Example:
<Border Name="border" BorderThickness="5" Width="200" Height="30">
<TextBox Text="StackOverflow"/>
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="#0066FF" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard HandoffBehavior="SnapshotAndReplace">
<Storyboard>
<ColorAnimation Duration="0:0:3" To="#FF0000" Storyboard.TargetProperty="BorderBrush.Color" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard HandoffBehavior="SnapshotAndReplace">
<Storyboard>
<ColorAnimation Duration="0:0:3" To="#0066FF" Storyboard.TargetProperty="BorderBrush.Color" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
Not sure why TextBox border color is not changing, but you could try this:
<Border Name = "border" BorderThickness="5">
<TextBox MouseEnter="TextBox_MouseEnter" MouseLeave="TextBox_MouseLeave"/>
</Border>
Then try this code on MouseEnter and MaouseLeave:
ColorAnimation ca = new ColorAnimation();
ca.From = (Color)ColorConverter.ConvertFromString("#0066FF");
ca.To = (Color)ColorConverter.ConvertFromString("#FF0000");
ca.Duration = TimeSpan.FromSeconds(3);
Storyboard sb = new Storyboard();
sb.Children.Add(ca);
Storyboard.SetTarget(ca, border);
Storyboard.SetTargetProperty(ca, new PropertyPath("(Border.BorderBrush).(SolidColorBrush.Color)"));
sb.Begin();
I was just hoping you could help fill a gap in my WPF knowledge.
(please forgive the generic naming, not sure if it helps)
I've a custom object, MyObject, that implements INotifyPropertyChanged. It has a property called MyCustomProperty, as follows;
public int MyCustomProperty
{
get { return this._myCustomProperty; }
set
{
if (this._myCustomProperty == value)
return;
this._myCustomProperty= value;
OnPropertyChanged("MyCustomProperty");
}
}
This all works.
In my WPF app I have these 3 functions;
private void DoStuff()
{
AddItemsToCanvas();
ChangeValues();
}
private void AddItemsToCanvas()
{
DataTemplate dt = (DataTemplate)FindResource("myDataTemplate");
foreach (MyObject temp in ListOfMyObjects)
{
ContentControl cc = new ContentControl();
cc.ContentTemplate = dt;
cc.Content = temp;
myCanvas.Children.Add(cc);
}
}
private void ChangeValues()
{
// this simply changes the MyCustomPropery in each of the objects
}
The DataTemplate looks like this;
<DataTemplate x:Key="myDataTemplate">
<Canvas>
<TextBlock Name="tb_debug" Text="{Binding Path=MyCustomProperty, NotifyOnTargetUpdated=True}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="tb_debug" Storyboard.TargetProperty="(Canvas.Top)" From="0" To="350" Duration="0:0:1.6" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</Canvas>
</DataTemplate>
It simply moves the textbox from the top to the bottom of the canvas when MyCustomProperty changes.
When the UserControl is Loaded, I call both AddItemsToCanvas() and ChangeValues(). The Text value updates and displays the correct value, but the Trigger doesn't fire (ie the TextBox doesn't move).
Any time after that, when I call ChangeValues() the Text updates AND the TextBox moves.
Why would the EventTrigger be failing that initial time?
thanks in advance
I'm not sure why the Binding.TargetUpdated does not get called, but it could be only called when a the target is updated once already set and not when its first set (maybe).
But you could just add another EventTrigger on TextBlock Loaded in your DataTremplate to make sure it fires on UserControl Load.
Example:
<DataTemplate x:Key="myDataTemplate">
<DataTemplate.Resources>
<Storyboard x:Key="animation" >
<DoubleAnimation Storyboard.TargetName="tb_debug" Storyboard.TargetProperty="(Canvas.Top)" From="0" To="350" Duration="0:0:1.6" />
</Storyboard>
</DataTemplate.Resources>
<Canvas>
<TextBlock Name="tb_debug" Text="{Binding Path=MyCustomProperty, NotifyOnTargetUpdated=True}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard Storyboard="{StaticResource animation}" />
</EventTrigger>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard Storyboard="{StaticResource animation}" />
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</Canvas>
</DataTemplate>
How can I highlight other pieces (columns, bars etc.) in a chart created with wpf toolkit. I am using a control template to style my own chart. So far I used a trigger to get a fading effect on the element on which the mouse is residing. I want to invert this; to fade other elements (a popular charting visual gimmick) on to which mouse is not pointing. Following image shows the selected column Faded, I want it to be the other way around.
Just set the default value to faded and use the trigger to bring it up to full opacity. You have done some other styling but here is an example based on the default style:
<Grid>
<Grid.Resources>
<PointCollection x:Key="sampleData">
<Point>1,20</Point>
<Point>2,40</Point>
<Point>3,30</Point>
</PointCollection>
<Style x:Key="dimEffectStyle" TargetType="{x:Type charting:ColumnDataPoint}" BasedOn="{StaticResource {x:Type charting:ColumnDataPoint}}">
<Setter Property="Opacity" Value="0.25"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.25" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<charting:Chart>
<charting:ColumnSeries
Title="A"
ItemsSource="{StaticResource sampleData}"
IndependentValueBinding="{Binding X}"
DependentValueBinding="{Binding Y}"
DataPointStyle="{StaticResource dimEffectStyle}"
/>
</charting:Chart>
</Grid>
Edit:
If you want to change all the other data points except the data point the mouse is over, that is a bit harder and can't be done simply by restyling the controls. But you can create your own series control that has that capability. Here is a chart with an unstyled column series class called MouseNotOverColumnSeries with a new MouseNotOverOpacity property:
<Grid.Resources>
<PointCollection x:Key="sampleData">
<Point>1,20</Point>
<Point>2,40</Point>
<Point>3,30</Point>
</PointCollection>
</Grid.Resources>
<charting:Chart Name="chart1">
<local:MouseNotOverColumnSeries
Title="A"
ItemsSource="{StaticResource sampleData}"
IndependentValueBinding="{Binding X}"
DependentValueBinding="{Binding Y}"
MouseNotOverOpacity="0.5"
/>
</charting:Chart>
Here is the MouseNotOverColumnSeries class:
public class MouseNotOverColumnSeries : ColumnSeries
{
public double MouseNotOverOpacity { get; set; }
protected override void OnDataPointsChanged(IList<DataPoint> newDataPoints, IList<DataPoint> oldDataPoints)
{
base.OnDataPointsChanged(newDataPoints, oldDataPoints);
foreach (var dataPoint in oldDataPoints)
{
dataPoint.MouseEnter -= new MouseEventHandler(dataPoint_MouseEnter);
dataPoint.MouseLeave -= new MouseEventHandler(dataPoint_MouseLeave);
}
foreach (var dataPoint in newDataPoints)
{
dataPoint.MouseEnter += new MouseEventHandler(dataPoint_MouseEnter);
dataPoint.MouseLeave += new MouseEventHandler(dataPoint_MouseLeave);
}
}
void dataPoint_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
foreach (var dataPoint in ActiveDataPoints)
if (e.OriginalSource != dataPoint) dataPoint.Opacity = MouseNotOverOpacity;
}
void dataPoint_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
foreach (var dataPoint in ActiveDataPoints)
dataPoint.Opacity = 1;
}
}
We just pay attention to when the data points change and register mouse enter/leave handlers that manipulate the opacity of all the other data points that the mouse is not over. This could be expanded to support storyboards, etc.
I am trying to create a simple (I think) animation effect based on a property change in my ViewModel. I would like the target to be a specific textblock in the control template of a custom control, which inherits from Window.
From the article examples I've seen, a DataTrigger is the easiest way to accomplish this. It appears that Window.Triggers doesn't support DataTriggers, which led me to try to apply the trigger in the style. The problem I am currently having is that I can't seem to target the TextBlock (or any other child control)--what happens is which the code below is that the animation is applied to the background of the whole window.
If I leave off StoryBoard.Target completely, the effect is exactly the same.
Is this the right approach with the wrong syntax, or is there an easier way to accomplish this?
<Style x:Key="MyWindowStyle" TargetType="{x:Type Window}">
<Setter Property="Template" Value="{StaticResource MyWindowTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ChangeOccurred}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard BeginTime="00:00:00" Duration="0:0:2" Storyboard.Target="{Binding RelativeSource={RelativeSource AncestorType=TextBlock}}"
Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
<ColorAnimation FillBehavior="Stop" From="Black" To="Red" Duration="0:0:0.5" AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
Update
Should have also mentioned that I tried to name the TextBlock and reference it via StoryBoard.TargetName (as Timores suggested), and got the error "TargetName property cannot be set on a Style Setter."
EDIT: I have overseen the fact that the TextBlock is in the ControlTemplate of your custom Window/Control. I do not think that it is possible to target a control within the ControlTemplate from a Storyboard outside of this ControlTemplate. You could however define a property on your custom Window which you then databind to your ChangeOccurred property, and then add the trigger to your ControlTemplate which will now get triggered by the custom Control's property rather than the Window's ViewModel's property (of course, indirectly it is triggered by the ViewModel because ChangeOccurred is bound to the property of the custom Window which in turn triggers the animation - uh, complex sentence, hope you understand). Is this an option? Could you follow? ;-)
Maybe some code helps:
public class MyCustomWindow : Window
{
public static readonly DependencyProperty ChangeOccurred2 = DependencyProperty.Register(...);
public bool ChangeOccurred2 { ... }
// ...
}
And some XAML:
<local:MyCustomWindow ChangeOccurred2="{Binding ChangeOccurred}" ... >
<!-- Your content here... -->
</local:MyCustomWindow>
<!-- Somewhere else (whereever your ControlTemplate is defined) -->
<ControlTemplate TargetType="{x:Type local:MyCustomWindow}">
<!-- your template here -->
<ControlTemplate.Triggers>
<Trigger Property="ChangeOccurred2" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard BeginTime="00:00:00" Duration="0:0:2"
Storyboard.TargetName="txtWhatever"
Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
<ColorAnimation FillBehavior="Stop"
From="Black" To="Red"
Duration="0:0:0.5"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Note: I named the Window's property ChangeOccurred2 because I wanted it to be distinguishable from the ViewModel's ChangeOccurred property. Of course, you should choose a better name for this property. However, I am missing the background for such a decision.
My old answer:
So, you want to animate a TextBlock which is in the content of a (custom) Window?!
Why do you want to set the style on the Window, and not on the TextBlock itself? Maybe you should try something like this (did not test this!):
<local:MyCustomWindow ... >
<!-- ... -->
<TextBlock x:Name="textBlockAnimated" ... >
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ChangeOccurred}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard BeginTime="00:00:00" Duration="0:0:2"
Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
<ColorAnimation FillBehavior="Stop"
From="Black" To="Red"
Duration="0:0:0.5"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<!-- ... -->
</local:MyCustomWindow>
The {Binding ChangeOccurred} might not be sufficient. You might have to add a DataContext to the TextBlock, or add a RelativeSource or something.
Is the TextBlock in the MyWindowTemplate ?
If so, give the TextBlock a name and use Storyboard.TargetName to reference it.
See another question in SO
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.