'Keep window centered after SizeToContent smoothly

I have a WPF window that changes it's size over time due to SizeToContent="WidthAndHeight". Initially the WindowStartupLocation="CenterScreen" shows the window centered correctly, and after that I recenter it with:

Private Sub Window_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs) Handles Me.SizeChanged
  Me.Top = (SystemParameters.WorkArea.Height - e.NewSize.Height) / 2
  Me.Left = (SystemParameters.WorkArea.Width - e.NewSize.Width) / 2
End Sub

But it produces a "jump" as the window is resized first and centered after.

Is there any way of doing it smoothly?



Solution 1:[1]

This worked for me:

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
    base.OnRenderSizeChanged(sizeInfo);

    //Calculate half of the offset to move the form

    if (sizeInfo.HeightChanged)
        this.Top += (sizeInfo.PreviousSize.Height - sizeInfo.NewSize.Height) / 2;

    if (sizeInfo.WidthChanged)
        this.Left += (sizeInfo.PreviousSize.Width - sizeInfo.NewSize.Width) / 2;
}

Solution 2:[2]

Instead of setting Me.Top and Me.Left directly you can use a TranslateTransform to animate position change.

public static void MoveTo(this UIElement target, double newX, double newY)
{
    var top = Canvas.GetTop(target);
    var left = Canvas.GetLeft(target);
    TranslateTransform trans = new TranslateTransform();
    target.RenderTransform = trans;
    DoubleAnimation anim1 = new DoubleAnimation(top, newY - top, TimeSpan.FromSeconds(10));
    DoubleAnimation anim2 = new DoubleAnimation(left, newX - left, TimeSpan.FromSeconds(10));
    trans.BeginAnimation(TranslateTransform.XProperty,anim1);
    trans.BeginAnimation(TranslateTransform.YProperty,anim2);
}

Code source: WPF. Easiest way to move Image to (X,Y) programmatically?

Solution 3:[3]

Thanks for posting this. My scenario is that I have a Dialog Window that resized when the ViewModel loaded which specifies its Height and Width. I used your example but modified it a bit to capture the Owner Window then I recenter.

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
    base.OnRenderSizeChanged(sizeInfo);

    //Calculate half of the owner to move the form
    Window owner = Owner as Window;

    this.Top = (owner.Height / 2) - (this.Height / 2);
    this.Left = (owner.Width / 2) - (this.Width / 2);
}

Solution 4:[4]

This solution is based on a combination of Imran's solution and Andras's solution.

This was written in .Net 6.0 with nullable reference types - so some tweaking might be necessary for .Net Framework 4.8 (and older).

This code basically keeps the window centered on it's original center even when the dynamic content causes the window to grow or shrink. Additionally, instead of jumping to the new location, it animates to it. Currently I have it set to 0.5 seconds. Anything slower than that and it felt clunky.

One thing this solution (and none of the others) account for is screen bounds. So you might want to consider putting in logic that A) caps max bounds based on the current screen the window is on, B) while keeping it 'centered' on it's current center on resize, don't let it edges go off a screen bound. Course that's a much more complex solution, and you could always ignore both of those slight problems and just blame the end user if they let it go outside their screen bounds.

protected override void OnRenderSizeChanged(SizeChangedInfo info)
{
    base.OnRenderSizeChanged(info);

    // This starts as false, and gets set to true in the Window.Loaded event.
    // It gets set back to false during the window closing event.
    // The window I am using this on has the min/max buttons disabled.
    // If you allow min/max, you might want to override the window state changed event and 
    // set this to false there as well.
    if (!this.canAnimate) return;

    DoubleAnimation? animT = null;
    DoubleAnimation? animL = null;

    if (info.HeightChanged)
        animT = new
                (
                    this.Top,
                    this.Top + ((info.PreviousSize.Height - info.NewSize.Height) / 2),
                    TimeSpan.FromSeconds(0.5)
                )
        {
            // !Important: Animation coerces the dependency property values. If you don't
            // specify Stop as the fill behavior, the coerced property will always report
            // the wrong value if you access it directly. IE, let's say the window is at
            // Y = 100, and the growth animation is going to cause it to be at 90.
            // The user then moves the window and now the true value is 150. When 
            // accessing this.Top the value will constantly be stuck at 90 - that is if you
            // don't specify FillBehavior = Stop.
            FillBehavior = FillBehavior.Stop
        };

    if (info.WidthChanged)
        animL = new
                (
                    this.Left,
                    this.Left + ((info.PreviousSize.Width - info.NewSize.Width) / 2),
                    TimeSpan.FromSeconds(0.5)
                )
        {
            FillBehavior = FillBehavior.Stop
        };

    if (animT is not null)
        this.BeginAnimation(TopProperty, animT);

    if (animL is not null)
        this.BeginAnimation(LeftProperty, animL);

}

Solution 5:[5]

private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (e.HeightChanged)
            Top += (e.PreviousSize.Height - e.NewSize.Height) / 2;
        if (e.WidthChanged)
            Left += (e.PreviousSize.Width - e.NewSize.Width) / 2;
    }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Crono
Solution 2 Community
Solution 3 Felipe Augusto
Solution 4 B.O.B.
Solution 5 Mohamed