[英]How do I bind to Canvas.Left or Canvas.Top attached property in code behind?
[英]WPF Bind Canvas.Left/Canvas.Top to Point DependencyProperty, Use PointAnimation
请考虑以下说明我的问题的简化示例:
主窗口.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>
主窗口.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);
}
}
}
东西.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);
}
}
}
}
点转换器.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
类表示LocationProperty
。 目标 #1 似乎工作正常,只有在尝试为LocationProperty
设置动画时,它才会按预期运行。
“预期”是指Thingy
的实例应该随着动画的进行而移动。
我可以使用DoubleAnimation
类的两个实例来完成此操作。
如果问题是Point
是值类型,那么我怀疑我可以定义我自己的Point
类型和我自己的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
目标属性。 您可能认为将其设为双向绑定会使其工作(当您显式设置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);
另一个区别是绑定转换器。 我们需要一个转换器,它接受一个Point
并提取用于Canvas.Left
和Canvas.Top
属性的double
doubles
,而不是取两个doubles
并给您一个Point
(我使用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
绑定并非适用于所有情况,尤其是动画。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.