Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my WPF Translate Animation stop before completing?

i wrote an WindowExtension that should provide a simple Translate animation to a window. But this Animation does always stop before it gets to the target coordinates. Can anybody give me an advice why?

Best regards chris

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

It can be simply tested in a WPF Window like this:

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

      private void Button_Click(object sender, RoutedEventArgs e)
      {
         this.Translate(10,10, TimeSpan.FromMilliseconds(250));
      }
   }
like image 541
The Chris Avatar asked Jun 18 '13 10:06

The Chris


2 Answers

Looks like a typo. Most likely it's because your xAnimation animates Window.Top and yAnimation animates Window.Left:

Storyboard.SetTargetProperty(xAnimation, new PropertyPath("(Window.Left)"));
Storyboard.SetTargetProperty(yAnimation, new PropertyPath("(Window.Top)"));
like image 20
dkozl Avatar answered Oct 17 '22 08:10

dkozl


WPF Window positioning / re-sizing with it's DPI independent scaling had always been an issue for me(especially when you want to animate the movement / size changes smoothly respecting monitor DPI and multi-monitor setups)

I did write a custom helper class to help animate Window dimensions which might help you as well.

The main class(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
}

Now for your requirement in your Button.Click handler you could have something like:

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

and it should work fine every-time under all the above mentioned cases.

NativeWindowSizeManager also has a NativeWindowWidth and NativeWindowHeight allowing re-sizing to be animated or like my case to animate a Window re-size while centering to the current window screen.

You can get a demo of this project for your use case: Here

like image 156
Viv Avatar answered Oct 17 '22 06:10

Viv