【问题标题】:WPF Bind Canvas.Left/Canvas.Top to Point DependencyProperty, Use PointAnimationWPF 将 Canvas.Left/Canvas.Top 绑定到点 DependencyProperty,使用 PointAnimation
【发布时间】:2017-01-20 16:11:15
【问题描述】:

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

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. Animate 使用 PointAnimation 类表示 LocationProperty

目标 #1 似乎工作正常,只是在尝试为 LocationProperty 设置动画时,它的行为才没有达到预期。

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

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

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

老实说,最重要的是,在我看来这应该是可行的,你能告诉我吗:

  1. 为什么不呢?
  2. 是否有定义的问题解决方案?

我还要提到,我的目标是 .Net Framework 4.5 用于这个项目。

谢谢。

【问题讨论】:

  • 不确定这是否有帮助,但您是否尝试过将 TranslateTransform 附加到 Thingy 并绑定/动画转换而不是 Canvas.Left 和 Canvas.Top?至少它会表现得更好。
  • 当您说“我可以使用 DoubleAnimation 类的两个实例来完成此任务”。你什么意思?在这种情况下,您要为哪些属性设置动画?
  • @PeterMoore 感谢 TranslateTransform 的想法,我一定会调查的。为了回答你的问题,我在 Canvas.Left/Top 上使用了 DoubleAnimations。
  • 明白了,有道理。请参阅我的更新答案,了解为什么您的原始方法不起作用。

标签: c# .net wpf windows


【解决方案1】:

这是最简单的动画代码。

  • 它利用依赖属性回调
  • 不使用绑定
  • 不使用转换器
  • 不使用情节提要

主窗口:

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);
        }
    }
}

【讨论】:

  • 完美!这是我缺少的 OnLocationChanged 回调...旁注,我使用绑定/转换器的原因是因为我希望 Location.X/Y 和 Canvas.Left/Top 之间始终存在双向连接。我将实现回调并进行相应调整。非常感谢!
  • 看看我的编辑,我添加了一个简单的方法来通过监听Canvas 属性的变化来反映变化(没有彻底检查,但它确实有效)。请注意,WPF 足够智能,不会无休止地更新属性。
【解决方案2】:

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

深入挖掘,问题在于您设置Thingy 绑定的方式。绑定 source 属性应为 Thingy.Location,而 target 属性应为 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); 

另一个区别是绑定转换器。我们需要一个转换器,它接受Point 并提取用于Canvas.LeftCanvas.Top 属性(我正在使用@ 987654340@ 指定需要哪一个)。所以:

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。基本上所有实际的绑定都是确保LocationCanvas.LeftCanvas.Top 执行时更新。

无论如何,关于为什么您的原始方法不起作用的谜团已解开。设置复杂绑定时,正确识别源和目标至关重要; TwoWay 绑定并非适用于所有情况,尤其是动画。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-31
    • 1970-01-01
    • 2013-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多