为什么我的WPF翻译动画在完成之前就停止了?

时间:2013-06-18 10:19:32

标签: c# wpf animation storyboard

我写了一个WindowExtension,它应该为窗口提供一个简单的Translate动画。但是这个动画总是在它到达目标坐标之前停止。任何人都可以给我一个建议吗?

祝你好运 克里斯

   public static class WindowExtensions
   {
      public static void Translate(this Window element, double x, double y, TimeSpan duration)
      {
         NameScope.SetNameScope(element, new NameScope());

         var xAnimation = new DoubleAnimationUsingKeyFrames {Duration = duration};
         xAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(element.Left, KeyTime.FromPercent(0)));
         xAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(x, KeyTime.FromPercent(1)));

         var yAnimation = new DoubleAnimationUsingKeyFrames {Duration = duration};
         yAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(element.Top, KeyTime.FromPercent(0)));
         yAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(y, KeyTime.FromPercent(1)));

         var storyboard = new Storyboard()
         {
            Children = { xAnimation, yAnimation }
         };

         Storyboard.SetTargetProperty(xAnimation, new PropertyPath("(Window.Left)"));
         Storyboard.SetTargetProperty(yAnimation, new PropertyPath("(Window.Top)"));

         storyboard.Duration = duration;
         storyboard.FillBehavior = FillBehavior.Stop;

         storyboard.Completed += (sender, args) =>
         {
            storyboard.SkipToFill();
            storyboard.Remove(element);
         };

         storyboard.Begin(element);
      }
   }

可以在WPF窗口中进行简单测试,如下所示:

   public partial class MainWindow : Window
   {
      public MainWindow()
      {
         InitializeComponent();
      }

      private void Button_Click(object sender, RoutedEventArgs e)
      {
         this.Translate(10,10, TimeSpan.FromMilliseconds(250));
      }
   }

3 个答案:

答案 0 :(得分:4)

WPF窗口定位/重新调整大小的DPI独立缩放对我来说一直是个问题(特别是当您想要根据监视器DPI和多显示器设置平滑地动画移动/大小变化时)

我确实编写了一个自定义帮助器类来帮助设置可能对您有帮助的Window维度。

主要班级(NativeWindowSizeManager):

using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;

/// <summary>
/// C Enumerator to Represent Special Window Handles
/// </summary>
public enum SpecialWindowHandles {
  kHwndTop = 0,
  kHwndBottom = 1,
  kHwndTopmost = -1,
  kHwndNotopmost = -2
}

/// <summary>
/// C Enumerator to Set Window Position Flags
/// </summary>
public enum SetNativeWindowPosition {
  kNoSize = 0x0001,
  kNoMove = 0x0002,
  kNoZOrder = 0x0004,
  kNoRedraw = 0x0008,
  kNoActivate = 0x0010,
  kDrawFrame = 0x0020,
  kFrameChanged = 0x0020,
  kShowWindow = 0x0040,
  kHideWindow = 0x0080,
  kNoCopyBits = 0x0100,
  kNoOwnerZOrder = 0x0200,
  kNoReposition = 0x0200,
  kNoSendChanging = 0x0400,
  kDeferErase = 0x2000,
  kAsyncWindowPos = 0x4000
}

/// <summary>
/// Class to perform Window Resize Animations
/// </summary>
public class NativeWindowSizeManager {
  #region Member Variables
  /// <summary>
  /// Attached Dependency Property for Native Window Height
  /// </summary>
  public static readonly
    DependencyProperty NativeWindowHeightProperty = DependencyProperty.RegisterAttached(
      "NativeWindowHeight",
      typeof(double),
      typeof(Window),
      new PropertyMetadata(OnNativeDimensionChanged));

  /// <summary>
  /// Attached Dependency Property for Native Window Width
  /// </summary>
  public static readonly
    DependencyProperty NativeWindowWidthProperty = DependencyProperty.RegisterAttached(
      "NativeWindowWidth",
      typeof(double),
      typeof(Window),
      new PropertyMetadata(OnNativeDimensionChanged));

  /// <summary>
  /// Attached Dependency Property for Native Window Left
  /// </summary>
  public static readonly
    DependencyProperty NativeWindowLeftProperty = DependencyProperty.RegisterAttached(
      "NativeWindowLeft",
      typeof(double),
      typeof(Window),
      new PropertyMetadata(OnNativeDimensionChanged));

  /// <summary>
  /// Attached Dependency Property for Native Window Top
  /// </summary>
  public static readonly
    DependencyProperty NativeWindowTopProperty = DependencyProperty.RegisterAttached(
      "NativeWindowTop",
      typeof(double),
      typeof(Window),
      new PropertyMetadata(OnNativeDimensionChanged));

  /// <summary>
  /// Private member holding Dpi Factor
  /// </summary>
  private static double? _dpiFactor;
  #endregion

  #region Constructors
  #endregion

  #region Commands & Properties
  #endregion

  #region Methods
  /// <summary>
  /// Sets the native height.
  /// </summary>
  /// <param name="element">The element.</param>
  /// <param name="value">The value.</param>
  public static void SetNativeWindowHeight(UIElement element, double value) {
    element.SetValue(NativeWindowHeightProperty, value);
  }

  /// <summary>
  /// Gets the native height.
  /// </summary>
  /// <param name="element">The element.</param>
  /// <returns>Native Height in pixels</returns>
  public static double GetNativeWindowHeight(UIElement element) {
    return (double)element.GetValue(NativeWindowHeightProperty);
  }

  /// <summary>
  /// Sets the native width.
  /// </summary>
  /// <param name="element">The element.</param>
  /// <param name="value">The value.</param>
  public static void SetNativeWindowWidth(UIElement element, double value) {
    element.SetValue(NativeWindowWidthProperty, value);
  }

  /// <summary>
  /// Gets the native width.
  /// </summary>
  /// <param name="element">The element.</param>
  /// <returns>Native Width in pixels</returns>
  public static double GetNativeWindowWidth(UIElement element) {
    return (double)element.GetValue(NativeWindowWidthProperty);
  }

  /// <summary>
  /// Sets the native left.
  /// </summary>
  /// <param name="element">The element.</param>
  /// <param name="value">The value.</param>
  public static void SetNativeWindowLeft(UIElement element, double value) {
    element.SetValue(NativeWindowLeftProperty, value);
  }

  /// <summary>
  /// Gets the native left.
  /// </summary>
  /// <param name="element">The element.</param>
  /// <returns>Native Left in pixels</returns>
  public static double GetNativeWindowLeft(UIElement element) {
    return (double)element.GetValue(NativeWindowLeftProperty);
  }

  /// <summary>
  /// Sets the native top.
  /// </summary>
  /// <param name="element">The element.</param>
  /// <param name="value">The value.</param>
  public static void SetNativeWindowTop(UIElement element, double value) {
    element.SetValue(NativeWindowTopProperty, value);
  }

  /// <summary>
  /// Gets the native top.
  /// </summary>
  /// <param name="element">The element.</param>
  /// <returns>Native Top in pixels</returns>
  public static double GetNativeWindowTop(UIElement element) {
    return (double)element.GetValue(NativeWindowTopProperty);
  }

  /// <summary>
  /// Method to Get Dpi Factor
  /// </summary>
  /// <param name="window">Window Object</param>
  /// <returns>Dpi Factor</returns>
  public static double GetDpiFactor(Visual window) {
    HwndSource windowHandleSource = PresentationSource.FromVisual(window) as HwndSource;
    if (windowHandleSource != null && windowHandleSource.CompositionTarget != null) {
      Matrix screenmatrix = windowHandleSource.CompositionTarget.TransformToDevice;
      return screenmatrix.M11;
    }

    return 1;
  }

  /// <summary>
  /// Method to Retrieve Dpi Factor for Window
  /// </summary>
  /// <param name="window">Requesting Window</param>
  /// <param name="originalValue">Dpi Independent Unit</param>
  /// <returns>Pixel Value</returns>
  private static int ConvertToDpiDependentPixels(Visual window, double originalValue) {
    if (_dpiFactor == null) {
      _dpiFactor = GetDpiFactor(window);
    }

    return (int)(originalValue * _dpiFactor);
  }

  /// <summary>
  /// Handler For all Attached Native Dimension property Changes
  /// </summary>
  /// <param name="obj">Dependency Object</param>
  /// <param name="e">Property Arguments</param>
  private static void OnNativeDimensionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
    var window = obj as Window;
    if (window == null)
      return;

    IntPtr handle = new WindowInteropHelper(window).Handle;
    var rect = new Rect();
    if (!GetWindowRect(handle, ref rect))
      return;

    rect.X = ConvertToDpiDependentPixels(window, window.Left);
    rect.Y = ConvertToDpiDependentPixels(window, window.Top);
    rect.Width = ConvertToDpiDependentPixels(window, window.ActualWidth);
    rect.Height = ConvertToDpiDependentPixels(window, window.ActualHeight);

    if (e.Property == NativeWindowHeightProperty) {
      rect.Height = ConvertToDpiDependentPixels(window, (double)e.NewValue);
    } else if (e.Property == NativeWindowWidthProperty) {
      rect.Width = ConvertToDpiDependentPixels(window, (double)e.NewValue);
    } else if (e.Property == NativeWindowLeftProperty) {
      rect.X = ConvertToDpiDependentPixels(window, (double)e.NewValue);
    } else if (e.Property == NativeWindowTopProperty) {
      rect.Y = ConvertToDpiDependentPixels(window, (double)e.NewValue);
    }

    SetWindowPos(
      handle,
      new IntPtr((int)SpecialWindowHandles.kHwndTop),
      rect.X,
      rect.Y,
      rect.Width,
      rect.Height,
      (uint)SetNativeWindowPosition.kShowWindow);
  }
  #endregion

  #region Native Helpers
  [DllImport("user32.dll", SetLastError = true)]
  private static extern bool GetWindowRect(IntPtr windowHandle, ref Rect rect);

  [DllImport("user32.dll")]
  [return: MarshalAs(UnmanagedType.Bool)]
  private static extern bool SetWindowPos(
    IntPtr windowHandle, IntPtr windowHandleInsertAfter, int x, int y, int cx, int cy, uint windowPositionFlag);

  /// <summary>
  /// C Structure To Represent Window Rectangle
  /// </summary>
  [SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented",
    Justification = "This is an Implementation for C Struct")]
  [StructLayout(LayoutKind.Sequential)]
  public struct Rect {
    public int X;
    public int Y;
    public int Width;
    public int Height;
  }
  #endregion
}

现在,您的Button.Click处理程序中的要求可以是:

private void ButtonBase_OnClick(object sender, RoutedEventArgs e) {
  var storyBoard = new Storyboard { Duration = new Duration(new TimeSpan(0, 0, 0, 0, 250)) };

  // Top
  var aniTop = new DoubleAnimationUsingKeyFrames { Duration = new Duration(new TimeSpan(0, 0, 0, 0, 250)) };
  aniTop.KeyFrames.Add(new EasingDoubleKeyFrame(Top, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, 00))));
  aniTop.KeyFrames.Add(new EasingDoubleKeyFrame(10, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, 250))));
  Storyboard.SetTarget(aniTop, this);
  Storyboard.SetTargetProperty(aniTop, new PropertyPath(NativeWindowSizeManager.NativeWindowTopProperty));
  storyBoard.Children.Add(aniTop);

  // Left
  var aniLeft = new DoubleAnimationUsingKeyFrames { Duration = new Duration(new TimeSpan(0, 0, 0, 0, 250)) };
  aniLeft.KeyFrames.Add(new EasingDoubleKeyFrame(Left, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, 00))));
  aniLeft.KeyFrames.Add(new EasingDoubleKeyFrame(10, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, 250))));
  Storyboard.SetTarget(aniLeft, this);
  Storyboard.SetTargetProperty(aniLeft, new PropertyPath(NativeWindowSizeManager.NativeWindowLeftProperty));
  storyBoard.Children.Add(aniLeft);
  storyBoard.Begin();
}

并且在所有上述情况下它应该每次都能正常工作。

NativeWindowSizeManager还有一个NativeWindowWidthNativeWindowHeight,可以重新调整大小,或者像我的情况一样设置窗口重新调整大小,同时以当前窗口屏幕为中心。

您可以针对您的用例获取此项目的演示:Here

答案 1 :(得分:1)

看起来像一个错字。很可能是因为您的xAnimation动画Window.TopyAnimation动画Window.Left

Storyboard.SetTargetProperty(xAnimation, new PropertyPath("(Window.Left)"));
Storyboard.SetTargetProperty(yAnimation, new PropertyPath("(Window.Top)"));

答案 2 :(得分:-1)

我找到了适合我需要的东西,但它非常脏,我想用一些不错的东西来改变实现。所以,如果有人知道为什么......请告诉:)

  public static void Translate(this Window element, double x, double y, TimeSpan duration)
  {
     var xAnimation = new DoubleAnimationUsingKeyFrames { Duration = duration };
     xAnimation.KeyFrames.Add(new LinearDoubleKeyFrame(element.Left, KeyTime.FromPercent(0.0)));
     xAnimation.KeyFrames.Add(new LinearDoubleKeyFrame(x, KeyTime.FromPercent(1.0)));

     var yAnimation = new DoubleAnimationUsingKeyFrames { Duration = duration };
     yAnimation.KeyFrames.Add(new LinearDoubleKeyFrame(element.Top, KeyTime.FromPercent(0.0)));
     yAnimation.KeyFrames.Add(new LinearDoubleKeyFrame(y, KeyTime.FromPercent(1.0)));

     Storyboard.SetTargetProperty(xAnimation, new PropertyPath("(Window.Left)"));
     Storyboard.SetTargetProperty(yAnimation, new PropertyPath("(Window.Top)"));

     var storyboard = new Storyboard
     {
        Children = { yAnimation, xAnimation },
        Duration = duration,
        FillBehavior = FillBehavior.Stop,
     };

     storyboard.Completed += (sender, args) =>
     {
        storyboard.SkipToFill();
        storyboard.Remove(element);

        element.InvalidateProperty(Window.LeftProperty);
        element.InvalidateProperty(Window.TopProperty);

        if (Math.Abs(element.Left - x) > Double.Epsilon || Math.Abs(element.Top - y) > Double.Epsilon)
           Translate(element, x, y, TimeSpan.FromTicks(Math.Min(duration.Ticks / 2, 100)));
     };

     element.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, new Action(() => element.BeginStoryboard(storyboard)));
  }