【问题标题】:Why does my WPF Translate Animation stop before completing?为什么我的 WPF 翻译动画在完成之前停止?
【发布时间】:2013-06-14 12:29:47
【问题描述】:

我写了一个 WindowExtension,它应该为一个窗口提供一个简单的翻译动画。但是这个动画总是在它到达目标坐标之前停止。谁能给我一个建议为什么?

最好的问候 克里斯

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

【问题讨论】:

    标签: c# wpf animation storyboard


    【解决方案1】:

    WPF 窗口定位/调整大小与它的 DPI 无关缩放一直是我的一个问题(尤其是当您想要根据监视器 DPI 和多监视器设置平滑地为移动/大小更改设置动画时)

    我确实编写了一个自定义助手类来帮助设置窗口尺寸的动画,这也可能对您有所帮助。

    主类(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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 哦!没错,那是一个错字......我纠正了我的错误,但我仍然得到相同的结果:(
      • 嗯,很奇怪,在我的情况下是不一致的。我试过删除Completed 事件,改为DoubleAnimation。甚至用固定的To 值在 XAML 中编写了相同的动画,有时它会到达目标点,有时它不会。
      • 首先感谢测试问题。我还尝试在 CompletedEventHandler 中递归调用 Translate 方法。 (当然仅用于测试目的)我发现了一些有趣的东西(奇怪)。我添加了一个检查目标坐标是否相等以及窗口的实际左侧和顶部值。它们是相等的,但窗口不在这个位置。
      【解决方案3】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-15
        • 2020-08-07
        • 1970-01-01
        相关资源
        最近更新 更多