Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

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));
      }
   }
share|improve this question

3 Answers 3

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

share|improve this answer

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)"));
share|improve this answer
    
Oh! Thats true, thats was a typo... i corrected my mistake, but i stil get the same result :( –  ChrisTTian667 Jun 18 '13 at 10:55
1  
Hmmmm, very strange and it's inconsistent in my case. I've tried removing Completed event, changing to DoubleAnimation. Even wrote same animations in XAML with fixed To values and sometimes it reaches destination point and sometimes it doesn't. –  dkozl Jun 18 '13 at 12:58
    
First of all big thx for testing the Problem. I also tried calling the Translate method recursively in the CompletedEventHandler. (of course for testing purposes only) There i found something funny (strange). I added a check for equality of the target coordinates and the actual left and top value of the window. They were equal, but the window was not located at this position. –  ChrisTTian667 Jun 18 '13 at 13:32

I found something that works for my needs, but it is very dirty and i would like to change the implementation with something nice. So, if anybody knows why... please tell :)

  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)));
  }
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.