请考虑以下简化的例子来说明我的问题:
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 }; } } }
这里的目标是:
LocationProperty
来操作和访问Canvas.LeftProperty
和Canvas.TopProperty
值。 PointAnimation
类animation说LocationProperty
。 目标1似乎工作正常,只有在试图animation化LocationProperty
,它不会像预期的那样运行。
“预期”是指Thingy
的实例应该随着animation的进展而移动。
我能够使用DoubleAnimation
类的两个实例来完成此操作。
如果问题是Point
是一个值types,那么我怀疑我可以定义我自己的Point
types和我自己的AnimationTimeline
。 这不是我想要做的。 这是一个更大的项目的一部分, LocationProperty
将用于其他事情。
说实话,底线是,在我看来,这应该只是工作,你能告诉我:
我还会提到,我针对这个项目的.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.Left
和Canvas.Top
。 你有倒退 – 你正在使Canvas.Left
和Canvas.Top
源属性和Thingy.Location
目标属性。 您可能会认为将其设置为双向绑定可以使其工作(并且在您明确设置Thingy.Location
属性时),但是看起来双向绑定方面在动画中将被忽略。
一个解决方案是不要在这里使用多重绑定。 当多个属性被多个属性或条件所取代时,多重绑定是真正的。 在这里,你有多个属性( Canvas.Left
和Canvas.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.Left
和Canvas.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.Left
或Canvas.Top
单独转换回完整的Point
。 换句话说,如果您随后更改Canvas.Left
或Canvas.Top
, Thingy.Location
将不会更新。 (这当然也适用于任何无约束的解决方案)。
但是,如果你回到原来的多重绑定版本,只需将代码添加到Location
属性更改处理程序来更新Canvas.Left
和Canvas.Top
,就可以拥有你的蛋糕并且可以吃掉它。 他们不需要在这一点上是TwoWay
绑定,因为你正在更新Location
的属性更改处理程序中的Canvas.Left
和Canvas.Top
。 基本上所有的实际绑定然后是确保Canvas.Left
和Canvas.Top
做的Location
更新。
无论如何,这个谜还是解决了为什么你原来的方法不起作用。 设置复杂绑定时,正确识别源和目标是至关重要的。 TwoWay
绑定并不是所有情况下的全部,特别是动画。