so I have a contentcontrol that has a routedevent:
public class TestBlind : ContentControl
{
public static readonly RoutedEvent VisibilityVisibleEvent =
EventManager.RegisterRoutedEvent("VisibilityVisible", RoutingStrategy.Tunnel, typeof(Visibility), typeof(TestBlind));
public event RoutedEventHandler VisibilityVisible
{
add { AddHandler(VisibilityVisibleEvent, value); }
remove { RemoveHandler(VisibilityVisibleEvent, value); }
}
[Category("TestBlind")]
public bool IsContentVisible
{
get { return (bool)GetValue(IsContentVisibleProperty); }
set { SetValue(IsContentVisibleProperty, value); }
}
public static readonly DependencyProperty IsContentVisibleProperty = DependencyProperty.Register("IsContentVisible", typeof(bool), typeof(TestBlind),
new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsOverlayContentVisibleChanged)));
private static void OnIsOverlayContentVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TestBlind blind = d as TestBlind;
if (blind != null)
SetVisibility(blind);
}
private static void SetVisibility(TestBlind blind)
{
blind.Visibility = blind.IsContentVisible ? Visibility.Visible : Visibility.Hidden;
blind.RaiseEvent(blind.Visibility == Visibility.Visible ? new RoutedEventArgs(VisibilityVisibleEvent) : new RoutedEventArgs(VisibilityHiddenEvent));
}
}
this event is fired when a dependency property is changed. What I want is to be able to fire an animation when the event fires.
In my resource file for the control I have the following exert that (I thought) would call see the event and start the animation:
<Grid.Triggers>
<EventTrigger RoutedEvent="control:TestBlind.VisibilityVisible">
<!-- <EventTrigger RoutedEvent="ContentControl.Loaded">-->
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Duration="00:00:02.25" BeginTime="00:00:00" Storyboard.TargetName="backdDropGlow" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="00:00:00.00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.25" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
The animation will work if I use the ContentControl.Loaded (only the first time the control's property is changed though), but if I try and register it for my event nothing happens.
Is this possible, am I going about it completely wrong? I hope this made sense.
Thanks
Why not trigger your animation when the DependnecyProperty changes instead of using an Event?
I have done this in the past by attaching an event to the DependencyPropertyDescriptor in the Constructor
public TestBlind()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TestBlind.VisibilityVisible, typeof(TestBlind));
if (dpd != null) dpd.AddValueChanged(this, delegate { IsVisibilityVisibleChanged(); });
}
private void IsVisibilityVisibleChanged()
{
bool isShown = GetVisibilityVisible(this);
if (isShown)
{
Storyboard animation = (Storyboard)this.FindResource("MyStoryboard");
animation.Begin();
}
}
Related
The following code is counting up starting from 1 to forever by using Timer.
XAML codes:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Label x:Name="myLabel"/>
</Grid>
</Window>
vb.net codes:
Class MainWindow
Dim myDispatcherTimer As New Windows.Threading.DispatcherTimer With {.Interval = TimeSpan.FromSeconds(1)}
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
AddHandler myDispatcherTimer.Tick, AddressOf Me.Hello
myDispatcherTimer.Start()
End Sub
Public Sub Hello()
Static myStatic As Integer = 0
myStatic = myStatic + 1
myLabel.Content = myStatic
End Sub
End Class
I want to count up by using Double Animation instead of by using Timer.
Is it possible?
Thanks in advance.
You may create an attached property of type double that sets the Content property of any ContentControl (e.g. a Label) to which it is applied.
public static class Counter
{
public static readonly DependencyProperty CountProperty =
DependencyProperty.RegisterAttached(
"Count", typeof(double), typeof(Counter),
new PropertyMetadata(0d, CountPropertyChanged));
public static double GetCount(DependencyObject obj)
{
return (double)obj.GetValue(CountProperty);
}
public static void SetCount(DependencyObject obj, double value)
{
obj.SetValue(CountProperty, value);
}
private static void CountPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is ContentControl control)
{
control.Content = string.Format("{0:F0}", args.NewValue);
}
}
}
Then animate that property by an appropriate DoubleAnimation.
<Label>
<Label.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(local:Counter.Count)"
From="0" By="1" Duration="0:0:1"
IsCumulative="True" RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Label.Triggers>
</Label>
Since you apparently only want to show integer values, you may as well use an Int32Animation with an attached property of type int.
public static class Counter
{
public static readonly DependencyProperty CountProperty =
DependencyProperty.RegisterAttached(
"Count", typeof(int), typeof(Counter),
new PropertyMetadata(0, CountPropertyChanged));
public static int GetCount(DependencyObject obj)
{
return (int)obj.GetValue(CountProperty);
}
public static void SetCount(DependencyObject obj, int value)
{
obj.SetValue(CountProperty, value);
}
private static void CountPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is ContentControl control)
{
control.Content = args.NewValue;
}
}
}
with
<Storyboard>
<Int32Animation
Storyboard.TargetProperty="(local:Counter.Count)"
From="0" By="1" Duration="0:0:1"
IsCumulative="True" RepeatBehavior="Forever"/>
</Storyboard>
I have the following in a ControlTemplate.Resources:
<ColorAnimation
Storyboard.TargetName="border"
Storyboard.TargetProperty="Background.(SolidColorBrush.Color)"
To="Orange"
Duration="0:0:0.2" />
It works all right if the original background that I wanted to change to orange was a solid color. But I'd also want to have this work when the original background is a LinearGradientBrush. In this second case, the animation tries to change the property in vain, nothing happens.
How can I specify an animation that replaces the background no matter what type it was earlier?
If your Background is LinearGradientBrush, then you will have to animate each GradientStop to the Color you want i.e. Orange in this case:
<Storyboard x:Key="Storyboard1">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="Background.(GradientBrush.GradientStops)[0].(GradientStop.Color)" Storyboard.TargetName="border">
<EasingColorKeyFrame KeyTime="0:0:2" Value="Orange"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="Background.(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="border">
<EasingColorKeyFrame KeyTime="0:0:2" Value="Orange"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
But if you want to Animate the whole Brush irrespective of its type then you will have to create your own Animation. I have created my own BrushAnimation class to animate the Brush
public class BrushAnimation : AnimationTimeline
{
static BrushAnimation()
{
FromProperty = DependencyProperty.Register("From", typeof(Brush),
typeof(BrushAnimation),new PropertyMetadata(new SolidColorBrush()));
ToProperty = DependencyProperty.Register("To", typeof(Brush),
typeof(BrushAnimation), new PropertyMetadata(new SolidColorBrush()));
}
public override Type TargetPropertyType
{
get
{
return typeof(Brush);
}
}
protected override System.Windows.Freezable CreateInstanceCore()
{
return new BrushAnimation();
}
public static readonly DependencyProperty FromProperty;
public Brush From
{
get
{
return (Brush)GetValue(BrushAnimation.FromProperty);
}
set
{
SetValue(BrushAnimation.FromProperty, value);
}
}
public static readonly DependencyProperty ToProperty;
public Brush To
{
get
{
return (Brush)GetValue(BrushAnimation.ToProperty);
}
set
{
SetValue(BrushAnimation.ToProperty, value);
}
}
public override object GetCurrentValue(object defaultOriginValue,
object defaultDestinationValue, AnimationClock animationClock)
{
Brush fromVal = ((Brush)GetValue(BrushAnimation.FromProperty));
Brush toVal = ((Brush)GetValue(BrushAnimation.ToProperty));
SolidColorBrush solid = toVal as SolidColorBrush;
if(fromVal is LinearGradientBrush)
{
LinearGradientBrush brush = fromVal as LinearGradientBrush;
LinearGradientBrush newBrush = new LinearGradientBrush();
foreach(var stop in brush.GradientStops)
{
ColorAnimation animation = new ColorAnimation(stop.Color,solid.Color,this.Duration);
Color color = animation.GetCurrentValue(stop.Color, solid.Color, animationClock);
newBrush.GradientStops.Add(new GradientStop(color,stop.Offset));
}
return newBrush;
}
else
{
SolidColorBrush brush = fromVal as SolidColorBrush;
SolidColorBrush newsolid = new SolidColorBrush();
ColorAnimation solidAnimation = new ColorAnimation(brush.Color, solid.Color, this.Duration);
newsolid.Color = solidAnimation.GetCurrentValue(brush.Color, solid.Color, animationClock);
return newsolid;
}
}
and I am using this Animation to animate Canvas.Background on my window
<Storyboard x:Key="MyStoryBoard" RepeatBehavior="Forever" AutoReverse="True">
<local:BrushAnimation Storyboard.TargetName="Canvas1"
Storyboard.TargetProperty = "(Canvas.Background)"
To="Orange" Duration="0:0:5"/>
</Storyboard>
and you can set From property of animation using the StaticResource or set it to the Background of Control in your codebehind like:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
((BrushAnimation) ((Storyboard) Resources["SolidStoryBoard"]).Children[0]).From = Canvas1.Background;
}
}
I created a custom user control that has a property of type Storyboard. Something like:
public class UC : UserControl
{
public static readonly DependencyProperty AnimationProperty = DependencyProperty.Register(
"Animation",
typeof(Storyboard),
typeof(UC),
null);
public Storyboard Animation
{
get { return (Storyboard)GetValue(AnimationProperty); }
set { SetValue(AnimationProperty, value); }
}
public UC()
{
this.Loaded += new RoutedEventHandler(UC_Loaded);
}
private void UC_Loaded(object sender, RoutedEventArgs e)
{
if (this.Animation != null)
{
this.Animation.Begin();
}
}
}
In XAML I used it as follows:
<loc:UC x:Name="uc" Opacity="0" >
<TextBlock FontSize="50">Some text</TextBlock>
<loc:UC.Animation>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="uc" Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:1" />
</Storyboard>
</loc:UC.Animation>
</loc:UC>
So far so good.
Later on I decided that I needed another item along with the storyboard. So I changed the code to accept a custom object that contains the storyboard and another piece of information. Something like:
public class UC : UserControl
{
public static readonly DependencyProperty AnimationProperty = DependencyProperty.Register(
"Animation",
typeof(AnimationHolder),
typeof(UC),
null);
public AnimationHolder Animation
{
get { return (AnimationHolder)GetValue(AnimationProperty); }
set { SetValue(AnimationProperty, value); }
}
public UC()
{
this.Loaded += new RoutedEventHandler(UC_Loaded);
}
private void UC_Loaded(object sender, RoutedEventArgs e)
{
if (this.Animation != null)
{
this.Animation.Animation.Begin();
}
}
}
public class AnimationHolder
{
public Storyboard Animation
{
get;
set;
}
public int OtherValue
{
get;
set;
}
}
And used it in XAML:
<loc:UC x:Name="uc" Opacity="0" >
<TextBlock FontSize="50">Some text</TextBlock>
<loc:UC.Animation>
<loc:AnimationHolder OtherValue="20">
<loc:AnimationHolder.Animation>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="uc" Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:0" />
</Storyboard>
</loc:AnimationHolder.Animation>
</loc:AnimationHolder>
</loc:UC.Animation>
</loc:UC>
However, now when I try to begin the animation I get an InvalidOperationException with the message: "Cannot resolve TargetName uc."
Anyone has an idea why?
I know that in this example I can work around the problem by not using a custom object, and using another property instead. However this is a simplified version of my scenario, which focuses at the problem. In the real scenario I must use a custom object.
Two things need to be done with your new approach:
In AnimationHolder class, make Animation property a dependency property, just like it previously was.
Derive AnimationHolder class from DependencyObject. This is necessary so that you can make Animation a dependency property. Please remember that only classes deriving from DependencyObject can define dependency properties!
I think once you do these two things, it'll solve your problem!
I've been struggling with this for hours and I can't find out what I'm doing wrong. Please help me find my mistake.
I created a user control with one custom dependency property and I want to animate this property.
Here is my class:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public long Val
{
get { return (long)GetValue(ValProperty); }
set {
SetValue(ValProperty, value);
// Update a text block for debug
((Class1)this.Resources["class1"]).Val = value;
}
}
public static readonly DependencyProperty ValProperty =
DependencyProperty.Register("Val", typeof(long), typeof(UserControl1), new UIPropertyMetadata(0L));
}
Here is the code that is supposed to animate it (there's an instance of UserControl1 called usercontrol11):
Int64Animation myAnimation = new Int64Animation();
myAnimation.From = 100;
myAnimation.To = 200;
myAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTargetName(myAnimation, userControl11.Name);
Storyboard.SetTargetProperty(myAnimation, new PropertyPath(UserControl1.ValProperty));
Storyboard myStoryboard = new Storyboard();
myStoryboard.Children.Add(myAnimation);
myStoryboard.Begin(this);
I also tried the XAML approach, but it didn't work either (in the following XAML, the Width animation works fine, but the Val doesn't):
<my:UserControl1 HorizontalAlignment="Left" Width="150" Margin="72,45,0,0" x:Name="userControl11" VerticalAlignment="Top" Background="#FFFFD100">
<my:UserControl1.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<Int64Animation To="300" Duration="0:0:1"
Storyboard.TargetProperty="Val" />
<DoubleAnimation To="300" Duration="0:0:1"
Storyboard.TargetProperty="Width" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</my:UserControl1.Triggers>
</my:UserControl1>
Any help will be much appreciated!
Well, it's actually animated. You're just checking in the wrong place, animation won't call Val property set accessor. You can, for example, override OnPropertyChanged method to see changes:
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if(e.Property == ValProperty)
{
MessageBox.Show(e.NewValue.ToString());
}
}
I am looking for a clean way to start an animation that will have dynamic values. Basically I want to do an animation where an element changes width based on the data of another element. Say I have a TextBlock that's Text Property is Binding. When this property changes I want a visual element say a Rectangle for our sake to do a DoubleAnimation changing the width from previous value to the new value.
I am trying to stay away from putting code in my view if possible. I've looked into DataTriggers but they seem to require that you know what the value would be such as an Enum. In my case it is just the value changing that needs to trigger a storyboard and the animation would need to start at the current(previous) value and move nicely to the new value.
Any ideas. Maybe I just missed a property.
Here is the solution I ended up with. To do the Animation based on data in my ViewModel I used a DataTrigger. Below is my Style for the control.
<Style TargetType="Grid" x:Key="DetailRotation" >
<Style.Triggers>
<DataTrigger Binding="{Binding Path=AnimationState}" Value="New">
<DataTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="EndAnimation" />
<BeginStoryboard Name="NewAnimation">
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,30,0,0" To="0,0,0,0" Duration="0:0:1" />
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
</DataTrigger.ExitActions>
</DataTrigger>
<DataTrigger Binding="{Binding Path=AnimationState}" Value="End">
<DataTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="NewAnimation" />
<BeginStoryboard Name="EndAnimation">
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,0,0,0" To="0,-20,0,0" Duration="0:0:1"/>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
You could explore using Attached Properties to hook up the necessary logic to the Storyboard/Animation that you desire.
This won't necessarily stop you from having to write code, but it will keep it separated from the view and allow it to be re-used across multiple views.
Actually you want to bind the DoubleAnimation.ToProperty to the ViewModel property and animate actual control. The problem is animation should be continued when ToProperty changed. My solution encapsulate all this logic to a MarkupExtenstion which wraps a Binding.
public class AnimateBindingExtension : MarkupExtension {
static DependencyPropertyDescriptor dpd =
DependencyPropertyDescriptor.FromProperty(DoubleAnimation.ToProperty,
typeof(DoubleAnimation));
public AnimateBindingExtension(PropertyPath path) {
Path = path;
}
public bool ValidatesOnExceptions { get; set; }
public IValueConverter Converter { get; set; }
public object ConverterParamter { get; set; }
public string ElementName { get; set; }
public RelativeSource RelativeSource { get; set; }
public object Source { get; set; }
public bool ValidatesOnDataErrors { get; set; }
[ConstructorArgument("path")]
public PropertyPath Path { get; set; }
public object TargetNullValue { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider) {
var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (valueProvider == null) {
throw new Exception("could not get IProviderValueTarget service.");
}
var bindingTarget = valueProvider.TargetObject as FrameworkElement;
var bindingProperty = valueProvider.TargetProperty as DependencyProperty;
if (bindingProperty == null || bindingTarget == null) {
throw new Exception();
}
var binding = new Binding {
Path = Path,
Converter = Converter,
ConverterParameter = ConverterParamter,
ValidatesOnDataErrors = ValidatesOnDataErrors,
ValidatesOnExceptions = ValidatesOnExceptions,
TargetNullValue = TargetNullValue
};
if (ElementName != null) binding.ElementName = ElementName;
else if (RelativeSource != null) binding.RelativeSource = RelativeSource;
else if (Source != null) binding.Source = Source;
// you can add a Duration property to this class and use it here
var anim = new DoubleAnimation {
Duration = new Duration(TimeSpan.FromSeconds(0.1)),
AccelerationRatio = 0.2,
DecelerationRatio = 0.8
};
// this can be a new subclass of DoubleAnimation that
// overrides ToProperty metadata and add a property
// change callback
dpd.AddValueChanged(anim, (s, e) => bindingTarget.BeginAnimation(bindingProperty, anim));
BindingOperations.SetBinding(anim, DoubleAnimation.ToProperty, binding);
// this is because we need to catch the DataContext so add animation object
// to the visual tree by adding it to target object's resources.
bindingTarget.Resources[bindingProperty.Name] = anim;
// animation will set the value
return DependencyProperty.UnsetValue;
}
}
You can do the same with other animation classes to animate other types.
Since properties modified by animation cannot be set outside the animation 'context', I came up with a code solution since I could not do the same in XAML effectively.
private void UserControl_IsVisibleChanged(object sender,
DependencyPropertyChangedEventArgs e)
{
if (this.Visibility == Visibility.Visible)
{
DoubleAnimation fadeIn = new DoubleAnimation();
fadeIn.From = 1d;
fadeIn.To = 1d;
fadeIn.Duration = new Duration(new TimeSpan(0, 0, 0));
DoubleAnimation fade = new DoubleAnimation();
fade.From = 1d;
fade.To = 0d;
fade.BeginTime = TimeSpan.FromSeconds(5);
fade.Duration = new Duration(new TimeSpan(0, 0, 1));
NameScope.SetNameScope(this, new NameScope());
this.RegisterName(this.Name, this);
Storyboard.SetTargetName(fadeIn, this.Name);
Storyboard.SetTargetProperty(fadeIn, new PropertyPath
(UIElement.OpacityProperty));
Storyboard.SetTargetName(fade, this.Name);
Storyboard.SetTargetProperty(fade, new PropertyPath
(UIElement.OpacityProperty));
Storyboard sb = new Storyboard();
sb.Children.Add(fadeIn);
sb.Children.Add(fade);
sb.Completed += new EventHandler(sb_Completed);
sb.Begin(this);
}
}
void sb_Completed(object sender, EventArgs e)
{
this.Visibility = Visibility.Hidden;
}