WPF绑定Canvas.Left / Canvas.Top到Point DependencyProperty,使用PointAnimation

请考虑以下简化的例子来说明我的问题:

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="500" Height="500" Title="Click anywhere to animate the movement of the blue thingy..."> <Canvas x:Name="canvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="AntiqueWhite" MouseDown="canvas_MouseDown" /> </Window> 

MainWindow.xaml.cs

 using System; using System.Windows; using System.Windows.Input; using System.Windows.Media.Animation; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.canvas.Children.Add(new Thingy()); } private void canvas_MouseDown(object sender, MouseButtonEventArgs e) { var thingy = (Thingy)this.canvas.Children[0]; var from = new Point(0.0, 0.0); var to = new Point( canvas.ActualWidth - thingy.ActualWidth, canvas.ActualHeight - thingy.ActualHeight ); var locAnim = new PointAnimation( from, to, new Duration(TimeSpan.FromSeconds(5)) ); locAnim.Completed += (s, a) => { // Only at this line does the thingy move to the // correct position... thingy.Location = to; }; thingy.Location = from; thingy.BeginAnimation(Thingy.LocationProperty, locAnim); } } } 

Thingy.xaml

 <UserControl x:Class="WpfApplication1.Thingy" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="50" Height="50" Background="Blue" /> 

Thingy.xaml.cs

 using System.Windows; using System.Windows.Controls; using System.Windows.Data; namespace WpfApplication1 { public partial class Thingy : UserControl { public static DependencyProperty LocationProperty = DependencyProperty.Register( "Location", typeof(Point), typeof(Thingy) ); public Thingy() { InitializeComponent(); Canvas.SetLeft(this, 0.0); Canvas.SetTop(this, 0.0); var xBind = new Binding(); xBind.Source = this; xBind.Path = new PropertyPath(Canvas.LeftProperty); xBind.Mode = BindingMode.TwoWay; var yBind = new Binding(); yBind.Source = this; yBind.Path = new PropertyPath(Canvas.TopProperty); yBind.Mode = BindingMode.TwoWay; var locBind = new MultiBinding(); locBind.Converter = new PointConverter(); locBind.Mode = BindingMode.TwoWay; locBind.Bindings.Add(xBind); locBind.Bindings.Add(yBind); BindingOperations.SetBinding( this, Thingy.LocationProperty, locBind ); } public Point Location { get { return (Point)this.GetValue(LocationProperty); } set { this.SetValue(LocationProperty, value); } } } } 

PointConverter.cs

 using System; using System.Globalization; using System.Windows; using System.Windows.Data; namespace WpfApplication1 { public class PointConverter : IMultiValueConverter { public object Convert(object[] v, Type t, object p, CultureInfo c) { return new Point((double)v[0], (double)v[1]); } public object[] ConvertBack(object v, Type[] t, object p, CultureInfo c) { return new object[] { ((Point)v).X, ((Point)v).Y }; } } } 

这里的目标是:

  1. 使用LocationProperty来操作和访问Canvas.LeftPropertyCanvas.TopProperty值。
  2. PointAnimation类animation说LocationProperty

目标1似乎工作正常,只有在试图animation化LocationProperty ,它不会像预期的那样运行。

“预期”是指Thingy的实例应该随着animation的进展而移动。

我能够使用DoubleAnimation类的两个实例来完成此操作。

如果问题是Point是一个值types,那么我怀疑我可以定义我自己的Pointtypes和我自己的AnimationTimeline 。 这不是我想要做的。 这是一个更大的项目的一部分, LocationProperty将用于其他事情。

说实话,底线是,在我看来,这应该只是工作,你能告诉我:

  1. 为什么不呢?
  2. 如果有解决问题的定义?

我还会提到,我针对这个项目的.Net Framework 4.5。

谢谢。

这是最简单的代码来动画的东西。

  • 它利用依赖属性回调
  • 不使用绑定
  • 不使用转换器
  • 不使用故事板

主窗口:

 using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media.Animation; namespace WpfApplication1 { public partial class MainWindow { public MainWindow() { InitializeComponent(); } private void MainWindow_OnMouseDown(object sender, MouseButtonEventArgs e) { var x = Canvas.GetLeft(Control1); var y = Canvas.GetTop(Control1); x = double.IsNaN(x) ? 0 : x; y = double.IsNaN(y) ? 0 : y; var point1 = new Point(x, y); var point2 = e.GetPosition(this); var animation = new PointAnimation(point1, point2, new Duration(TimeSpan.FromSeconds(1))); animation.EasingFunction = new CubicEase(); Control1.BeginAnimation(UserControl1.LocationProperty, animation); } } } 

主窗口:

 <Window x:Class="WpfApplication1.MainWindow" 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:WpfApplication1" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525" MouseDown="MainWindow_OnMouseDown"> <Canvas> <local:UserControl1 Background="Red" Height="100" Width="100" x:Name="Control1" /> </Canvas> </Window> 

控制:

 using System.Windows; using System.Windows.Controls; namespace WpfApplication1 { public partial class UserControl1 { public static readonly DependencyProperty LocationProperty = DependencyProperty.Register( "Location", typeof(Point), typeof(UserControl1), new UIPropertyMetadata(default(Point), OnLocationChanged)); public UserControl1() { InitializeComponent(); } public Point Location { get { return (Point) GetValue(LocationProperty); } set { SetValue(LocationProperty, value); } } private static void OnLocationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control1 = (UserControl1) d; var value = (Point) e.NewValue; Canvas.SetLeft(control1, value.X); Canvas.SetTop(control1, value.Y); } } } 

控制:

 <UserControl x:Class="WpfApplication1.UserControl1" 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:WpfApplication1" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> </UserControl> 

TODO:根据您的需要调整代码:)

编辑:一个简单的双向绑定,听Canvas.[Left|Top]Property

(待加强)

 using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; namespace WpfApplication1 { public partial class UserControl1 { public static readonly DependencyProperty LocationProperty = DependencyProperty.Register( "Location", typeof(Point), typeof(UserControl1), new PropertyMetadata(default(Point), OnLocationChanged)); public UserControl1() { InitializeComponent(); DependencyPropertyDescriptor.FromProperty(Canvas.LeftProperty, typeof(Canvas)) .AddValueChanged(this, OnLeftChanged); DependencyPropertyDescriptor.FromProperty(Canvas.TopProperty, typeof(Canvas)) .AddValueChanged(this, OnTopChanged); } public Point Location { get { return (Point) GetValue(LocationProperty); } set { SetValue(LocationProperty, value); } } private void OnLeftChanged(object sender, EventArgs eventArgs) { var left = Canvas.GetLeft(this); Location = new Point(left, Location.Y); } private void OnTopChanged(object sender, EventArgs e) { var top = Canvas.GetTop(this); Location = new Point(Location.X, top); } private static void OnLocationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control1 = (UserControl1) d; var value = (Point) e.NewValue; Canvas.SetLeft(control1, value.X); Canvas.SetTop(control1, value.Y); } } } 

我喜欢Aybe的答案,但它没有解释为什么原始代码不起作用。 我运行你的代码,并尝试了一些替代方法,看来发生了什么事是绑定转换器在动画过程中被忽略。 如果您在转换器方法中设置断点,或者执行Debug.WriteLine,则无论哪种方式,您都可以看到转换器不会在整个动画中被调用,而只是在您的代码中明确设置属性时。

深入挖掘,问题在于您设置Thingy绑定的方式。 绑定属性应该是Thingy.Location目标属性应该是Canvas.LeftCanvas.Top 。 你有倒退 – 你正在使Canvas.LeftCanvas.Top源属性和Thingy.Location目标属性。 您可能会认为将其设置为双向绑定可以使其工作(并且在您明确设置Thingy.Location属性时),但是看起来双向绑定方面在动画中将被忽略。

一个解决方案是不要在这里使用多重绑定。 当多个属性被多个属性或条件所取代时,多重绑定是真正的。 在这里,你有多个属性( Canvas.LeftCanvas.Top ),你想用一个属性来源 – Thingy.Location 。 所以,在Thingy构造函数中:

  var xBind = new Binding(); xBind.Source = this; xBind.Path = new PropertyPath(Thingy.LocationProperty); xBind.Mode = BindingMode.OneWay; xBind.Converter = new PointToDoubleConverter(); xBind.ConverterParameter = false; BindingOperations.SetBinding(this, Canvas.LeftProperty, xBind); var yBind = new Binding(); yBind.Source = this; yBind.Path = new PropertyPath(Thingy.LocationProperty); yBind.Mode = BindingMode.OneWay; yBind.Converter = new PointToDoubleConverter(); yBind.ConverterParameter = true; BindingOperations.SetBinding(this, Canvas.TopProperty, yBind); 

另一个区别是绑定转换器。 而不是采取两个doubles ,给你一个Point ,我们需要一个转换器,需要一个Point并提取用于Canvas.LeftCanvas.Top属性(我使用ConverterParameter指定哪一个是所需的)的双。 所以:

 public class PointToDoubleConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var pt = (Point)value; bool isY = (bool)parameter; return isY ? pt.Y : pt.X; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value; } } 

这使得动画工作,同时仍然使用绑定和转换器。 唯一的缺点是Canvas属性和Thingy.Location之间的绑定必须是单向的,因为没有办法将Canvas.LeftCanvas.Top单独转换回完整的Point 。 换句话说,如果您随后更改Canvas.LeftCanvas.TopThingy.Location将不会更新。 (这当然也适用于任何无约束的解决方案)。

但是,如果你回到原来的多重绑定版本,只需将代码添加到Location属性更改处理程序来更新Canvas.LeftCanvas.Top ,就可以拥有你的蛋糕并且可以吃掉它。 他们不需要在这一点上是TwoWay绑定,因为你正在更新Location的属性更改处理程序中的Canvas.LeftCanvas.Top 。 基本上所有的实际绑定然后是确保Canvas.LeftCanvas.Top做的Location更新。

无论如何,这个谜还是解决了为什么你原来的方法不起作用。 设置复杂绑定时,正确识别源和目标是至关重要的。 TwoWay绑定并不是所有情况下的全部,特别是动画。