Real-time window resizing (WM_SIZING): OnResize() short circuits too early

-------- Context: --------

I’d like to dynamically resize the client window / backbuffer as the user resizes the window.

I am doing so by hooking into these events:

Form gameForm = (Form)Form.FromHandle(game.Window.Handle);
gameForm.ResizeBegin += GameForm_ResizeBegin;
gameForm.Resize += GameForm_Resize;
gameForm.ResizeEnd += GameForm_ResizeEnd;

I grab the client window size like so:

private void GameForm_Resize(object sender, EventArgs e)
{
    Form gameForm = (Form)sender;
    System.Drawing.Size newSize = gameForm.ClientSize;

    ... update GraphicsDeviceManager ...
    ... call ApplyChanges() ...
}

-------- Problem: --------

However, MonoGame has window centering logic for initialization, that kicks in for:

  1. initialization (the window is centered on the screen at launch),
  2. but also kicks in when it shouldn’t: on the 1st (but not subsequent) user resizing.

This causes whatever corner/edge the user is moving, to move as it should – but the enforced window centering readjusts it (which then causes a second move, since the mouse drags the corner that was just moved back to the mouse!) – which interestingly enough makes the opposite edge also resize (much like MacOS). Kind of cool – perhaps even desired, but it is inconsistent (it only does so on the 1st resize, and never again).

-------- Root Cause: --------

WinFormsGameWindow.cs:
Has a Boolean to indicate if we moved the window:

// true if window position was moved either through code 
// or by dragging/resizing the form
private bool _wasMoved;

This is set false so that on launch, the window is forcibly resized inside of

internal void ChangeClientSize(Size clientBounds)

and ignored otherwise (it lets the user resize the window). (It’s perhaps a little odd that ChangeClientSize has the power to reject a user sizing, just so it can handle the launch positioning, but I follow the logic.)

The problem is in the following function, which rejects the WM_SIZING / Form.IsResizing handling, and does not allow _wasMoved to be reset. Only after resizing is done, is it reset – “OK, launch complete, no need to forcibly center the window; allow the user to resize now.

private void OnResize(object sender, EventArgs eventArgs)`
{
    if (_switchingFullScreen || Form.IsResizing) // <<<<<<<< SHORT CIRCUIT
        return;

    // this event can be triggered when moving the window through Windows hotkeys
    // in that case we should no longer center the window after resize
        if (_lastFormState == Form.WindowState)
            _wasMoved = true;  // <<<<<<<<<<<<< NOT SET DURING 1ST WM_SIZING
...

-------- Solution: --------

If OnResize didn’t short circuit on Form.IsResizing, I think this would not only solve this problem, but it would likely allow dynamic resizing by default.

I uploaded a YouTube video that showcases the bug:

  1. First edge/corner resize = noticeably updates the opposite corner as well.
  2. Subsequent resizing = works as intended.

This is due to the delayed setting of: private bool _wasMoved;, which is also delayed (not set) if you maximize / restore/ minimize the window. Thus you can maximize, then restore, then resize a corner, and the opposite corner moves on the first attempt.

I opened a bug report here:

I have this example for MonoGame:

1 Like

The framework will resize the backbuffer automatically when you resize the window.
You need to set AllowUserResizing in GameWindow.

Are you trying to do something other than that?

nkast, thanks for the reply.

I am using AllowUserResizing (otherwise, the OS blocks any attempt to resize). But, if I only use this, and do nothing else, the game does not “see” the real-time resize. The framework appears to internally handle the WM_SIZE but not the WM_SIZING. The source code appears to short circuit real-time WM_SIZING events. Thus, resizing the window merely shows a stretched version of the old resolution until you stop resizing, and then it snaps into the new resolution.

If you are OK with this, then that works fine.
However, I am trying to show a dynamic resize:

When the resizing events stream in during a single user resize, I want the game to “see” the ever-changing client window changes, and update accordingly. This allows the user to see results in real-time. In this case, you never see the game window stretched – it should be as you expect while holding the mouse button resize down.

Please see the video above for an example.

Thanks Apostolique

Looks like your code:

Is simply doing this on WM_SIZING:

_graphics.PreferredBackBufferWidth = width;
_graphics.PreferredBackBufferHeight = height;
_graphics.ApplyChanges();

You are using SDL. If the code is similar to the Windows Forms version, then the same issue should occur (have you noticed if resize resizes both corners on the first attempt?). If not, the SDL implementation must not have the same _wasMoved boolean. (It is a tough job for frameworks to support so many backend technologies!)