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