I have a UserControl with a Border, the color of the border should be setted with a Dependency Property. I also want to animate the opacity of the border. My current xaml code looks like this:
<Border BorderBrush="{Binding ElementName=ImageViewerUserControl,
Path=NotificationColor}" BorderThickness="3" x:Name="AnimatedBorderBrush"
Visibility="{Binding ElementName=ImageViewerUserControl,
Path=ShowSequenceErrorNotification, Converter={StaticResource boolToVisibility}}">
<Border.Triggers>
<EventTrigger RoutedEvent="Border.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="AnimatedBorderBrush"
Storyboard.TargetProperty="BorderBrush.Opacity"
RepeatBehavior="Forever"
AutoReverse="True"
From="1"
To="0.0"
Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
</Border>
This only give the error:
Cannot resolve all property references in the property path 'BorderBrush.Opacity'. Verify that applicable objects support the properties.
But if I change to color of the BorderBrush to, lets say Black it works. How is this possible to achieve? I want to set the Brush color of my border via a dependency property. And yes, the dependency property is a Brush
I think the problem here is that the animation will only work if there is an object (the Brush) to animate. If you register your DependencyProperty without a default value it is null by default. Please try registering the DP with a default value
public static readonly DependencyProperty NotificationColorProperty = DependencyProperty.Register(
"NotificationColor",
typeof(Brush),
typeof(ImageViewerUserControl),
new PropertyMetadata(Brushes.Transparent)
);
Edit:
And as #Sheridan says use Storyboard.TargetProperty="Opacity" instead of Border.Opacity. Although it works if you specify a direct BorderBrush it doesn't worked for me with a bounded DP.
Your AnimatedBorderBrush name is misleading as it relates to a Border and not a BorderBrush. If you want to animate the Border.Opacity, then use Border.Opacity in the DoubleAnimation instead of BorderBrush.Opacity:
<DoubleAnimation Storyboard.TargetName="AnimatedBorderBrush"
Storyboard.TargetProperty="Border.Opacity"
RepeatBehavior="Forever"
AutoReverse="True"
From="1"
To="0.0"
Duration="0:0:1" />
UPDATE >>>
Ahhhhh, my bad... As the animation is defined inside the Border, there is no need to reference it, just use Opacity:
<DoubleAnimation Storyboard.TargetName="AnimatedBorderBrush"
Storyboard.TargetProperty="Opacity"
RepeatBehavior="Forever"
AutoReverse="True"
From="1"
To="0.0"
Duration="0:0:1" />
Related
I have a TextBlock that I would like to give it a color animation effect; I have done something like this:
<Window.Resources>
<Storyboard x:Key="AnimateTarget" RepeatBehavior="Forever">
<ColorAnimation AutoReverse="False" Duration="0:0:5" From="Red" To="black" Storyboard.TargetName="txtBarcode" AccelerationRatio="1" Storyboard.TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)" FillBehavior="HoldEnd">
</ColorAnimation>
</Storyboard>
</Window.Resources>
I start the animation from code behind:
((Storyboard)this.Resources["AnimateTarget"]).Begin();
but when i start the animation its give me the following error:
'Background' property does not point to a DependencyObject in path '(0).(1)'.
I would priciest if someone helps me on this,
Thanks,
Give your TextBlock any Background and it will work.
<TextBlock Name="txtBarcode"
Background="Transparent"
Probably Background is Null and so there is no DependencyObject to animate.
Here's my XAML, so far when someone enter any image in my Window, the animation pops up correctly.
<Style.Triggers>
<EventTrigger RoutedEvent="Image.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.25" From="1" To="1.2" AutoReverse="True"
Storyboard.TargetProperty="RenderTransform.ScaleX"/>
<DoubleAnimation Duration="0:0:0.1" From="1" To="1.2" AutoReverse="True"
Storyboard.TargetProperty="RenderTransform.ScaleY"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
Now I'd like to create another storyboard, one that fires on Image.MouseDown, however this time the animation should change the margin of a Grid called x:Name="container".
Can I access other controls properties inside a trigger? If so, how?
I can think of 2 options here:
Create an EventTrigger for your Grid and set its SourceName property to Image name that fires MouseDown or it`s parent;
Add another EventTrigger directly to the Image and set Storyboard.TargetName for animation to Grid`s name.
I have a Custom control in WPF in which I define a large ItemsControl Template.
In there, I have a Grid and in one column of that grid, I got a TextBlock and in another column I have a Border.
I want to highlight the Border when the mouse enters the TextBlock.
I tried several scenarios:
first an EventTrigger in the TextBlock's Style, but I learned that you can't do that, then an EventTrigger within the TextBlock's Triggers section, and now I just put it in the DataTemplate.Triggers of my ItemsControl, but I keep getting the error:
"Cannot resolve all property references in the property path 'Border.BorderBrush.Color'. Verify that applicable objects support the properties."
Here is the code that causes trouble:
<DataTemplate.Triggers>
<EventTrigger SourceName="mytxtblock" RoutedEvent="TextBlock.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="myborder"
Storyboard.TargetProperty="Border.BorderBrush.Color"
Duration="0:0:1"
To="White" />
<ThicknessAnimation Storyboard.TargetProperty="Border.BorderThickness"
Duration="0:0:1"
From="0"
To="1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</DataTemplate.Triggers>
I think I'm missing something about the way i refer to the Color property of my Border, any insight?
Thanks!
EDIT: I figured out that declaring a SolidColorBrush in Resources and then using that value allows me to get rid of the
Storyboard.TargetProperty="Border.BorderBrush.Color" that changes to Storyboard.TargetProperty="Border.BorderBrush",
but now the compiler tells me that the color i declared (i tried Green and Transparent) is not a valid value for "To"...
Try
<ColorAnimation
Storyboard.TargetName="myborder"
Storyboard.TargetProperty="BorderBrush.(SolidColorBrush.Color)"
Duration="0:0:1"
To="White" />
but you have to declare a BorderBrush
BorderBrush="whatever"
or
<Border.BorderBrush>
<SolidColorBrush Color="whatever" />
</Border.BorderBrush>
in your "myborder" too.
On your ColorAnimation there are two properties:
Storyboard.TargetName="myborder"
Storyboard.TargetProperty="Border.BorderBrush.Color"
It implies, that myborder has a property called Border. I think that causes your error.
I'm using a simple DoubleAnimation in Button.Trigger. Animation From is bound to root element ActualHeight. This works as expected.
Next I've tried to move the Storyboard to VisualStateManager. Now WPF complains:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=ActualWidth; DataItem=null; target element is 'DoubleAnimation' (HashCode=29201322); target property is 'To' (type 'Nullable`1')
Is it not possible to use bindings in animations within the VisualStateManager.VisualStateGroups/VisualState ?
It seems not. I fixed the problem by moving the storyboard to resources. So now GameToTitle will work while TitleToGame will fail.
I'd still appreciate to know if this is expected or not.
Relevant XAML:
<Grid x:Name="MainInterface">
<Grid.Resources>
<Storyboard x:Key="GameToTitle">
<DoubleAnimation Storyboard.TargetName="GameScreenInterface" Storyboard.TargetProperty="(Control.RenderTransform).(TransformGroup.Children)[1].(TranslateTransform.Y)" From="0" To="{Binding Path=ActualHeight, ElementName=MainInterface}" Duration="0:0:0.2"/>
<DoubleAnimation Storyboard.TargetName="TitleScreenInterface" Storyboard.TargetProperty="(Control.RenderTransform).(TransformGroup.Children)[1].(TranslateTransform.Y)" From="{Binding Path=ActualHeight, ElementName=MainInterface, Converter={Converters:InverseConverter}}" To="0" Duration="0:0:0.2"/>
</Storyboard>
</Grid.Resources>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="TitleScreenState" Storyboard="{StaticResource ResourceKey=GameToTitle}"/>
<VisualState x:Name="GameScreenState">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="GameScreenInterface" Storyboard.TargetProperty="(Control.RenderTransform).(TransformGroup.Children)[1].(TranslateTransform.Y)" From="{Binding Path=ActualHeight, ElementName=MainInterface}" To="0" Duration="0:0:0.2"/>
<DoubleAnimation Storyboard.TargetName="TitleScreenInterface" Storyboard.TargetProperty="(Control.RenderTransform).(TransformGroup.Children)[1].(TranslateTransform.Y)" From="0" To="{Binding Path=ActualHeight, ElementName=MainInterface, Converter={Converters:InverseConverter}}" Duration="0:0:0.2"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<View:TitleScreen x:Name="TitleScreenInterface"/>
<View:GameScreen x:Name="GameScreenInterface" />
</Grid>
As the question's comments note, Storyboards containing bindings must be declared as resources instead of inline within the VisualStateGroup for the bindings to work. This is due to the fact that VisualStates aren't part of the VisualTree. Resources, on the other hand, are.
You can visualize this for yourself by using Snoop (or similar utility) to look at the visual tree of your application. You'll notice that Resources nodes appear in the visual tree within Snoop, but VisualStates do not.
I am learning WPF animation, and I created a simple demo app with a pretty straightforward animation. I have divided the main grid into three rows; a Buttons Row at the top, and two content rows that for the remainder of the screen, one red and one blue. Complete XAML is below.
There are two buttons, Show Red and Show Blue. When each button is pressed, I want the area below the Buttons Row to change color with a slow top-to-bottom wipe. The Storyboard sets the height of both rows to 0, then animates the desired row to a height of 1*, like this:
<Storyboard>
<Utility:GridLengthAnimation Storyboard.TargetName="RedRow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0" />
<Utility:GridLengthAnimation Storyboard.TargetName="BlueRow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0" />
<Utility:GridLengthAnimation Storyboard.TargetName="BlueRow" Storyboard.TargetProperty="Height" From="0" To="1*" Duration="0:0:5" />
</Storyboard>
The colors change as expected, but there is no animation. So, my question is simple: Why isn't the animation working?
I am using a custom animation class, GridLengthAnimation (adapted from this CodeProject article) to animate the grid lengths. I have reproduced the class below.
To recreate the demo project: To recreate my demo project, create a new WPF project (I used VS 2010) and replace the XAML in MainWindow.xaml with the following:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Utility="clr-namespace:Utility" Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Name="Buttons" Height="35" />
<RowDefinition Name="RedRow" Height="0.5*" />
<RowDefinition Name="BlueRow" Height="0.5*" />
</Grid.RowDefinitions>
<!-- Buttons -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Show Red" Width="100" Margin="5" >
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<Utility:GridLengthAnimation Storyboard.TargetName="RedRow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0" />
<Utility:GridLengthAnimation Storyboard.TargetName="BlueRow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0" />
<Utility:GridLengthAnimation Storyboard.TargetName="RedRow" Storyboard.TargetProperty="Height" From="0" To="1*" Duration="0:0:5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
<Button Content="Show Blue" Width="100" Margin="5" >
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<Utility:GridLengthAnimation Storyboard.TargetName="RedRow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0" />
<Utility:GridLengthAnimation Storyboard.TargetName="BlueRow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0" />
<Utility:GridLengthAnimation Storyboard.TargetName="BlueRow" Storyboard.TargetProperty="Height" From="0" To="1*" Duration="0:0:5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
</StackPanel>
<!-- Grid Fills-->
<Border Grid.Row="1" Background="Red" />
<Border Grid.Row="2" Background="Blue" />
</Grid>
</Window>
There is no code-behind added to MainWindow.xaml.
Add a C# class to the project named GridLengthAnimation.cs. Replace the code in that class with the following:
using System;
using System.Windows.Media.Animation;
using System.Windows;
namespace Utility
{
/// <summary>
/// Enables animation of WPF Grid row heights and column widths.
/// </summary>
/// <remarks>Adapted from Graus & Sivakumar, "WPF Tutorial - Part 2 : Writing a custom animation class",
/// http://www.codeproject.com/KB/WPF/GridLengthAnimation.aspx, retrieved 08/12/2010.</remarks>
internal class GridLengthAnimation : AnimationTimeline
{
static GridLengthAnimation()
{
FromProperty = DependencyProperty.Register("From", typeof(GridLength),
typeof(GridLengthAnimation));
ToProperty = DependencyProperty.Register("To", typeof(GridLength),
typeof(GridLengthAnimation));
}
public override Type TargetPropertyType
{
get
{
return typeof(GridLength);
}
}
protected override Freezable CreateInstanceCore()
{
return new GridLengthAnimation();
}
public static readonly DependencyProperty FromProperty;
public GridLength From
{
get
{
return (GridLength)GetValue(FromProperty);
}
set
{
SetValue(FromProperty, value);
}
}
public static readonly DependencyProperty ToProperty;
public GridLength To
{
get
{
return (GridLength)GetValue(ToProperty);
}
set
{
SetValue(ToProperty, value);
}
}
public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
{
double fromVal = ((GridLength)GetValue(FromProperty)).Value;
double toVal = ((GridLength)GetValue(ToProperty)).Value;
if (animationClock.CurrentProgress != null)
{
if (fromVal > toVal)
{
return new GridLength((1 - animationClock.CurrentProgress.Value) * (fromVal - toVal) + toVal, GridUnitType.Star);
}
else
{
return new GridLength(animationClock.CurrentProgress.Value * (toVal - fromVal) + fromVal, GridUnitType.Star);
}
}
else
{
return null;
}
}
}
}
I found my answer in this blog post. It turns out there is a problem animating height or width properties. I worked around the problem by using a dissolve effect, instead of a wipe. To animate a dissolve, declare both controls in the same Grid row and column, which will load them on top of each other. Declare the default control last, which will make it the visible control. Then, animate the Opacity value of the default control to zero to hide it, and back to 1 to show it.
If the controls being animated are UserControls or other controls you need to click on, you need to take one more step. That's because setting the Opacity of a control to zero simply makes it invisible. It will still prevent a click on the control beneath it. So, declare a Render.Transform on the default control, then animate the ScaleY property to set it to 0 when invisible and 1 when showing.
Here is an example from the production app I am working on. It switches between a note list and a calendar (two different UserControls) in the Navigator pane of an Explorer-style interface. Here is the declaration of the two controls:
<!-- ClientArea: Navigator -->
<Grid x:Name="Navigator">
<View:CalendarNavigator x:Name="Calendar" />
<View:NoteListNavigator x:Name="NoteList">
<View:NoteListNavigator.RenderTransform>
<ScaleTransform ScaleX="1" ScaleY="1" />
</View:NoteListNavigator.RenderTransform>
</View:NoteListNavigator>
</Grid>
Note the declaration of the ScaleTransform on the note list. I use a couple of Ribbon buttons to switch between the two UserControls:
<ribbon:RibbonToggleButton x:Name="NoteListViewButton" LargeImageSource="..\Images\ListViewLarge.png" SmallImageSource="..\Images\ListViewSmall.png" Label="Note List" Click="OnViewButtonClick">
<ribbon:RibbonToggleButton.Triggers>
<EventTrigger RoutedEvent="ribbon:RibbonToggleButton.Checked">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="NoteList" Storyboard.TargetProperty="(View:NoteListNavigator.RenderTransform).(ScaleTransform.ScaleY)" To="1" Duration="0:0:0" />
<DoubleAnimation Storyboard.TargetName="NoteList" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</ribbon:RibbonToggleButton.Triggers>
</ribbon:RibbonToggleButton>
<ribbon:RibbonToggleButton x:Name="CalendarViewButton" LargeImageSource="..\Images\CalendarViewLarge.png" SmallImageSource="..\Images\CalendarViewSmall.png" Label="Calendar" Click="OnViewButtonClick">
<ribbon:RibbonToggleButton.Triggers>
<EventTrigger RoutedEvent="ribbon:RibbonToggleButton.Checked">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="NoteList" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:1" />
<DoubleAnimation Storyboard.TargetName="NoteList" Storyboard.TargetProperty="(View:NoteListNavigator.RenderTransform).(ScaleTransform.ScaleY)" To="0" Duration="0:0:0" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</ribbon:RibbonToggleButton.Triggers>
</ribbon:RibbonToggleButton>
The ScaleY transforms get the invisible note list out of the way when the Calendar is showing so that I can click on my calendar controls. Note that I needed fully-qualified references to the ScaleY properties in my Storyboards. That's why the references are enclosed in parentheses.
Hope that helps someone else down the road! It's likely to be me, since I'll probably forget how I did this...