Callback function when value equals specified number - wpf

I'm quite new in WPF-Animations so apologize if it's too easy, but I can't find any answer (nor my question). So:
I have very simple animation - some canvas is rotating from angle -45 degrees to 45 degrees. All animation is made in XAML (got some issues with code-behind animation). I would like to bind function when value equals 0 (e.g. make some noise then). How can I approach this?
Thank you for all hints.

I have two options to solve this problem. One is intrusive but gives you more control over the actual value, another is not intrusive but gives you only indirect control over the value. I'll give the sample code with both options at the end of the answer.
Non intrusive solution
Subscribe to the CurrentTimeInvalidated event on your DoubleAnimation object. If you know the animation function and its duration you can approximately say when the animation value is close to your event. For say, animation duration is 500 ms, and the animation function is linear. Then you can say, that at 250ms you are halfway through.
Intrusive solution
Remember: DoubleAnimation (like any other animation) is just a class and you are welcome to inherit it and override any virtual member. In case of DoubleAnimation of particular interest is GetCurrentValueCore() method. And of course you can define any events or dependency properties on this new class. Now you see where it's all going. Inherit DoubleAnimation, override GetCurrentValueCore(), define ValueChanged event, and fire it on every call to GetCurrentValueCore().
Code example
MainWindow.xaml
<Window x:Class="WpfPlayground.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"
xmlns:l="clr-namespace:WpfPlayground">
<Grid>
<Grid.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard Duration="00:00:00.500" Storyboard.TargetName="rectangle" RepeatBehavior="Forever">
<l:DoubleAnimationWithCallback From="0"
To="180" Duration="00:00:00.500"
Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(RotateTransform.Angle)"
Callback="{Binding AnimationCallback, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:MainWindow}}}"
CurrentTimeInvalidated="OnCurrentTimeInvalidated" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<!--We animate this rectangle-->
<Rectangle x:Name="rectangle" Width="50" Height="50" Fill="Green">
<Rectangle.LayoutTransform>
<RotateTransform />
</Rectangle.LayoutTransform>
</Rectangle>
<!--Debug information-->
<TextBlock x:Name="tbTime" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<TextBlock x:Name="tbAngle" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Globalization;
namespace WpfPlayground
{
public partial class MainWindow : Window
{
public Func<double, double> AnimationCallback { get { return AnimationCallbackImpl; } }
public MainWindow()
{
InitializeComponent();
}
private double AnimationCallbackImpl(double value)
{
tbAngle.Text = value.ToString(CultureInfo.CurrentCulture);
return value;
}
private void OnCurrentTimeInvalidated(object sender, EventArgs e)
{
tbTime.Text = ((AnimationClock)sender).CurrentTime.ToString();
}
}
}
DoubleAnimationWithCallback.cs
using System;
using System.Windows;
using System.Windows.Media.Animation;
namespace WpfPlayground
{
public class DoubleAnimationWithCallback : DoubleAnimation
{
// Cache Callback DP, to avoid performance hit.
private Func<double, double> _callback;
// reference to frozen instance. See comments below for explanation.
private DoubleAnimationWithCallback _coreInstance;
public Func<double, double> Callback
{
get { return (Func<double, double>)GetValue(CallbackProperty); }
set { SetValue(CallbackProperty, value); }
}
public static readonly DependencyProperty CallbackProperty =
DependencyProperty.Register("Callback", typeof(Func<double, double>), typeof(DoubleAnimationWithCallback), new PropertyMetadata(null, OnCallbackChanged));
private static void OnCallbackChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var dawc = o as DoubleAnimationWithCallback;
if (dawc != null)
{
dawc.UpdateCallback(e.NewValue as Func<double, double>);
}
}
private void UpdateCallback(Func<double, double> callback)
{
_callback = callback;
if (_coreInstance != null)
{
_coreInstance._callback = _callback;
}
}
protected override Freezable CreateInstanceCore()
{
if (_coreInstance == null)
{
// When callback changes we update corresponding callback on
// the frozen object too.
_coreInstance = new DoubleAnimationWithCallback()
{
Callback = Callback
};
}
return _coreInstance;
}
protected override double GetCurrentValueCore(double defaultOriginValue, double defaultDestinationValue, AnimationClock animationClock)
{
var value = base.GetCurrentValueCore(defaultOriginValue, defaultDestinationValue, animationClock);
if (_callback != null)
{
return _callback(value);
}
return value;
}
}
}
There is one caveat though: animation pipeline works with Freezable objects, so you'll have to override CreateInstanceCore() method and return proper instance. Furthermore, if you change Callback dependency property on the real object you'll have to also update the frozen one. It is not quite welcome practice and that's why I call it intrusive. Be very careful with this code and test it throughly. It just shows a possible direction and is not the final destination.
Hope this helps

Related

wpf Button always disabled (with CommandBinding, CanExecute=True and IsEnabled= True)

Revised: I apologize for missing some important descriptions in the first version, now the problem should be well-defined:
so I'm making a toy CAD program with following views:
MainWindow.xaml
CustomizedUserControl.xaml
CustomizedUserControl is a Tab within MainWindow, and its DataContext is defined in MainWindow.xaml as:
<Window.Resources>
<DataTemplate DataType="{x:Type local:CustomizedTabClass}">
<local:UserControl1/>
</DataTemplate>
</Window.Resources>
And CustomizedUserControl.xaml provides a canvas and a button, so when the button is pressed the user should be able to draw on the canvas. As the following code shows, the content of Canvas is prepared by the dataContext, "tabs:CustomizedTabClass".
CustomizedUserControl.xaml
<CustomizedUserControl x:Name="Views.CustomizedUserControl11"
...
>
<Button ToolTip="Lines (L)" BorderThickness="2"
Command="{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.LinesChainCommand}"
IsEnabled="True"
Content = "{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.Button1Name}">
</Button>
...
<canvas x:Name="CADCanvas"
Drawing="{Binding Drawing ,Mode=TwoWay}" >
</canvas>
It is also notable that I used an external library, Fody/PropertyChanged, in all classes so property notifications would be injected without further programming.
CustomizedUserControl.xaml.cs
using PropertyChanged;
using System.ComponentModel;
using System.Windows.Controls;
[AddINotifyPropertyChangedInterface]
public partial class CustomizedUserControl: Usercontrol, INotifyPropertyChanged{
public CADDrawingCommands DrawingCommands { get; set; }
public CustomizedUserControl()
{
InitializeComponent();
DrawingCommands = new CADDrawingCommands(this);
DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
}
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
CADDrawingCommands.cs
using PropertyChanged;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows;
[AddINotifyPropertyChangedInterface]
public class CADDrawingCommands : INotifyPropertyChanged{
UserControl _drawableTab;
public string Button1Name { get; set; } = "TestForDataBinding";
public RoutedCommand LinesChainCommand { get; set; } = new RoutedCommand();
public CADDrawingCommands(UserControl dTab){
_drawableTab = dTab;
CommandBinding lineCommandBinding = new CommandBinding(LinesChainCommand,
(object sender, ExecutedRoutedEventArgs e) =>
{
MessageBox.Show("Test");
//Draw on canvas inside CustomizedUserControl (modify Drawing property in CustomizedTabClass)
}, (object sender, CanExecuteRoutedEventArgs e) => { e.CanExecute = true; });
_drawableTab.CommandBindings.Add(lineCommandBinding);
}
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
The Content of Button is set correctly, as I can read the string defined in Button1Name:
Therefore I suppose the Data Binding for Command is also ok. IsEnabled has been set to true and CanExecute of the CommandBinding would only return true.
Why is my button still greyed out and not clickable?
If I define the button inside a Window instead of UserControl (and set the datacontext of the Window to its own code behind, the button will be clickable! Why?
Thank you for your time! Hopefully would somebody help me cuz I've run out of ideas and references.
Made the simplest example.
Everything works as it should.
BaseInpc is my simple INotifyPropertyChanged implementation from here: BaseInpc
using Simplified;
using System.Windows;
using System.Windows.Input;
namespace CustomizedUserControlRoutedCommand
{
public class CADDrawingCommands : BaseInpc
{
UIElement _drawableTab;
private string _button1Name = "TestForDataBinding";
public string Button1Name { get => _button1Name; set => Set(ref _button1Name, value); }
public static RoutedCommand LinesChainCommand { get; } = new RoutedCommand();
public CADDrawingCommands(UIElement dTab)
{
_drawableTab = dTab;
CommandBinding lineCommandBinding = new CommandBinding(LinesChainCommand,
(object sender, ExecutedRoutedEventArgs e) =>
{
MessageBox.Show("Test");
//Draw on canvas inside CustomizedUserControl (modify Drawing property in CustomizedTabClass)
}, (object sender, CanExecuteRoutedEventArgs e) => { e.CanExecute = true; });
_drawableTab.CommandBindings.Add(lineCommandBinding);
}
}
}
<UserControl x:Name="CustomizedUserControl11" x:Class="CustomizedUserControlRoutedCommand.CustomizedUserControl"
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"
xmlns:local="clr-namespace:CustomizedUserControlRoutedCommand"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Button ToolTip="Lines (L)" BorderThickness="2"
Command="{x:Static local:CADDrawingCommands.LinesChainCommand}"
IsEnabled="True"
Content = "{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.Button1Name}">
</Button>
</Grid>
</UserControl>
using System.Windows.Controls;
namespace CustomizedUserControlRoutedCommand
{
public partial class CustomizedUserControl : UserControl
{
public CADDrawingCommands DrawingCommands { get; }
public CustomizedUserControl()
{
DrawingCommands = new CADDrawingCommands(this);
InitializeComponent();
DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
}
}
}
<Window x:Class="CustomizedUserControlRoutedCommand.TestCustomizedUserControlWindow"
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:CustomizedUserControlRoutedCommand"
mc:Ignorable="d"
Title="TestCustomizedUserControlWindow" Height="450" Width="800">
<Grid>
<local:CustomizedUserControl/>
</Grid>
</Window>
If you showed your code in full, then I see the following problems in it:
You are setting the value incorrectly for the DrawingCommands property.
In this property, you do not raise PropertyChanged.
The binding in the Button is initialized in the InitializeComponent() method. At this point, the property is empty, and when you set a value to it, the binding cannot find out.
There are two ways to fix this:
Raise PropertyChanged in the property;
If you set the property value once in the constructor, then set it immediately in the initializer. Make the property "Read Only". This way, in my opinion, is better.
public CADDrawingCommands DrawingCommands { get; }
public FileEditTabUserControl()
{
DrawingCommands = new CADDrawingCommands(this);
InitializeComponent();
DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
}
You have a button bound to a command in the DrawingCommands.LinesChainCommand property.
But to this property, you assign an empty instance of the = new RoutedCommand () routing command.
This looks pointless enough.
If you need a routable command, create it in the "Read Only" static property.
This will make it much easier to use in XAML:
public static RoutedCommand LinesChainCommand { get; } = new RoutedCommand();
<Button ToolTip="Lines (L)" BorderThickness="2"
Command="{x:Static local:DrawingCommands.LinesChainCommand}"
IsEnabled="True"
Content = "{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.Button1Name}">
</Button>
Raising PropertyChanged in CADDrawingCommands properties is also not visible in your code.
If it really does not exist, then the binding is also unaware of changing property values.

Animating a Double Collection Value at Runtime

Say I have a shape. I've given it a Stroke and StrokeThickness and a StrokeDashArray to get the desired dashed outline. Then I animate the StrokeDashOffset via VisualStateManager to get the "Marching Ants" style animation to it. Everything works great...
Except I want the default of the shape to NOT have a StrokeDashArray and instead want to set that based on a VisualState in the VisualStateManager except unfortunately as we know I can only do a DoubleAnimation on a Property, and not a Double Collection Value like what StrokeDashArray is...
My question is, is there a clever way I could animate that value during runtime so the shape in an UnSelected State has a solid Stroke but via the 'VisualStateManager' (maybe) still supply the StrokeDashOffset on the Selected State to the same shape? Or am I better off having two separate shapes and toggling the visibility between them so that each have their own default values?
If it would help visualize with a picture or something let me know and I'll add more to the question.
One option would be to animate a double property and create a new DoubleCollection that is bound to the StrokeDashArray.
Xaml:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="StrokeDashArrayAnimation">
<Storyboard BeginTime="0">
<DoubleAnimation Duration="0:0:5"
From="0"
Storyboard.TargetName="UI"
Storyboard.TargetProperty="StrokeValue"
To="10" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Ellipse x:Name="lo"
Stroke="Red"
StrokeDashArray="{Binding StrokeArray}"
StrokeThickness="5" />
<Button Width="150"
Height="49"
Margin="29,65,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="Button_Click_1"
Content="Start" />
</Grid>
Code:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
public double StrokeValue
{
get { return (double)GetValue(StrokeValueProperty); }
set { SetValue(StrokeValueProperty, value); }
}
public static readonly DependencyProperty StrokeValueProperty =
DependencyProperty.Register("StrokeValue", typeof(double), typeof(MainPage),
new PropertyMetadata(0.0, OnStrokeValueChanged));
private static void OnStrokeValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var page = d as MainPage;
if (page != null) page.StrokeArray = new DoubleCollection { (double)e.NewValue, 1 };
}
public DoubleCollection StrokeArray
{
get { return (DoubleCollection)GetValue(StrokeArrayProperty); }
set { SetValue(StrokeArrayProperty, value); }
}
public static readonly DependencyProperty StrokeArrayProperty =
DependencyProperty.Register("StrokeArray", typeof(DoubleCollection), typeof(MainPage)
, new PropertyMetadata(new DoubleCollection { 0, 1 }));
private void Button_Click_1(object sender, RoutedEventArgs e)
{
VisualStateManager.GoToState(this, "StrokeDashArrayAnimation", false);
}
}
}

Issue with "Thumb.DragStarted" event with MVVMLight

I'm trying to trigger Slider Thumb.DragStarted event by using MVVMLight EventToCommand but it is not working. The same thing is working perfectly for Slider Event ValueChanged.
Below is my code:
<Slider
Width="150"
AutoToolTipPlacement="BottomRight"
AutoToolTipPrecision="2"
IsSnapToTickEnabled="True"
Maximum="{Binding SilderMaxValue}"
Minimum="0"
Value="{Binding SliderValue}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<cmd:EventToCommand
Command="{Binding SliderValueChangedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="Thumb.DragStarted">
<cmd:EventToCommand
Command="{Binding SliderDragStartedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</Slider>
Thanks..
I saw your post while I was trying to do something similar (albeit with Thumb.DragCompleted). In any case, I used an attached property. I'll post my solution in case it's of use to anyone.
SliderDragBehavoirs.cs:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
namespace WpfApplication1
{
public static class SliderDragBehaviors
{
public static readonly DependencyProperty DragCompletedCommandProperty =
DependencyProperty.RegisterAttached("DragCompletedCommand", typeof(ICommand), typeof(SliderDragBehaviors),
new FrameworkPropertyMetadata(new PropertyChangedCallback(DragCompleted)));
private static void DragCompleted(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var slider = (Slider)d;
var thumb = GetThumbFromSlider(slider);
thumb.DragCompleted += thumb_DragCompleted;
}
private static void thumb_DragCompleted(object sender, DragCompletedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
element.Dispatcher.Invoke(() =>
{
var command = GetDragCompletedCommand(element);
var slider = FindParentControl<Slider>(element) as Slider;
command.Execute(slider.Value);
});
}
public static void SetDragCompletedCommand(UIElement element, ICommand value)
{
element.SetValue(DragCompletedCommandProperty, value);
}
public static ICommand GetDragCompletedCommand(FrameworkElement element)
{
var slider = FindParentControl<Slider>(element);
return (ICommand)slider.GetValue(DragCompletedCommandProperty);
}
private static Thumb GetThumbFromSlider(Slider slider)
{
var track = slider.Template.FindName("PART_Track", slider) as Track;
return track == null ? null : track.Thumb;
}
private static DependencyObject FindParentControl<T>(DependencyObject control)
{
var parent = VisualTreeHelper.GetParent(control);
while (parent != null && !(parent is T))
{
parent = VisualTreeHelper.GetParent(parent);
}
return parent;
}
}
}
There's a couple of things worth noting here. Because the command is hooked up to the Slider, but the event is fired on the Thumb, it is necessary to be able to look up/down the visual tree in order to get one from the other.
Example XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviors="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="grid">
<Slider behaviors:SliderDragBehaviors.DragCompletedCommand="{Binding Path=DragCompletedCommand}"/>
</Grid>
</Window>
Hope that's of some use :)
I had a problem with the code from Tom Allen because the slider template was not available at the time I wanted to bind it with command. Basically, all i needed to do is wait for the slider control to load and try again.
Here are the changes that i needed to make in order for it to work:
private static void DragCompleted(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//the Template of the slider is not available now
//we have to wait for the slider to load completely in order to do this
var slider = (Slider)d;
slider.Loaded += slider_Loaded;
}
static void slider_Loaded(object sender, RoutedEventArgs e)
{
var slider = (Slider)sender;
var thumb = GetThumbFromSlider(slider);
thumb.DragCompleted += thumb_DragCompleted;
}
Hope it helps!
Regards

Binding the Duration property of an animation in Silverlight

I have a DoubleAnimation that I want to bind its From, To and Duration Properties so they would change smoothly.
The binding of From and To works great, changes them smoothly, but the changes of the Duration are simply being ignored.
For debugging, I made a button that on click calls the Stop and Begin methods of the containing StoryBoard, and the animation started from the beginning with the correct duration. Also I checked and saw that the Duration property of the animation is actually being updated everytime, so the change is just being ignored by the animation. (Unlike the From and To that really react smoothly.)
Tried the same in WPF and got the same results, here's a snippet:
<Canvas>
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard x:Name="Story">
<DoubleAnimation x:Name="Anime"
Duration="{Binding Duration}"
RepeatBehavior="Forever"
Storyboard.TargetName="Text1"
Storyboard.TargetProperty="(Canvas.Left)"
From="0"
To="400"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
<TextBlock Text="Hello" Name="Text1"/>
</Canvas>
I would really appreciate an explanation why isn't this working and any workarounds that would make the binding work with a smooth transition.
Also, the actual final goal of mine is to have the TextBlock moving in constant speed despite changes in From and To. So if there is another way of achieving this, it would be even better.
Thanks.
There are two concern: how is your binding source (ViewModel) written, and did you update your datacontext. Here is my code and it works.
the MainPage.xaml:
<Canvas>
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard x:Name="Story">
<DoubleAnimation x:Name="Anime"
Duration="{Binding}"
RepeatBehavior="Forever"
Storyboard.TargetName="Text1"
Storyboard.TargetProperty="(Canvas.Left)"
From="0"
To="400"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
<TextBlock Text="Hello" Name="Text1"/>
<Button Content="Change" Margin="0, 100, 0, 0" Click="Button_Click" />
</Canvas>
the MainPage.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace SilverlightApplication
{
public partial class MainPage : UserControl
{
AnimVM vm = new AnimVM();
double dur = 5;
public MainPage()
{
InitializeComponent();
vm.Duration = new Duration(TimeSpan.FromSeconds(dur));
this.DataContext = vm.Duration;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
dur += 5;
vm.Duration = new Duration(TimeSpan.FromSeconds(dur));
this.DataContext = vm.Duration; // don't forget this line
}
}
public class ViewModelBase : INotifyPropertyChanged, IDisposable
{
protected ViewModelBase()
{
}
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
//this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public void Dispose()
{
this.OnDispose();
}
protected virtual void OnDispose()
{
}
}
public class AnimVM : ViewModelBase
{
private Duration _duration = new Duration(TimeSpan.FromSeconds(5));
public Duration Duration
{
get { return _duration; }
set
{
if (object.ReferenceEquals(this._duration, value)) return;
this._duration = value;
base.OnPropertyChanged("Duration");
}
}
}
}
I know I am way late to the party, but the problem is that Duration is not a DependencyProperty, so WPF is not listening to the value change event, and therefore not updating the animation when the value changes - that is different than just storing the updated value in the object.

Very strange problem in Silverlight with XAML, custom property and animation

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!

Resources