This question already has an answer here:
: 'Cannot animate 'Fill.Color' on an immutable object instance.'
(1 answer)
Closed 2 years ago.
I am new to WPF, I Have a rectangle that I try to animate to flesh red color if some boolean is true. and I want it to stop when the boolean is false. In order to do that, I used the <DataTrigger.ExitActions>
However, I still want my Fill color to change according to the AlertColor, but after the animation is stopped it seems like the binding is stoped also, and the background-color stays LightPink only.
why? how can I fix this and is there a better approach to animate the color only in case of a specific color and stop the animation when the color changes (with Binding)?
The XAML relevant code:
<Rectangle Width="840" Height="40">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill">
<Setter.Value>
<SolidColorBrush Color="{Binding AlertUnit.AlertColor , FallbackValue=LightPink}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding AlertUnit.Emergency}" Value="true" >
<DataTrigger.EnterActions>
<BeginStoryboard Name="FlashingRedAnimation">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Fill.Color" To="White" Duration="0:0:1" AutoReverse="True"
RepeatBehavior="Forever"></ColorAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="FlashingRedAnimation" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
edit:
I found the problem it was a really silly one. the binding should be:
Binding AlertUnit.AlertColor.Color
not:
Binding AlertUnit.AlertColor
Thank you all for the help.
I produced a similar visual effect by animating the opacitity value of a brush rather than the colour.
<Style TargetType="{x:Type vctrl:perBlinkingBorder}">
<Style.Triggers>
<Trigger Property="IsBlinking" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard TargetProperty="(BlinkingBorderBrush).Opacity">
<DoubleAnimation
AutoReverse="True"
RepeatBehavior="Forever"
From="1"
To="0"
Duration="0:0:0.5">
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
To ensure that the visual effect is removed, I have two brush properties on the perBlinkingBorder control, and switch between them when the IsBlinking property is set.
More details and a demo project on my recent blog post.
I found the problem it was a really silly one.
the binding should be:
Binding AlertUnit.AlertColor.Color
not:
Binding AlertUnit.AlertColor
Thank you all for the help.
My Application will download data from the web, whenever the user will trigger. The user can trigger multiple downloads.
Each download is listed in a stack panel an a busy icon is shown on each item which is rotating while downloading.
<Image x:Name="rotatingCircle" Source="{StaticResource busy_icon}" Height="30" RenderTransformOrigin=".5,.5" Visibility="Visible">
<Image.RenderTransform>
<RotateTransform x:Name="AnimatedRotateTransform" Angle="0" />
</Image.RenderTransform>
<Image.Style>
<Style>
<Style.Triggers>
<Trigger Property="Image.IsEnabled" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.Angle"
From="0"
To="360"
Duration="0:0:1"
RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
How can I synchronise the animation, so that every circle is rotating with the same angle, regardless when it was started?
In my case it was enough to store Storyboards in a list. Every time when a animated icon was added I simply iterate through all Storyboards and started them again (sb.Begin()).
There is a small flicker, because every element is starting at the initial position, but they are running absolut synchronously.
I would like to alter my program so that when an image is clicked it reveals a sidebar on the far right side of the application.
I have currently been trying to achieve this using an Expander. This is what it looks like so far:
<Expander>
<Expander.Header>
<Image Width="200" Height="300" Source="{Binding image}"/>
</Expander.Header>
<i:Interaction.Behaviors>
<local:UniqueNameBehavior ID="{Binding id}"/>
</i:Interaction.Behaviors>
<StackPanel Margin="10,4,0,0">
<ToggleButton Margin="4" Content="Option 1" Template="{StaticResource SimpleExpanderButtonTemp}"/>
<ListView>
<TextBox Text="Search" />
</ListView>
</StackPanel>
</Expander>
The idea is that whenever any movie is clicked, a sidebar on the far right side of the app is revealed containing info about the movie. If another movie is subsequently clicked, the sidebar will now contain info about the new movie.
I know that it is possible to change the ExpandDirection, but this only results in the content appearing beside the image as opposed to the far right of the application.
I've also tried adjusting the Margin of StackPanel but this results in the surrounding movies being pushed aside.
Could someone please help me out with how I can achieve this?
Thank you for your help!
I don't believe this kind of behaviour can be achieved using expanders the way you are using.
Why don't you create a grid on the far right side of your app with its width set to 0 and when you click/select a movie it sets the grid width to a new value and change its content based on the movie?
<Grid HorizontalAlignment="Right">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Width" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowMovieForm}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.1" Storyboard.TargetProperty="Width" To="400" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.1" Storyboard.TargetProperty="Width" To="0" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
I have a problem with color animation. This is my source:
<Window.Resources>
<hedit:BrushToColorConverter x:Key="BrushToColorConverter" />
<Style x:Key="MyButtonStyle" TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ControlTemplate.Resources>
<Storyboard x:Key="buttonAnimIn">
<!-- Problem line -->
<ColorAnimation Storyboard.TargetName="bntBack" Storyboard.TargetProperty="Color" To="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource BrushToColorConverter}}" />
</Storyboard>
<Storyboard x:Key="buttonAnimOut">
<ColorAnimation Storyboard.TargetName="bntBack" Storyboard.TargetProperty="Color" To="Blue" />
</Storyboard>
<Storyboard x:Key="buttonAnimForegroundIn">
<ColorAnimation Storyboard.TargetName="btnFore" Storyboard.TargetProperty="Color" To="Blue" />
</Storyboard>
<Storyboard x:Key="buttonAnimForegroundOut">
<ColorAnimation Storyboard.TargetName="btnFore" Storyboard.TargetProperty="Color" To="Red" />
</Storyboard>
</ControlTemplate.Resources>
<Border Name="border"
BorderThickness="1"
Padding="4,2"
BorderBrush="DarkGray"
CornerRadius="3">
<Border.Background>
<SolidColorBrush Color="Blue" x:Name="bntBack" />
</Border.Background>
<ContentControl HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding Content}">
<ContentControl.Foreground>
<SolidColorBrush Color="Red" x:Name="btnFore" />
</ContentControl.Foreground>
</ContentControl >
</Border>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Button.MouseEnter">
<BeginStoryboard Storyboard="{StaticResource buttonAnimIn}" />
<BeginStoryboard Storyboard="{StaticResource buttonAnimForegroundIn}" />
</EventTrigger>
<EventTrigger RoutedEvent="Button.MouseLeave">
<BeginStoryboard Storyboard="{StaticResource buttonAnimOut}" />
<BeginStoryboard Storyboard="{StaticResource buttonAnimForegroundOut}" />
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
The problem is:
Cannot convert the value in attribute 'Style' to object of type 'System.Windows.Style'. Cannot freeze this Storyboard timeline tree for use across threads. Error at object 'System.Windows.Controls.Button' in markup file 'HLSLEditor;component/mainwindow.xaml' Line 223 Position 25.
When using fixed colors it worked, but it cannot work with the Foreground color of the parent...
How do I do an animation to the foreground or background color?
Thanks!
You cannot freeze Bindings, you probably can get around this issue by declaring a color as a resource and then bind your Control's Background to it while using StaticResource in the animation.
e.g.
<Window.Background>
<SolidColorBrush Color="{DynamicResource Background}"/>
</Window.Background>
<Window.Resources>
<Color x:Key="Background">Green</Color>
</Window.Resources>
<ColorAnimation Storyboard.TargetProperty="Foreground.Color"
Duration="0:0:1"
To="{StaticResource Background}"/>
Alternative using a resource class:
public static class MyColors
{
public static Color MyHighlightColor = Color.FromArgb(255, 0, 88, 0);
}
<ColorAnimation Storyboard.TargetProperty="Foreground.Color"
Duration="0:0:1"
To="{x:Static local:MyColors.MyHighlightColor}"/>
I think that understanding the error might give you a way of fixing the problem.
Animation requires the use of threads besides the UI thread. So storyboards have to be freezable, which means that all the animations in the storyboard must be freezable, and everything those animations use must also be freezable.
Bindings aren't freezable - pretty much by definition, as they are a mechanism whereby a dependency property can be changed. You can't use a dynamic binding in a color animation - there's the possibility that the property could change while the animation was running. The same thing happens whether you're binding to an object or you're using DynamicResource.
The thing is, this is protecting you from something that you don't really want anyway. You don't really want the colors to change while the animation is running. That's not what you're trying to accomplish. You want the color resources that the animation is using to change if the user selects a different skin.
So instead of binding storyboards to skinnable resources, add the storyboards to the dictionary of resources that get set when the skin changes (using static bindings to set the colors), and use dynamic binding in your event triggers. That should work.
When I came across this problem I worked around it by modifying my style to contain two identical elements on top of each other - one for the 'normal' state and one for the 'pressed' state. The 'pressed' one had its Opacity set to 0 by default and the other one had an Opacity of 1. My animation changed the opacities from 0 to 1 and vice versa.
This approach avoided actually animating the Color property but produced the same effect whilst keeping everything in XAML. As the colours were set in the style definition rather than the animation they could be bound as required. This will probably not be suitable for all situations but for my fairly simple style it was a very quick way to achieve the desired effect.
In my WPF app, I have a feedback control that I want to appear after a user action completes (save data, delete...). The visibility is set to Hidden to begin and style set to the animateFadeOut style defined as a resource (see below). Then I want to set the text and control Visibility to visible in my C# code and have the feedback control display the message and fade out after 5 seconds and remain hidden (Visibility.Hidden).
The following XAML works the first time I call control.Visiblity= Visibility.Visible but the control doesn't reappear the second time. I figure that is because the animation is still running, which has control over the feedback control. I then tried to set FillBehavior to "Stop" but that just made the control visible again and I want it hidden. Then, with FillBehavior="Stop", I tried to set a trigger "when Opacity = 0, set the Visibility to Hidden". The trigger didn't seem to fire and I was left with the visible control once more after the animation completed.
Please help point out what I am doing wrong here.
Alternatively, if you can suggest a better way to display a control that fades after 5 seconds and can be called over and over, I would appreciate it.
Thanks!
<Style TargetType="{x:Type FrameworkElement}" x:Key="animateFadeOut">
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard >
<Storyboard>
<DoubleAnimation BeginTime="0:0:5.0" Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:0.5"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
The problem is that after your animation completes your control still has Visibility=Visible, so it cannot be entered again.
I would rather use animation that does the whole thing, first shows the control, then hides it.
<Storyboard x:Key="animate">
<ObjectAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation BeginTime="0:0:0.0" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:0.2"/>
<DoubleAnimation BeginTime="0:0:5.0" Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:0.5"/>
<ObjectAnimationUsingKeyFrames BeginTime="0:0:5.5" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Hidden</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
And use it as follows:
((Storyboard)FindResource("animate")).Begin(someControl);
Liz is correct about Visibility still being Visible. alpha-mouse is also correct that you need to set it back to Hidden at some point. But it won't work if you set it back before the animation is completed like this:
MyControl.Visibility = System.Windows.Visibility.Visible;
MyControl.Visibility = System.Windows.Visibility.Hidden;
because animations take higher precedence (MSDN)
You can set it back to Hidden in Storyboard.Completed event:
private void Show()
{
MyControl.Visibility = System.Windows.Visibility.Visible;
var a = new DoubleAnimation
{
From = 1.0,
To = 0.0,
FillBehavior= FillBehavior.Stop,
BeginTime = TimeSpan.FromSeconds(2),
Duration = new Duration(TimeSpan.FromSeconds(0.5))
};
var storyboard = new Storyboard();
storyboard.Children.Add(a);
Storyboard.SetTarget(a, MyControl);
Storyboard.SetTargetProperty(a, new PropertyPath(OpacityProperty));
storyboard.Completed += delegate { MyControl.Visibility = System.Windows.Visibility.Hidden; };
storyboard.Begin();
}
Here is my work around. This fades a control in and back out again. Instead of playing around with the Visibility, I handled it by playing only with the Opacity.
Thanks to Kane from this post for the orginal code: Fade any control using a WPF animation
Storyboard storyboard = new Storyboard();
TimeSpan duration = TimeSpan.FromMilliseconds(500); //
DoubleAnimation fadeInAnimation = new DoubleAnimation()
{ From = 0.0, To = 1.0, Duration = new Duration(duration) };
DoubleAnimation fadeOutAnimation = new DoubleAnimation()
{ From = 1.0, To = 0.0, Duration = new Duration(duration) };
fadeOutAnimation.BeginTime = TimeSpan.FromSeconds(5);
Storyboard.SetTargetName(fadeInAnimation, element.Name);
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath("Opacity", 1));
storyboard.Children.Add(fadeInAnimation);
storyboard.Begin(element);
Storyboard.SetTargetName(fadeOutAnimation, element.Name);
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath("Opacity", 0));
storyboard.Children.Add(fadeOutAnimation);
storyboard.Begin(element);
My God that took forever. Take a look at this, it solves that problem of animating upon Visibility changes to 'Visible' and 'Hidden' using alpha and the animation will not freeze.
using System.Windows;
namespace WpfApplication4
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
button.Visibility = Visibility.Hidden;
}
private void button2_Click(object sender, RoutedEventArgs e)
{
button.Visibility = Visibility.Visible;
}
}
}
XAML:
<Window x:Class="WpfApplication4.MainWindow"
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"
xmlns:local="clr-namespace:WpfApplication4"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<Style BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type Button}">
<Style.Resources>
<Storyboard x:Key="FadeOut">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" FillBehavior="Stop">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="0:0:1" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:1" AutoReverse="False" />
</Storyboard>
<Storyboard x:Key="FadeIn">
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:1" AutoReverse="False" />
</Storyboard>
</Style.Resources>
<Setter Property="Width" Value="120"></Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Visibility" Value="Hidden" />
<Condition Property="Opacity" Value="1" />
</MultiTrigger.Conditions>
<MultiTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="FadeInStoryboard" />
<BeginStoryboard Name="FadeOutStoryboard" Storyboard="{StaticResource FadeOut}" />
</MultiTrigger.EnterActions>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Visibility" Value="Visible" />
<Condition Property="Opacity" Value="0" />
</MultiTrigger.Conditions>
<MultiTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="FadeOutStoryboard" />
<BeginStoryboard Name="FadeInStoryboard" Storyboard="{StaticResource FadeIn}" />
</MultiTrigger.EnterActions>
</MultiTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="200,186,0,0" VerticalAlignment="Top" Width="75" Height="38" />
<Button x:Name="button1" Content="Hide it" HorizontalAlignment="Left" Margin="112,96,0,0" VerticalAlignment="Top" Width="75" Click="button1_Click"/>
<Button x:Name="button2" Content="Show it" HorizontalAlignment="Left" Margin="200,96,0,0" VerticalAlignment="Top" Width="75" Click="button2_Click"/>
<Label x:Name="label" Content="{Binding ElementName=button, Path=Opacity}" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<Label x:Name="label1" Content="{Binding ElementName=button, Path=Visibility}" HorizontalAlignment="Left" Margin="10,36,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>
All the answers above use at least some sort of XAML code, which I personally am not that great of a fan of (because it's very confusing), so I found a way to do the same with some simple C# code:
int secs = 2; // How long the fade should take in seconds
for (int i = 99; i >= 0; i--)
{
someControl.Opacity = i / 100d;
await Task.Delay(secs * 10);
}
someControl.Visibility = Visibility.Hidden;
someControl.Opacity = 1;
You can use this on any Control. You also need to add the async modifier in your method signature. Without the await operator your UI couldn't respond while it's fading out the control.
After the control faded out, you can make it visible again like this:
someControl.Visibility = Visibility.Visible;
This method may not be the "best", but it is certainly the simplest and easiest to understand.
This should fix your storyboard.
However, remember that once the animation is complete, your control is completely opaque - invisible, but your Visibility property is still set to Visible. So you'll have to make sure that the Visibility property is reset to hidden or collapsed somewhere too.
<Style TargetType="{x:Type FrameworkElement}" x:Key="animateFadeOut">
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard Name="MyFadeEffect">
<Storyboard>
<DoubleAnimation BeginTime="0:0:5.0" Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:0.5"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="MyFadeEffect"/>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>