问题
I have a simple problem with an animated binded property. Here is a simple example to illustrate it :
ViewModel :
public class ViewModel
{
private double myProperty;
public double MyProperty
{
get { return myProperty; }
set { myProperty = value; /* Break point here */ }
}
public ViewModel()
{
MyProperty = 20;
}
}
MyControl :
public class MyControl : Button
{
protected override void OnClick()
{
base.OnClick();
Height = ActualHeight + 20;
}
}
MyAnimatedControl :
public class MyAnimatedControl : Button
{
protected override void OnClick()
{
base.OnClick();
DoubleAnimation a = new DoubleAnimation(ActualHeight + 20, new Duration(TimeSpan.FromMilliseconds(500)));
this.BeginAnimation(HeightProperty, a);
}
}
MainWindow :
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new ViewModel();
InitializeComponent();
}
}
<Grid>
<StackPanel>
<local:MyAnimatedControl Height="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Content="Animated"/>
<local:MyControl Height="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Content="Normal 1"/>
<local:MyControl Height="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Content="Normal 2"/>
</StackPanel>
</Grid>
I have two controls inherited from Button (MyControl and MyAnimatedControl), their Height property is binded (mode two way) to the MyProperty property in the ViewModel.
When the controls are clicked, they grow their Height property. The MyControl assigns it a new value and the MyAnimatedControl animates it.
Problem :
When I click one of the MyControl, the bindings work correctly, all the controls' heights are updated, the break point works in the ViewModel. But when I click the MyAnimatedControl, the binding seems to not work anymore : it grows alone and the binding doesn't work anymore whereas the two normal controls still grow together. It doesn't share the same height with to other controls anymore.
Is there a way to have an operational binding on an animated dependency property, which will update the ViewModel all along the animation? I read this post but it doesn't answer it.
Thanks.
回答1:
The problem seems to be that the animated property is at the same time the source of the binding. To get around this, you could create another height property (e.g. MyHeight
) that gets updated whenever the control's height changes and that serves as the target of a OneWayToSource
binding.
public class MyAnimatedControl : Button
{
public static readonly DependencyProperty MyHeightProperty =
DependencyProperty.Register(
"MyHeight", typeof(double), typeof(MyAnimatedControl));
public double MyHeight
{
get { return (double)GetValue(MyHeightProperty); }
set { SetValue(MyHeightProperty, value); }
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
MyHeight = sizeInfo.NewSize.Height;
}
}
The binding:
<local:MyAnimatedControl ...
MyHeight="{Binding MyProperty, Mode=OneWayToSource}"
Height="{Binding MyProperty, Mode=OneWay}"/>
In order to get the other binding targets updated, you would also need to implement the INotifyPropertyChanged
interface in your view model:
public class ViewModel : INotifyPropertyChanged
{
private double myProperty = 30;
public double MyProperty
{
get { return myProperty; }
set
{
myProperty = value;
RaisePropertyChanged("MyProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Finally, as Rohit Vats has written in his answer, you'll need to remove the hold of the animation on the Height property inside the Completed event handler:
protected override void OnClick()
{
base.OnClick();
DoubleAnimation a = new DoubleAnimation(ActualHeight + 20,
new Duration(TimeSpan.FromMilliseconds(500)));
a.Completed += (s, e) =>
{
BeginAnimation(Button.HeightProperty, null); // Remove animation.
SetCurrentValue(Button.HeightProperty, ActualHeight); // Set value.
};
this.BeginAnimation(Button.HeightProperty, a);
}
回答2:
Issue is animation has higher precedence order so when it gets applied to any property subsequent changes in that property won't reflect back on UI and in binding.
As described here, you can solve this with three approaches:
- Set the animation's FillBehavior to Stop.
- Remove the entire storyBoard.
- Remove the animation from individual property.
In your case we can use third option to remove animation from Height DP after animation is completed.
Second, you should call SetCurrentValue method to set the DP which will update the binding and hence update the bound ViewModel property.
Put all this in Completed
event of DoubleAnimation
:
public class MyAnimatedControl : Button
{
protected override void OnClick()
{
base.OnClick();
DoubleAnimation a = new DoubleAnimation(ActualHeight + 20,
new Duration(TimeSpan.FromMilliseconds(500)));
a.Completed += (s, e) =>
{
BeginAnimation(Button.HeightProperty, null); // Remove animation.
SetCurrentValue(Button.HeightProperty, ActualHeight); // Set value.
};
this.BeginAnimation(Button.HeightProperty, a);
}
}
Also make sure your ViewModel is implementing INotifyPropertyChanged and property changed event is raised from MyProperty
setter.
回答3:
The Control.Height
property is a DependencyProperty
and DependencyProperty
s can be set from a variety of sources, eg. Style Setter
, Animation
, from code, etc. Because of this reason, Microsoft had to define an order of precedence to decide which of these possible ways to change the DependencyProperty
value were more important than others.
It was logically decided that setting a DependencyProperty
from an Animation
as you do in your example should 'override' values set from other sources, eg. from a property setter. For full information of the DependencyProperty
value precedence list, please see the Dependency Property Value Precedence page on MSDN.
However, using your code, I could not reproduce your problem. I could repeatedly click on each and every Button
and see it continually growing in size. Therefore, if you couldn't do that, then I suspect that you have some other code somewhere interfering with this. If that was not your problem, then please clearly explain what is.
UPDATE >>>
Oh I think I see what you're after now... you're wondering why your property setter is not called when the Animation
animates the property value. However, that won't work because Animation
s don't permanently change property values. From the Data Binding and Animating Animations section of the Animation Overview page:
Most animation properties can be data bound or animated; for example, you can animate the
Duration
property of aDoubleAnimation
. However, because of the way the timing system works, data bound or animated animations do not behave like other data bound or animated objects.
来源:https://stackoverflow.com/questions/24240596/why-bindings-dont-work-with-animations