I seem to have reached some sort of MVVM breaking point here.
I would like for a control to have its opacity animated for half a second (DoubleAnimation from 0.5 to 1.0) when the underlying view model object have its "Status" property changed. I achieved this at first using a DataTrigger but since I haven't found a way to react to ANY change, just a given value, I had to always flip the VM objects "Status" property to a special "pending" value before setting it to its intended value. (Is there a way to react to ANY change btw?)
This was hacky so I started fiddling with EventTriggers instead...
This is what I've tried so far:
EventTrigger
This seems to require a RoutedEvent but that, in turn, requires that my underlying view model object inherits from DependencyObject.
i:Interaction.Triggers
That way I can listen to and react to normal .NET events but I haven't found a way to start a StoryBoard
using that approach.
i:Interaction.Triggers
and writing a Behavior
This experiment fell short on the fact I found no way to attach my custom behavior to its associated control.
This is what the XAML looked like:
<cc:MyControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Updated">
<i:Interaction.Behaviors>
<cv:OpacityBehavior Duration="0:0:0:5" />
</i:Interaction.Behaviors>
</i:EventTrigger>
</i:Interaction.Triggers>
And here's the custom behavior:
class OpacityBehavior : Behavior<MyControl>
{
public Duration Duration { get; set; }
protected override void OnAttached()
{
base.OnAttached();
var animation = new DoubleAnimation(0.5, 1, Duration, FillBehavior.HoldEnd);
var associatedObject = lookupVisualParent(this);
associatedObject.BeginAnimation(UIElement.OpacityProperty, animation);
}
}
That didn't work because the XAML parser required it to be attached directly to "MyControl" but I need to attach it to the event trigger. I then tried this approach:
class OpacityBehavior : Behavior<DependencyObject>
{
public Duration Duration { get; set; }
protected override void OnAttached()
{
base.OnAttached();
var animation = new DoubleAnimation(0.5, 1, Duration, FillBehavior.HoldEnd);
var associatedObject = lookupVisualParent(this);
associatedObject.BeginAnimation(UIElement.OpacityProperty, animation);
}
private UIElement lookupVisualParent(DependencyObject dObj)
{
if (dObj is UIElement)
return (UIElement) dObj;
if (dObj == null)
return null;
return lookupVisualParent(LogicalTreeHelper.GetParent(dObj));
}
}
This failed on the fact that lookupVisualParent
doesn't work. The logical parent of the behavior is always null
.
It strikes me this should be a fairly common task? Is there a good solution to this problem? I find it strange that I will have write my view model classes so that they derive from DependencyObject
in order to start an animation when an event fires.
Cheers
You could simply use a flag: set a flag on your VM called 'RecentlyChangedFlag'. Pulse it to true
, then false
, whenever the appropriate value(s) change. You could do that like this:
private bool _changedFlag;
public bool ChangedFlag
{
get
{
if (_changedFlag)
{
_changedFlag = false;
OnPropertyChanged("ChangedFlag");
return true;
}
// (else...)
return false;
}
protected set
{
_changedFlag = value;
OnPropertyChanged("ChangedFlag");
}
}
I.e., with the above code set ChangedFlag = true
when you want to signal the animation to start. It will be reset to false after WPF queries the true value.
Then have the animation occur when the value of RecentlyChangedFlag
is true
, as an EnterAction
for instance.
Hope that helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With