5

I'd like to animate the width and height of a wpf window. I've tried the following, which unfortunately just animates the width... the height of the window never changes.

I'm sure I've missed something silly and hope that by posting here someone will see my error!

Here's the code behind for a simple window with a button I've wired up do to the resize:

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

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.AnimateWindowSize(ActualWidth + 200, ActualHeight + 200);
    }
}

And here is the animation code I've written as an extension method so it could be applied to any window...

public static class WindowUtilties
{
    public static void AnimateWindowSize(this Window target, double newWidth, double newHeight)
    {
        var sb = new Storyboard {Duration = new Duration(new TimeSpan(0, 0, 0, 0, 200))};

        var aniWidth = new DoubleAnimationUsingKeyFrames();
        var aniHeight = new DoubleAnimationUsingKeyFrames();

        aniWidth.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 200));
        aniHeight.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 200));

        aniHeight.KeyFrames.Add(new EasingDoubleKeyFrame(target.ActualHeight, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, 00))));
        aniHeight.KeyFrames.Add(new EasingDoubleKeyFrame(newHeight, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, 200))));
        aniWidth.KeyFrames.Add(new EasingDoubleKeyFrame(target.ActualWidth, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, 00))));
        aniWidth.KeyFrames.Add(new EasingDoubleKeyFrame(newWidth, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, 200))));

        Storyboard.SetTarget(aniWidth, target);
        Storyboard.SetTargetProperty(aniWidth, new PropertyPath(Window.WidthProperty));

        Storyboard.SetTarget(aniHeight, target);
        Storyboard.SetTargetProperty(aniHeight, new PropertyPath(Window.HeightProperty));

        sb.Children.Add(aniWidth);
        sb.Children.Add(aniHeight);

        sb.Begin();
    }
}

Thanks in advance for any help.

Cameron Peters
  • 2,502
  • 2
  • 27
  • 34

3 Answers3

2

After Joe's comment of using pinvoke and dependency properties I ended up with this code. I'll apologize now if the code is long and I shouldn't have put it all here. The math is not perfect on the sizes. There is quite a difference between the WPF Actual(Height/Width) versus the Rect.Height/Width, it may take some calculations to get the exact sizes you want.

This was added to the MainWindow class

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int X;
    public int Y;
    public int Width;
    public int Height;
}

public enum SpecialWindowHandles
{
    HWND_TOP = 0,
    HWND_BOTTOM = 1,
    HWND_TOPMOST = -1,
    HWND_NOTOPMOST = -2
}

[DllImport("user32.dll", SetLastError = true)]
static extern bool GetWindowRect(IntPtr hWnd, ref RECT Rect);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

public static readonly DependencyProperty WindowHeightAnimationProperty = DependencyProperty.Register("WindowHeightAnimation", typeof(double),
                                                                                            typeof(MainWindow), new PropertyMetadata(OnWindowHeightAnimationChanged));

private static void OnWindowHeightAnimationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var window = d as Window;

    if (window != null)
    {
        IntPtr handle = new WindowInteropHelper(window).Handle;
        var rect = new RECT();
        if (GetWindowRect(handle, ref rect))
        {
            rect.X = (int)window.Left;
            rect.Y = (int)window.Top;

            rect.Width = (int)window.ActualWidth;
            rect.Height = (int)(double)e.NewValue;  // double casting from object to double to int

            SetWindowPos(handle, new IntPtr((int)SpecialWindowHandles.HWND_TOP), rect.X, rect.Y, rect.Width, rect.Height, (uint)SWP.SHOWWINDOW);
        }
    }
}

public double WindowHeightAnimation
{
    get { return (double)GetValue(WindowHeightAnimationProperty); }
    set { SetValue(WindowHeightAnimationProperty, value); }
}

public static readonly DependencyProperty WindowWidthAnimationProperty = DependencyProperty.Register("WindowWidthAnimation", typeof(double),
                                                                                            typeof(MainWindow), new PropertyMetadata(OnWindowWidthAnimationChanged));

private static void OnWindowWidthAnimationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var window = d as Window;

    if (window != null)
    {
        IntPtr handle = new WindowInteropHelper(window).Handle;
        var rect = new RECT();
        if (GetWindowRect(handle, ref rect))
        {
            rect.X = (int)window.Left;
            rect.Y = (int) window.Top;
            var width = (int)(double)e.NewValue;
            rect.Width = width;
            rect.Height = (int) window.ActualHeight;

            SetWindowPos(handle, new IntPtr((int)SpecialWindowHandles.HWND_TOP), rect.X, rect.Y, rect.Width, rect.Height, (uint)SWP.SHOWWINDOW);
        }
    }
}

public double WindowWidthAnimation
{
    get { return (double)GetValue(WindowWidthAnimationProperty); }
    set { SetValue(WindowWidthAnimationProperty, value); }
}

private void GrowClick(object sender, RoutedEventArgs e)
{
    this.AnimateWindowSize(Width+200, Height+200);
}

/// <summary>
/// SetWindowPos Flags
/// </summary>
public static class SWP
{
    public static readonly int
    NOSIZE = 0x0001,
    NOMOVE = 0x0002,
    NOZORDER = 0x0004,
    NOREDRAW = 0x0008,
    NOACTIVATE = 0x0010,
    DRAWFRAME = 0x0020,
    FRAMECHANGED = 0x0020,
    SHOWWINDOW = 0x0040,
    HIDEWINDOW = 0x0080,
    NOCOPYBITS = 0x0100,
    NOOWNERZORDER = 0x0200,
    NOREPOSITION = 0x0200,
    NOSENDCHANGING = 0x0400,
    DEFERERASE = 0x2000,
    ASYNCWINDOWPOS = 0x4000;
}

And in the OP's code I changed the height and width target properties accordingly

Storyboard.SetTargetProperty(aniHeight, new PropertyPath(Window.HeightProperty));
Storyboard.SetTargetProperty(aniWidth, new PropertyPath(Window.WidthProperty));

to

Storyboard.SetTargetProperty(aniHeight, new PropertyPath(MainWindow.WindowHeightAnimationProperty));
Storyboard.SetTargetProperty(aniWidth, new PropertyPath(MainWindow.WindowWidthAnimationProperty));

Original answer:

From what I have found there is no problem with your code. When I changed the order in which I was adding the animations to the storyboard (sb.Children.Add) instance, I got the height animating with no width.

This leads me to believe that while the first animation is happening, the other animation becomes invalid.

All I could come up with is having them animate one after the other by having one animation be slightly longer than the other. The longer animation will occur once the first animation completed.

var sb = new Storyboard { Duration = new Duration(new TimeSpan(0, 0, 0, 0, 300)) };

var aniWidth = new DoubleAnimationUsingKeyFrames();
var aniHeight = new DoubleAnimationUsingKeyFrames();

aniWidth.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 300));
aniHeight.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 150));

aniHeight.KeyFrames.Add(new EasingDoubleKeyFrame(target.ActualHeight, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, 00))));
aniHeight.KeyFrames.Add(new EasingDoubleKeyFrame(newHeight, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, 150))));

aniWidth.KeyFrames.Add(new EasingDoubleKeyFrame(target.ActualWidth, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, 150))));
aniWidth.KeyFrames.Add(new EasingDoubleKeyFrame(newWidth, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, 300))));

Not even using XAML storyboards could I get both height and width of the window to resize simultaneously.

Shane Charles
  • 299
  • 1
  • 5
  • That's what I found too... I wondered if there might be a solution using paralleltimelines, but I couldn't get that to work either. This http://stackoverflow.com/questions/1769317/animate-window-resize-width-and-height-c-sharp-wpf?rq=1 seems like a hack but maybe that's the only way to make it work?? – Cameron Peters Sep 09 '12 at 03:24
  • Yes, it looks like it may have to be done manually. That link was a good find. – Shane Charles Sep 09 '12 at 12:18
  • 1
    Some of these top level properties on Window are a bit weird and don't behave the same way as other WPF properties because they're on the boundary of Win32. If you want to animate them together a WPFy way to do it would be to create an attached DependencyProperty on Window that under the covers p/invokes SetWindowPos on the backing HwndSource. I don't have a Windows dev environment available to write up the solution, so putting this as a comment instead of an answer. If you're doing this on WPF pre-4.0 there are additional issues to worry about. I'll write up a sln if I get a chance soon. – Joe Castro Sep 10 '12 at 00:34
  • @Joe Thanks. After reading your comment I started playing around and that works well for resizing a window using WPF animations. – Shane Charles Sep 10 '12 at 22:27
  • 1
    Glad you solved it :) The reason the math is a little funny is probably because your width and height are including the Window's chrome. p/invoking GetClientRect will probably help with that. Also the Win32 functions work in pixels instead of device independent pixels (dips) which at 96dpi are the same, but generally you need to be careful about high DPI and converting those. The only other change I'd suggest is DependencyProperty.RegisterAttached() to make it associated with the Window. It would also make it XAML friendly. – Joe Castro Sep 12 '12 at 00:20
0

The one suggestion with newer DPs seemed a bit overkill for me, especially since the solution acknowledged that it still wouldn't resize simultaneously. In my quick experimentation, adding a delay (via Task.Run()) of even 1ms achieved the final result (window resizes). This solution also does not resize simultaneously and so the animation isn't as elegant as it could be, but it 'works' in the end.

Tim Heuer
  • 4,301
  • 21
  • 20
0

I know its an old thread, but I found a workaround that seems to work "acceptably", at least tested in .net 7. Insted of animating windows properties, set size on content and define window size using SizeToContent="WidthAndHeight". Then animate the grid size. It's not smooth for sure (every frame leads to a redraw, because that's just how windows works and I doubt any solution would fix that), but at least its running both width/height in parallel.


<Window ... SizeToContent="WidthAndHeight">
    <Window.Resources>
        <Storyboard x:Key="MainStory">
            <DoubleAnimation Storyboard.TargetName="SizingGrid" Storyboard.TargetProperty="(Grid.Width)" Duration="00:00:01" To="1200"/>
            <DoubleAnimation Storyboard.TargetName="SizingGrid" Storyboard.TargetProperty="(Grid.Height)" Duration="00:00:01" To="800"/>
        </Storyboard>
    </Window.Resources>
    <Grid Name="SizingGrid" Width="640" Height="480">
        ...
    </Grid>
</Window>
mmix
  • 6,057
  • 3
  • 39
  • 65