6

我写了一个 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));
      }
   }
4

3 回答 3

4

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允许重新调整大小以进行动画处理,或者像我的情况一样在以当前窗口屏幕为中心的同时为窗口重新调整大小设置动画。

您可以为您的用例获取此项目的演示:这里

于 2013-06-18T15:18:51.923 回答
1

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

Storyboard.SetTargetProperty(xAnimation, new PropertyPath("(Window.Left)"));
Storyboard.SetTargetProperty(yAnimation, new PropertyPath("(Window.Top)"));
于 2013-06-18T10:26:42.543 回答
-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)));
  }
于 2013-06-18T14:22:29.810 回答