ClientSizeChanged - Detect End of window drag for resize

The Window.ClientSizeChanged event works fine for detecting when the screen size is changing due to the user dragging from one of the window corners. However, I’d like to be able to determine when this “drag resize” operation has ended.

I don’t see any obvious events for it, and the MouseState for the left mouse button shows “Release” for the duration of the “drag resize” operation.

Does anyone have any suggestions on how I can accomplish this?

(The reason being is that my GUI looks all jumpy and wobbly when the screen resize gets called rapidly in succession)

What platform are you targeting and what OS are you running?

I thought the MG implementation worked as you expected on Windows (both DX and DesktopGL), but I could be wrong. I know on Mac/Linux the event is triggered every frame while the user is resizing. That’s a bit of a hard problem to solve, but maybe we could detect mouse release to figure it out, I actually hadn’t thought of that before.

Linux Mint (DesktopGL).

Yeah, I was hoping that I could track the mouse “pressed” and then “release” during the drag, but for some reason it stays in the “released” state for the duration of the drag.

It’s not a show stopper or anything. it just looks clunky as my UI elements sort of stutter around the page. I might be able to hide them or something for a few milliseconds after the last resize event.

If you find a decent solution to this problem, please let me know. I would love to get this fixed in MG itself.

My attempts thus far have fallen flat to resolve this. One blow to my efforts is that the resize event seems to trigger on mouse moves, but not on “mouse button released”.

I’m tempted to build MonoGame from source and see if I can add an “end resize event”, but I’m feeling a bit in over my head. I pulled the source down but looking over it all is a bit overwhelming. From what I did read, it looks like the events are triggered in SDL and not event MonoGame. Is that accurate?

This is the workaround solution I’m using for now. Its not ideal, but it works pretty smoothly and doesn’t seem to cause any trouble.

  1. Enable Window Resizing
  2. Trigger an “Event” every 200 elapsed milliseconds
  3. Check to see if the backbuffer size is different from the client window size
  4. If they’re different:
    a. Clamp the Client Window dims to a predetermined minimum
    b. Set the back buffer to the new dims
    c. Apply the changes

Oh sorry i read that wrong.

Ya that’s tricky… the mouse release isn’t detected for me on the window corner when resizing.

I would say try this.

We have 3 methods i would consider that we could use to detect this thru logic then act on.

update()
Resize() the callback registered to the clientsizedchange event.
JustFinishedClientSizeChange(); // something we want to fire when all done.

make two class scope bools

IsResizing = false
WasResizing = false;

in Resize() have the callback method set…

IsResizing = to true;

Then in the Update method. query this first bool with the other…

if( WasResizing == true && IsResizing == false )
{ 
WasResizing = false; 
JustFinishedClientSizeChange();
}

Then after that which is the real trick this Has to come after the above.

if( IsResizing){ WasResizing = true; IsResizing = false; }

This is basically a two pass update trick on the first pass we set up for the second pass.

im not sure that will work since it depends on the callback being constantly fired.
But in the case it does, maybe in the base update base draw under monogame you might be able to rig up something like that which works transparently.

willmotil,
I’m thinking that the Resize() event might not happen frequently/reliably enough to keep the IsResizing flag true when the Update() call checks it. If Update() gets called twice before Resize() then in each occurrence JustFinishedClientSizeChange() will be called. We could always add some delays and only execute JustFinishedClientSizeChange() if the IsResizing flag has been true for a certain span of time, but that really doesn’t get quite us out of the woods.

The crux here is that unless we know the user released the mouse button, it’s all just tricks with timing. They could drag some, pause, hold the button down for 2000ms, and then continue dragging.

This is what creates the main problem where we can get into a state where MonoGame reports the Window.ClientBounds as values that are different from what’s actually on the screen.

Its hard to describe and only seems to happen in Linux, but I’ll take a stab at it though:

Setup

  1. Set a MIN_SIZE const to 500x500
  2. Subscribe to: Window.ClientSizeChanged (in Linux this is fired repeatedly as the mouse moves)
  3. For each execution,
    a) if Window.ClientBounds >= MIN_SIZE then set backbuffer size to client window size
    b) if Window.ClientBounds < MIN_SIZE set backbuffer to MIN_SIZE size and apply changes

This almost works, unless the user sizes below MIN_SIZE and then delays releasing the mouse button after the resize (no additional mouse movement). In this case, Window.ClientBounds will report MIN_SIZE as its value, but you’ll still see the smaller window on the screen.

I put some sample code on Stack Exchange that demos the problem if you want to give it a go. Bear in mind this only seems to happen in Linux: MonoGame - Resize Client Window End - Linux DesktopGL

I do have a (new) reliable workaround for this, but it’s not ideal.

Maybe a simpler way to put it is this:


In Linux

If the user drags the window to W1,H1 and the code sets the size to W2,H2 while the user still has the mouse button down, SDL ignores the code and leaves the window rendered at W1,H1. However, Window.ClientBounds will be set to W2,H2.


This means you can’t detect the discrepancy or tell what’s actually on screen (I tried to access SDL directly to get the screen size, but its an internal class).

The workaround I used is to just blindly set the window size again a few moments after each resize (technically every 400ms for 2000ms).

I may not fully understand the problem after all.

I don’t know if this will help but here is my old game1 window testing class i made it when the resizing and fullscreen was really messed up and broken to see what the differences were between like dx and gl ect… This is like the 13th version.

It’s basically just straight code in a game1 its meant to be run with the console window on it tells you everything that is changing between a windows resize after you do it.

If you want to try it just copy paste the entire thing into a game1 class. And change it’s namespace to your namespace.

I posted it in two parts Both classes go in the game1.cs file it got pretty big after it auto runs it gives a little option menu or hit f4.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace MgFsWindowingTests13
{
    // this is actually 13
    public class Game1 : Game
    {

        // change to false to see all variable info per change
        bool just_display_changes = true;

        // this only works if just_display_changes is true
        bool display_unchanged_stuff = false;

        // runs a scripted test at start
        bool run_test_script = true;

        // show code in test
        bool show_code = true;

        // scripted and user pause amount
        int input_pause_amount = 10;

        GraphicsDeviceManager gdm;
        GraphicsDevice gd;
        SpriteBatch spriteBatch;
        KeyboardState kbs;
        Point started = new Point(GraphicsDeviceManager.DefaultBackBufferWidth, GraphicsDeviceManager.DefaultBackBufferHeight);
        Point preset = new Point(1200, 720);
        Point preferred = new Point(800, 600);
        private bool was_resize_called = false;
        private bool was_apply_called = false;
        private int onresizecounter = 0;
        private int wasapplycalledcounter = 0;

        List<DisplayMode> bestDisplayModes = new List<DisplayMode>();
        List<DisplayMode> supportedDisplayModes = new List<DisplayMode>();
        
        DisplayMode starteddm;
        int startedbestdmindex = -1;

        int auto_script_cmd_value = -1;
        int auto_script_cmd_execution = -1;

        static string nline = Environment.NewLine;
        static string tab4 = "    ";
        static string dline = "__________________________________________________________________________________________________________________________";
        static string ndnline = nline + dline + nline + nline;

        public void RunAutoScript()
        {
            auto_script_cmd_value = -1;
            if (run_test_script && auto_script_cmd_execution < 11 && pause < 0)
            {
                auto_script_cmd_execution++;
                switch (auto_script_cmd_execution)
                {
                    case 0:
                        Console.WriteLine("");
                        input_pause_amount = input_pause_amount * 10;
                        pause = 540;
                        Console.WriteLine(nline + " Auto Script Commands About to Start in 5 Seconds Please Wait pause amount changed to " + input_pause_amount + nline + nline + nline);
                        break;
                    case 1: // F2 expand window to preset value
                        auto_script_cmd_value = 2;
                        break;
                    case 2: // f10 goto fullscreen
                        auto_script_cmd_value = 10;
                        break;
                    case 3: // f9 next fullscreen mode change
                        auto_script_cmd_value = 9;
                        break;
                    case 4: // f9 next fullscreen mode change
                        auto_script_cmd_value = 9;
                        break;
                    case 5: // f9 next fullscreen mode change
                        auto_script_cmd_value = 9;
                        break;
                    case 6: // f11 drop out of fullscreen with gdm apply
                        auto_script_cmd_value = 11;
                        break;
                    case 7: // F1 shrink window to started value
                        auto_script_cmd_value = 1;
                        break;
                    case 8: // f12 toggle fullscreen
                        auto_script_cmd_value = 12;
                        break;
                    case 9: // f12 toggle fullscreen
                        auto_script_cmd_value = 12;
                        break;
                    case 10: // f3 set backbuffer thru presentation parameters
                        auto_script_cmd_value = 3;
                        break;
                    default: // set pause amound back to normal
                        auto_script_cmd_value = -1;
                        input_pause_amount = (int)(input_pause_amount * .10f);
                        Console.WriteLine("");
                        Console.WriteLine("**********************************");
                        Console.WriteLine("***Auto Script Commands Ended*****");
                        Console.WriteLine("**********************************");
                        nextdisplaymodeindex = -1;
                        DisplayOptions();
                        break;
                }
            }
        }

        public Game1()
        {
            IsMouseVisible = true;
            gdm = new GraphicsDeviceManager(this);
            Console.WriteLine(nline + "*** Game1 Constructor()");
            Console.WriteLine
                (
                nline + " the below changes take effect in this constructor" +
                nline + " gdm.PreferredBackBufferWidth  = preferred.X; " + preferred.X +
                nline + " gdm.PreferredBackBufferHeight = preferred.Y; " + preferred.Y +
                nline + " allow user resizing and register a callback for window.clientsizechanged"
                );
            gdm.PreferredBackBufferWidth = preferred.X;
            gdm.PreferredBackBufferHeight = preferred.Y;
            gdm.PreferredBackBufferFormat = SurfaceFormat.Color;
            Window.AllowUserResizing = true;
            Window.AllowAltF4 = true;


            Window.ClientSizeChanged += OnResize;

            Content.RootDirectory = "Content";
            //gdm.SynchronizeWithVerticalRetrace = false;
            //gdm.IsFullScreen = true;
            //gdm.PreparingDeviceSettings += GdmPreparingDeviceSettings;
        }

        private void ApplyTheChanges()
        {
            Console.WriteLine(nline + "  User or Script calling My ApplyTheChanges() which will make the call ");
            was_apply_called = true;
            wasapplycalledcounter++;
            Console.WriteLine("  Before gdm.ApplyingChanges ");
            DisplayGdmGdWindowRecordedInfo();
            Console.WriteLine(nline + "  Now we will call...  gdm.ApplyChanges()...");
            gdm.ApplyChanges();
            if (was_resize_called == true)
            {
                Console.WriteLine("  After  gdm.ApplyingChanges was called ... OnResize Was called from gdm.ApplyChanges");
            }
            else
            {
                Console.WriteLine("  After  gdm.ApplyingChanges was called ... OnResize was NOT Yet called");
            }
            DisplayGdmGdWindowRecordedInfo();
            Console.WriteLine("   ...leaving my ApplyTheChanges method");
        }

        public void OnResize(object sender, EventArgs e)
        {
            Console.WriteLine(nline + "  \"A Resize Has Just Occured\"  Window.ClientSizeChanged has fired...OnResize");
            onresizecounter++;
            if (was_apply_called == false)
            {
                Console.WriteLine("  Within OnResize.... my ApplyTheChanges() Was NOT called");
            }
            else
            {
                Console.WriteLine("  Within OnResize.... my ApplyTheChanges() Was called");
            }
            was_resize_called = true;
            DisplayGdmGdWindowRecordedInfo();
        }

        protected override void Initialize()
        {
            Console.WriteLine(nline + "*** Game Initialize()");
            gd = gdm.GraphicsDevice;
            ListSupportedDisplayModes();
            //Window.Title = gd.Adapter.DeviceName; // not yet implemented in monogame
            //Window.Title += gd.Adapter.Description; // not yet implemented in monogame
            base.Initialize();
        }

        protected override void LoadContent()
        {
            Console.WriteLine(nline + "*** Game LoadContent()");
            gd = GraphicsDevice;
            spriteBatch = new SpriteBatch(GraphicsDevice);
            // get current display mode and find best matches
            starteddm = gd.Adapter.CurrentDisplayMode;
            bestDisplayModes = FindBestDisplayModes();
            startedbestdmindex = FindCurrentOrClosestDisplayModeInList(bestDisplayModes, starteddm);
            // printout what we are doing
            Console.WriteLine(nline + " started backbuffer was " + started + " set to prefered " + preferred);
            DisplayGdmGdWindowRecordedInfo();
            Console.WriteLine(nline + " Vtrace period gdm.GraphicsDevice.PresentationParameters.PresentationInterval " + gdm.GraphicsDevice.PresentationParameters.PresentationInterval);
            Console.WriteLine(
                nline + "__________________________________" +
                nline + "*** Leaving Game LoadContent()" +
                nline + "__________________________________" +
                nline + "*** Game Running" +
                nline + "__________________________________" + 
                nline
                );
        }

        public void DisplayOptions()
        {
            Console.WriteLine(
                nline + "_____Available Options_____________" +
                nline +
                nline + " F1  Sets started " + started +
                nline + " F2  Sets preset " + preset +
                nline + " F3  Sets the presentation backbuffer thru the graphics device to 400x400 " +
                nline + " F4  List supported display modes" +
                nline + " F5  " +
                nline + " F6  Goto fullscreen with prefferd backbuffer change" +
                nline + " F7  Back out of fullscreen with backbuffer change" +
                nline + " F8  " +
                nline + " F9  Next display mode  current=" + curdisplaymodeindex.ToString() + " next=" + nextdisplaymodeindex.ToString() +
                nline + " F10 Fullscreen true " +
                nline + " F11 Fullscreen false " +
                nline + " F12 Toggle fullscreen" +
                nline + " Alt F4 Exit" +
                nline + "___________________________________"
                );
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            was_resize_called = false;
            was_apply_called = false;

            kbs = Keyboard.GetState();

            if (kbs.IsKeyDown(Keys.Escape))
                this.Exit();

            // switch preferred

            if ((kbs.IsKeyDown(Keys.F1) || auto_script_cmd_value == 1) && Paused == false)
            {
                Console.WriteLine(ndnline + "INPUT  F1   Setting gdm preferred backbuffer" + started + nline);
                SetPreferred(started.X, started.Y);
                ApplyTheChanges();
            }
            if ((kbs.IsKeyDown(Keys.F2) || auto_script_cmd_value == 2) && Paused == false)
            {
                Console.WriteLine(ndnline + "INPUT  F2   Setting gdm preferred backbuffer " + preset + nline);
                SetPreferred(preset.X, preset.Y);
                ApplyTheChanges();
            }

            if ((kbs.IsKeyDown(Keys.F3) || auto_script_cmd_value == 3) && Paused == false)
            {
                Console.WriteLine(ndnline + "INPUT  F3   set backbuffer thru gd presentation parameters 400x400" + nline);
                SetBackBufferThruGdPresentationParams(400, 400);
                ApplyTheChanges();
            }
            if ((kbs.IsKeyDown(Keys.F4) || auto_script_cmd_value == 4) && Paused == false)
            {
                Console.WriteLine(ndnline + "INPUT  F4   List supported display modes" + nline);
                Console.WriteLine("     ___________________________" + nline);
                Console.WriteLine("     Current display mode index  " + FindCurrentDisplayModeInList().ToString() + nline);
                ListSupportedDisplayModes();
                FindBestDisplayModes();
                DisplayOptions();
            }
            if ((kbs.IsKeyDown(Keys.F5) || auto_script_cmd_value == 5) && Paused == false)
            {
                Console.WriteLine(ndnline + "INPUT  F5   " + nline);


            }
            if ((kbs.IsKeyDown(Keys.F6) || auto_script_cmd_value == 6) && Paused == false)
            {
                Console.WriteLine(ndnline + "INPUT  F6   Go into fullscreen -- Setting gdm preferred backbuffer" + started + nline);
                SetFullScreen(true);
                SetPreferred(gd.DisplayMode.Width, gd.DisplayMode.Height);
                ApplyTheChanges();
            }
            if ((kbs.IsKeyDown(Keys.F7) || auto_script_cmd_value == 7) && Paused == false)
            {
                Console.WriteLine(ndnline + "INPUT  F7   Backout of fullscreen -- Setting gdm preferred backbuffer" + started + nline);
                SetFullScreen(false);
                SetPreferred(started.X, started.Y);
                ApplyTheChanges();
            }

            if ((kbs.IsKeyDown(Keys.F8) || auto_script_cmd_value == 8) && Paused == false)
            {
                Console.WriteLine(ndnline + "INPUT  F8   " + nline);


            }

            // attempt to switch to different display mode resolutions

            if ((kbs.IsKeyDown(Keys.F9) || auto_script_cmd_value == 9) && Paused == false)
            {
                Console.WriteLine(ndnline + "INPUT  F9   attempting next displaymode" + nline);
                Console.WriteLine(ndnline + "Current display mode index  " + FindCurrentDisplayModeInList().ToString() + nline);
                ChangeToNextDisplayMode(true);
                //ApplyTheChanges();
            }

            // different ways to just call fullscreen

            if ((kbs.IsKeyDown(Keys.F10) || auto_script_cmd_value == 10) && Paused == false)
            {
                Console.WriteLine(ndnline + "INPUT  F10   set to fullscreen via gdm applychanges" + nline);
                SetFullScreen(true);
                ApplyTheChanges();
            }
            if ((kbs.IsKeyDown(Keys.F11) || auto_script_cmd_value == 11) && Paused == false)
            {
                Console.WriteLine(ndnline + "INPUT  F11   set to windowed   via gdm applychanges" + nline);
                SetFullScreen(false);
                ApplyTheChanges();
            }
            if ((kbs.IsKeyDown(Keys.F12) || auto_script_cmd_value == 12) && Paused == false)
            {
                Console.WriteLine(ndnline + "INPUT  F12   toggle fullscreen (their is no call to apply changes like this)" + nline);
                TheFullScreenToggle();
                // no apply changes is called for this normally in xna or xna calls it on its own
                ApplyTheChanges();
            }

            CheckForDoubleCalls();

            RunAutoScript();

            ReducePause();

            base.Update(gameTime);
        }

        private void CheckForDoubleCalls()
        {
            if (wasapplycalledcounter > 1)
            {
                Console.WriteLine(nline + " !!! Note !!! ApplyTheChanges was fired more then once ... this for the test so i dont do it by accident");
            }
            if (onresizecounter > 1)
            {
                Console.WriteLine(nline + " !!! Note !!! OnResize was fired more then once... this would only be normal if applychanges was called more then once");
            }
            if(wasapplycalledcounter > 0 || onresizecounter > 0)
            {
                Console.WriteLine("     onresizecounter " + onresizecounter.ToString());
                Console.WriteLine("     wasapplycalledcounter " + wasapplycalledcounter.ToString());
                onresizecounter = 0;
                wasapplycalledcounter = 0;
            }
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            base.Draw(gameTime);
        }

        private void DisplayGdmGdWindowRecordedInfo()
        {
            gd = gdm.GraphicsDevice; // just to show gd is in fact current
            new RecordDiff(gdm, gd, Window);
            if (just_display_changes && RecordDiff.EnoughRecords)
            {
                RecordDiff.DisplayDifferences(display_unchanged_stuff);
            }
            else
            {
                RecordDiff.DisplayCurrentStatus(gdm, gd, Window);
            }
        }

        public void SetPreferred(int w, int h)
        {
            if (show_code)
            {
                Console.WriteLine(
                    tab4 + "Code" +
                    nline + tab4 + "preferred.X = w;" +
                    nline + tab4 + "preferred.Y = h;" +
                    nline + tab4 + "gdm.PreferredBackBufferWidth = preferred.X;" +
                    nline + tab4 + "gdm.PreferredBackBufferHeight = preferred.Y;"
                    );
            }
            Console.WriteLine(nline + " we have made a called to set gdm.PreferredBackBuffer.WH from " + preferred + " to (" + w + "," + h + ")");
            preferred.X = w;
            preferred.Y = h;
            gdm.PreferredBackBufferWidth = preferred.X;
            gdm.PreferredBackBufferHeight = preferred.Y;
        }

        public void SetBackBufferThruGdPresentationParams(int w, int h)
        {
            if (show_code)
            {
                Console.WriteLine(
                    tab4 + "Code" +
                    nline + tab4 + "gd.PresentationParameters.BackBufferWidth = w;" +
                    nline + tab4 + "gd.PresentationParameters.BackBufferHeight = h;"
                    );
            }
            gd.PresentationParameters.BackBufferWidth = w;
            gd.PresentationParameters.BackBufferHeight = h;
        }

        public void SetFullScreen(bool tf)
        {
            if (show_code)
            {
                Console.WriteLine(
                    tab4 + "Code" +
                    nline + tab4 + "if (tf != gdm.IsFullScreen){gdm.IsFullScreen = tf;}"
                    );
            }
            Console.WriteLine(nline + " checking gdm.isfullscreen(" + gdm.IsFullScreen + ") requesting change to " + tf);
            if (tf != gdm.IsFullScreen)
            {
                Console.WriteLine(" Ok changing gdm.isfullscreen setting To " + tf);
                gdm.IsFullScreen = tf;
            }
            else
            {
                Console.WriteLine(" !!note!! gdm isfullscreen is " + tf + " Already !!!!!!!!");
            }
        }
        public void TheFullScreenToggle()
        {
            if (show_code)
            {
                Console.WriteLine(
                    tab4 + "Code" +
                    nline + tab4 + "gdm.ToggleFullScreen();"
                    );
            }
            gdm.ToggleFullScreen();
            DisplayGdmGdWindowRecordedInfo();
        }

        public void ListSupportedDisplayModes()
        {
            Console.WriteLine(nline + " list supported Display modes");
            Console.WriteLine("  Current Mode " + gd.Adapter.CurrentDisplayMode + nline);
            int counter = 0;
            counter = 0;
            //foreach (DisplayMode dm in GraphicsAdapter.DefaultAdapter.SupportedDisplayModes) // either works
            foreach (DisplayMode dm in gd.Adapter.SupportedDisplayModes)
            {
                Console.WriteLine(
                    "   DisplayMode[" + counter.ToString() + "] " + dm.Width.ToString() + "," + dm.Height.ToString() +
                    "   AspectRatio " + dm.AspectRatio.ToString() +
                    "   SurfaceFormat " + dm.Format.ToString()
                    //+" RefreshRate " + dm.RefreshRate.ToString()  //in monogame but not in xna 4.0 that's required for arm i think
                    );
                counter++;
            }
        }

        int curdisplaymodeindex = 0;
        int nextdisplaymodeindex = 0;

        public void ChangeToNextDisplayMode(bool enterfullscreenfirst)
        {
            if (show_code)
            {
                Console.WriteLine(
                    tab4 + "Code   :relevant:" +
                    nline + tab4 + "gdm.PreferredBackBufferWidth = dm.Width;" +
                    nline + tab4 + "gdm.PreferredBackBufferHeight = dm.Height;" +
                    nline + tab4 + "gdm.PreferredBackBufferFormat = dm.Format;" +
                    nline + tab4 + "//gd.PresentationParameters.BackBufferWidth = dm.Width;" +
                    nline + tab4 + "//gd.PresentationParameters.BackBufferHeight = dm.Height;" +
                    nline + tab4 + "//gd.PresentationParameters.BackBufferFormat = dm.Format;" +
                    nline + tab4 + "gdm.IsFullScreen = true; // call this specifically here as this might fail windowed"
                    );
            }
            Console.WriteLine(nline + " Next Display mode coaxing setting fullscreen");

            DisplayMode currentdm = gd.Adapter.CurrentDisplayMode;
            curdisplaymodeindex = FindCurrentDisplayModeInList();

            Console.WriteLine(" Ensureing we are in fullscreen [code] gdm.IsFullScreen = true;");
            if (gdm.IsFullScreen == false && enterfullscreenfirst)
            {
                gdm.IsFullScreen = true;
                gdm.ApplyChanges();
            }

            int startmode = curdisplaymodeindex;
            int attempts = 1;
            if (curdisplaymodeindex > -1)
            {
                for (int i = 0; i < attempts; i++)
                {
                    nextdisplaymodeindex += 1;
                    if(nextdisplaymodeindex == curdisplaymodeindex)
                    {
                        nextdisplaymodeindex += 1;
                    }
                    if (nextdisplaymodeindex >= supportedDisplayModes.Count)
                    {
                        nextdisplaymodeindex = 0;
                    }

                    //DisplayGdmGdWindowRecordedInfo();
                    Console.WriteLine();
                    Console.WriteLine(" gd.displaymode ["+ curdisplaymodeindex + "] ... wh " + gd.DisplayMode.Width + "," + gd.DisplayMode.Height + " aspct " + gd.DisplayMode.AspectRatio + " format " + gd.DisplayMode.Format);
                    Console.WriteLine();

                    DisplayMode dm = supportedDisplayModes[nextdisplaymodeindex];
                    Console.WriteLine(" Next displaymode ["+ nextdisplaymodeindex + "] ... wh " + dm.Width + "," + dm.Height + " aspct " + dm.AspectRatio + " format " + dm.Format);
                    Console.WriteLine();

                    gdm.PreferredBackBufferWidth = dm.Width;
                    gdm.PreferredBackBufferHeight = dm.Height;
                    gdm.PreferredBackBufferFormat = dm.Format;
                    //gdm.GraphicsDevice.PresentationParameters.BackBufferWidth = dm.Width;
                    //gdm.GraphicsDevice.PresentationParameters.BackBufferHeight = dm.Height;
                    //gdm.GraphicsDevice.PresentationParameters.BackBufferFormat = dm.Format;
                    //gdm.HardwareModeSwitch = true;
                    gdm.ApplyChanges();

                    curdisplaymodeindex = FindCurrentDisplayModeInList();
                    if (startmode != curdisplaymodeindex)
                    {
                        i = attempts;
                        Console.WriteLine(" Display mode has changed  cur=" + curdisplaymodeindex);
                    }
                    else
                    {
                        Console.WriteLine(" Failed to change Display mode cur="+ curdisplaymodeindex);
                    }
                }
            }
            else
            {
                if (gdm.IsFullScreen)
                {
                    gdm.IsFullScreen = false;
                    gdm.ApplyChanges();
                }
                Console.WriteLine(" !!!! NO match found for the current display mode ? falling back to windowed");
            }

            DisplayGdmGdWindowRecordedInfo();

            Console.WriteLine(" ChangeToNextDisplayMode... ends");
        }

        public int FindCurrentDisplayModeInList()
        {
            DisplayMode tofind = gd.Adapter.CurrentDisplayMode;
            
            int index = -1;
            int matchlevel = 0;
            int j = 0;
            foreach (DisplayMode bd in gd.Adapter.SupportedDisplayModes)
            {
                if (tofind.Format == bd.Format)
                {
                    if (matchlevel < 1) { matchlevel = 1; index = j; }
                    if (tofind.Width == bd.Width)
                    {
                        if (matchlevel < 2) { matchlevel = 2; index = j; }
                        if (tofind.Height == bd.Height)
                        {
                            if (matchlevel < 3) { matchlevel = 3; index = j; }
                            if (tofind.AspectRatio == bd.AspectRatio)
                            {
                                if (matchlevel < 4) { matchlevel = 4; index = j; }
                                if (tofind.Format == bd.Format)
                                {
                                    if (matchlevel < 5) { matchlevel = 5; index = j; }
                                }
                            }
                        }
                    }
                }
                j++;
            }
            return index;
        }
        public int FindCurrentOrClosestDisplayModeInList(List<DisplayMode> bd, DisplayMode tofind)
        {
            int index = -1;
            int matchlevel = 0;
            for (int j = 0; j < bd.Count; j++)
            {
                if (tofind.Format == bd[j].Format)
                {
                    if(matchlevel < 1) { matchlevel = 1; index = j; }
                    if (tofind.Width == bd[j].Width )
                    {
                        if (matchlevel < 2){ matchlevel = 2; index = j; }
                        if (tofind.Height == bd[j].Height )
                        {
                            if (matchlevel < 3) { matchlevel = 3; index = j; j = bd.Count; }
                        }
                    }
                }
            }
            return index;
        }

        public List<DisplayMode> FindBestDisplayModes()
        {
            Console.WriteLine(nline + " Finding Best DisplayModes ");
            // rebuild viable list into sdm
            DisplayModeCollection dms = gdm.GraphicsDevice.Adapter.SupportedDisplayModes;
            bestDisplayModes = new List<DisplayMode>();
            supportedDisplayModes = new List<DisplayMode>();

            for (int i = 0; i < dms.Count(); i++)
            {
                DisplayMode now = dms.ElementAt(i);
                supportedDisplayModes.Add(now);
                if (now.Format == starteddm.Format && now.AspectRatio == starteddm.AspectRatio)
                {
                    bool isgood = true;
                    for (int j = 0; j < bestDisplayModes.Count; j++)
                    {
                        if (now.Width == bestDisplayModes[j].Width && now.Height == bestDisplayModes[j].Height)
                        {
                            isgood = false;
                        }
                    }
                    if (isgood)
                    {
                        bestDisplayModes.Add(now);
                        Console.WriteLine("  (" + i + ")-->(" + (bestDisplayModes.Count() - 1) + ")" + now + " aspct " + now.AspectRatio + " format " + now.Format);
                    }
                }
            }
            return bestDisplayModes;
        }

        int pause = 0;
        public void ReducePause()
        {
            if (pause > -1)
                pause--;
        }
        public bool Paused
        {
            get
            {
                if (pause < 1)
                {
                    pause = input_pause_amount;
                    return false;
                }
                else
                {
                    return true;
                }
            }
        }

        //void GdmPreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
        //{
        //    //e.GraphicsDeviceInformation.PresentationParameters.RenderTargetUsage = RenderTargetUsage.PlatformContents;
        //    e.GraphicsDeviceInformation.PresentationParameters.MultiSampleCount = 4;
        //    e.GraphicsDeviceInformation.PresentationParameters.BackBufferWidth = 800;
        //    e.GraphicsDeviceInformation.PresentationParameters.BackBufferHeight = 600;
        //    e.GraphicsDeviceInformation.PresentationParameters.PresentationInterval = PresentInterval.Immediate;
        //    e.GraphicsDeviceInformation.PresentationParameters.IsFullScreen = true;
        //}
            public class RecordDiff
            {
                private static List<RecordDiff> registered_list = new List<RecordDiff>();
                bool isfullscreen = false;
                bool isgdfullscreen = false;
                bool iswidescreen = false;
                Point preferedbackbuffwh = new Point();
                SurfaceFormat preferedbackbufferformat;
                Rectangle windowclientbounds = new Rectangle();
                DisplayOrientation displayorientation = DisplayOrientation.Default;
                Rectangle gdpresentationparambounds = new Rectangle();
                Point presentationparametersbackbufferwh = new Point();
                SurfaceFormat presentationparametersbackbufferformat;
                Viewport viewport = new Viewport();
                Rectangle viewporttilesafearea = new Rectangle();
                Point displaymodeWh = new Point();
                Rectangle displaymodetilesafearea = new Rectangle();
                bool isdefaultgraphicsadapter = true;
                Point gdcurrentdisplaymodewh = new Point();
                Rectangle gdcurrentdisplaymodetilesafearea = new Rectangle();

                public static bool EnoughRecords { get { return (registered_list.Count > 1); } }

                public RecordDiff(GraphicsDeviceManager gdm, GraphicsDevice gd, GameWindow Window)
                {
                    isfullscreen = gdm.IsFullScreen;
                    isgdfullscreen = gd.PresentationParameters.IsFullScreen;
                    iswidescreen = false;
                    preferedbackbuffwh = new Point(gdm.PreferredBackBufferWidth, gdm.PreferredBackBufferHeight);
                    preferedbackbufferformat = gdm.PreferredBackBufferFormat;
                    windowclientbounds = Window.ClientBounds;
                    displayorientation = gd.PresentationParameters.DisplayOrientation;
                    gdpresentationparambounds = gd.PresentationParameters.Bounds;
                    presentationparametersbackbufferwh = new Point(gd.PresentationParameters.BackBufferWidth, gd.PresentationParameters.BackBufferHeight);
                    presentationparametersbackbufferformat = gd.PresentationParameters.BackBufferFormat;
                    viewport = gd.Viewport;
                    viewporttilesafearea = gd.Viewport.TitleSafeArea;
                    displaymodeWh = new Point(gd.DisplayMode.Width, gd.DisplayMode.Height);
                    displaymodetilesafearea = gd.DisplayMode.TitleSafeArea;
                    isdefaultgraphicsadapter = true;//gd.Adapter.IsDefaultAdapter; // not implemented in monogame
                    gdcurrentdisplaymodewh = new Point(gd.Adapter.CurrentDisplayMode.Width, gd.Adapter.CurrentDisplayMode.Height);
                    gdcurrentdisplaymodetilesafearea = gd.Adapter.CurrentDisplayMode.TitleSafeArea;
                    // add it
                    registered_list.Add(this);
                }
                public static void DisplayDifferences(bool displayunchanged)
                {
                    if (EnoughRecords)
                    {
                        int to = registered_list.Count - 1;
                        int from = to - 1;
                        TheDifferences(registered_list[from], registered_list[to], displayunchanged);
                    }
                }
                private static void TheDifferences(RecordDiff A, RecordDiff B, bool displayunchanged)
                {
                    string differences = "";
                    string stayedthesame = "";
                    string formating = "\n   diff {0} CHANGE FROM {1} TO {2}";
                    string formating2 = "\n    nochange {0}";
                    string changedvar = "";
                    differences += string.Format("\n   ------ changes that occured -----------");
                    differences += string.Format("\n   ---------------------------------------");
                    stayedthesame += string.Format("\n   ------ remained unchanged -----------");
                    stayedthesame += string.Format("\n   ---------------------------------------");
                    if (A.isfullscreen != B.isfullscreen) { changedvar = "gdm.FullScreen"; differences += string.Format(formating, changedvar, A.isfullscreen, B.isfullscreen); }
                    else { stayedthesame += string.Format(formating2, "gdm.FullScreen"); }
                    if (A.isgdfullscreen != B.isgdfullscreen) { changedvar = "gd.PresentationParameters.IsFullScreen"; differences += string.Format(formating, changedvar, A.isgdfullscreen, B.isgdfullscreen); }
                    else { stayedthesame += string.Format(formating2, "gd.PresentationParameters.IsFullScreen"); }
                    if (A.iswidescreen != B.iswidescreen) { changedvar = "gd.Adapter.isWideScreen"; differences += string.Format(formating, changedvar, A.iswidescreen, B.iswidescreen); }
                    else { stayedthesame += string.Format(formating2, "gd.Adapter.isWideScreen"); }
                    if (A.preferedbackbuffwh != B.preferedbackbuffwh) { changedvar = "gdm.PreferredBackBuffer(Width Height)"; differences += string.Format(formating, changedvar, A.preferedbackbuffwh, B.preferedbackbuffwh); }
                    else { stayedthesame += string.Format(formating2, "gdm.PreferredBackBuffer(Width Height)"); }
                    if (A.preferedbackbufferformat != B.preferedbackbufferformat) { changedvar = "gdm.PreferredBackBufferFormat"; differences += string.Format(formating, changedvar, A.preferedbackbufferformat, B.preferedbackbufferformat); }
                    else { stayedthesame += string.Format(formating2, "gdm.PreferredBackBufferFormat"); }
                    if (A.windowclientbounds != B.windowclientbounds) { changedvar = "Window.ClientBounds"; differences += string.Format(formating, changedvar, A.windowclientbounds, B.windowclientbounds); }
                    else { stayedthesame += string.Format(formating2, "Window.ClientBounds"); }
                    if (A.displayorientation != B.displayorientation) { changedvar = "gd.PresentationParameters.DisplayOrientation"; differences += string.Format(formating, changedvar, A.displayorientation, B.displayorientation); }
                    else { stayedthesame += string.Format(formating2, "gd.PresentationParameters.DisplayOrientation"); }
                    if (A.gdpresentationparambounds != B.gdpresentationparambounds) { changedvar = "gd.PresentationParameters.Bounds"; differences += string.Format(formating, changedvar, A.gdpresentationparambounds, B.gdpresentationparambounds); }
                    else { stayedthesame += string.Format(formating2, "gd.PresentationParameters.Bounds"); }
                    if (A.presentationparametersbackbufferwh != B.presentationparametersbackbufferwh) { changedvar = "gd.PresentationParameters.BackBuffer(WidthHeight)"; differences += string.Format(formating, changedvar, A.presentationparametersbackbufferwh, B.presentationparametersbackbufferwh); }
                    else { stayedthesame += string.Format(formating2, "gd.PresentationParameters.BackBuffer(WidthHeight)"); }
                    if (A.presentationparametersbackbufferformat != B.presentationparametersbackbufferformat) { changedvar = "gd.PresentationParameters.BackBufferFormat"; differences += string.Format(formating, changedvar, A.presentationparametersbackbufferformat, B.presentationparametersbackbufferformat); }
                    else { stayedthesame += string.Format(formating2, "gd.PresentationParameters.BackBufferFormat"); }
                    if (A.viewport.Bounds != B.viewport.Bounds) { changedvar = "gd.Viewport"; differences += string.Format(formating, changedvar, A.viewport, B.viewport); }
                    else { stayedthesame += string.Format(formating2, "gd.Viewport"); }
                    if (A.viewporttilesafearea != B.viewporttilesafearea) { changedvar = "gd.Viewport.TitleSafeArea"; differences += string.Format(formating, changedvar, A.viewporttilesafearea, B.viewporttilesafearea); }
                    else { stayedthesame += string.Format(formating2, "gd.Viewport.TitleSafeArea"); }
                    if (A.displaymodeWh != B.displaymodeWh) { changedvar = "gd.DisplayMode.(Width, Height)"; differences += string.Format(formating, changedvar, A.displaymodeWh, B.displaymodeWh); }
                    else { stayedthesame += string.Format(formating2, "gd.DisplayMode.(Width, Height)"); }
                    if (A.displaymodetilesafearea != B.displaymodetilesafearea) { changedvar = "gd.DisplayMode.TitleSafeArea"; differences += string.Format(formating, changedvar, A.displaymodetilesafearea, B.displaymodetilesafearea); }
                    else { stayedthesame += string.Format(formating2, "gd.DisplayMode.TitleSafeArea"); }
                    if (A.isdefaultgraphicsadapter != B.isdefaultgraphicsadapter) { changedvar = "gd.Adapter.IsDefaultAdapter"; differences += string.Format(formating, changedvar, A.isdefaultgraphicsadapter, B.isdefaultgraphicsadapter); }
                    else { stayedthesame += string.Format(formating2, "gd.Adapter.IsDefaultAdapter"); }
                    if (A.gdcurrentdisplaymodewh != B.gdcurrentdisplaymodewh) { changedvar = "gd.Adapter.CurrentDisplayMode.WH"; differences += string.Format(formating, changedvar, A.gdcurrentdisplaymodewh, B.gdcurrentdisplaymodewh); }
                    else { stayedthesame += string.Format(formating2, "gd.Adapter.CurrentDisplayMode.WH"); }
                    if (A.gdcurrentdisplaymodetilesafearea != B.gdcurrentdisplaymodetilesafearea) { changedvar = "gd.Adapter.CurrentDisplayMode.TitleSafeArea"; differences += string.Format(formating, changedvar, A.gdcurrentdisplaymodetilesafearea, B.gdcurrentdisplaymodetilesafearea); }
                    else { stayedthesame += string.Format(formating2, "gd.Adapter.CurrentDisplayMode.TitleSafeArea"); }
                    differences += string.Format("\n   ---------------------------------------");
                    stayedthesame += string.Format("\n   ---------------------------------------");
                    //
                    Console.WriteLine(differences);
                    if (displayunchanged)
                        Console.WriteLine(stayedthesame);
                }
                public static void DisplayCurrentStatus(GraphicsDeviceManager gdm, GraphicsDevice gd, GameWindow Window)
                {
                    Console.WriteLine("  DisplayCurrentStatus ");
                    if (gdm.IsFullScreen)
                        Console.WriteLine("  ___________Info______Gdm_Currently FullScreen");
                    else
                        Console.WriteLine("  ___________Info______Gdm_Currently Windowed");
                    if (gd.PresentationParameters.IsFullScreen)
                        Console.WriteLine("  gd.PresentationParameters.IsFullScreen Currently FullScreen");
                    else
                        Console.WriteLine("  gd.PresentationParameters.IsFullScreen Currently Windowed");
                    Console.WriteLine("  gd.Adapter.IsWideScreen                      " + gd.Adapter.IsWideScreen);
                    Console.WriteLine("  gdm.PreferredBackBuffer WH:                           Width:" + gdm.PreferredBackBufferWidth + " Height:" + gdm.PreferredBackBufferHeight);
                    Console.WriteLine("  gdm.PreferredBackBufferFormat                " + gdm.PreferredBackBufferFormat);
                    Console.WriteLine("  Window.ClientBounds                          " + Window.ClientBounds);
                    Console.WriteLine("  gd.PresentationParameters.DisplayOrientation " + gd.PresentationParameters.DisplayOrientation);
                    Console.WriteLine("  gd.PresentationParameters.Bounds             " + gd.PresentationParameters.Bounds);
                    Console.WriteLine("  gd.PresentationParameters.BackBuffer WH:              Width:" + gd.PresentationParameters.BackBufferWidth + " Height:" + gd.PresentationParameters.BackBufferHeight);
                    Console.WriteLine("  gd.PresentationParameters.BackBufferFormat   " + gd.PresentationParameters.BackBufferFormat);
                    Console.WriteLine("  gd.Viewport                                  " + gd.Viewport);
                    Console.WriteLine("  gd.Viewport.TitleSafeArea                    " + gd.Viewport.TitleSafeArea);
                    Console.WriteLine("  gd.DisplayMode.Width, Height                          Width:" + gd.DisplayMode.Width + " Height:" + gd.DisplayMode.Height);
                    Console.WriteLine("  gd.DisplayMode.TitleSafeArea                 " + gd.DisplayMode.TitleSafeArea);
                    //Console.WriteLine("gd.Adapter.IsDefaultAdapter                  " + gd.Adapter.IsDefaultAdapter); //// not yet implemented in monogame
                    Console.WriteLine("  gd.Adapter.IsDefaultAdapter                  not yet implemented in monogame");
                    Console.WriteLine("  gd.Adapter.CurrentDisplayMode WH:                     Width:" + gd.Adapter.CurrentDisplayMode.Width + " Height:" + gd.Adapter.CurrentDisplayMode.Height);
                    Console.WriteLine("  gd.Adapter.CurrentDisplayMode.TitleSafeArea  " + gd.Adapter.CurrentDisplayMode.TitleSafeArea);
                    Console.WriteLine("  ________________________________");
                }
            }
        }
    }

There isn’t another event for this, ClientSizeChanged should fire once when resize ends. It just needs to be fixed in the MG code. It’s a known issue with DesktopGL & Win10 store apps.

UPDATE
It seems that both SDL and XAML will fire SizeChanged repetitively, there is no SizeChangedEnded event. So, if someone were to fix this it will need some type of trick like the one that @willmotil described in his first post.
By the way, W10 stores the event and fires only one ClientSizeChanged event between two Update(). That was a nice side-efect of syncing events with the game loop thread. I don’t think there’s a similar mechanisms in OpenGL and it’s possible that you are getting multiple events from the mouse, every 4ms or so.
In that case , setting a boolean on ClientSizeChanged and handling the resize on Update() will improve the situation somewhat…

UPDATE ][
Another idea,
have ClientSizeChanged set an integer to 2

ClientSizeCount = 2;

and Update() to Decrease it down to zero.

if(ClientSizeCount == 1) DoResize();
if(ClientSizeCount > 0)   ClientSizeCount--;

A value of 2 will give you a 1 frame delay.
That’s similar to the ‘two pass update trick’ by @willmotil

@willmotil,
So far, I have implemented your “two pass” approach in a test on my MacOS laptop, but I haven’t moved it to my Linux environment yet. I’ll do that this weekend and let you know how it goes. Thank you so much for taking time to help. I’ll look over this code you sent on the weekend.

@nkast
On my MacBook Pro, the Window.ClientSizeChanged event only fires once for a resize operation (DesktopGL: 3.6.9.1625). So it behaves like an “EndDrag” event. The “multiple drag event” scenario is happening in my Linux tests (I haven’t tried windows yet).

In fact the MacOs behavior is completely different. On Linux, the screen stays visible and keeps drawing. On the MacOS, the client window turns black and does not appear again until I release the mouse button.

As for handling the resize in my update, I have done something similar. I have a counter that delays by 200ms per resize. The problem is that I can still end up in the situation I mentioned above where the client bounds are different than what’s actually on screen. I could try to make a demo video or something if I can find some recording software for Linux.

This counter is implemented with the GameTime.ElapsedTime or with some type of Thread/Timer?
That situation is very strange. Is it possible that Linux fires the event on a different thread than the game loop?
(I don’t know much about Linux. Last time I checked it had no real threads.)

If I had to guess, I’d say the resize is not running in another thread, but just being triggered repeatedly by something like a mouse move event, but I’m just speculating.

I’m not using another thread myself though:

  • I run a countdown using the ElapsedTime in Update to trigger a method every 200ms.
  • When a ClientSizeChanged event triggers I mark a boolean flag “ResizeRequested=true”.
  • When the Update triggers the 200msMethod, I check the ResizeRequested flag.
  • If ResizeRequested is true, I clear it and resize the screen.

This was just a way to slow down the incoming resize events. It doesn’t really change the overall behavior, but keeps the resize code from overloading the system.

So, this issue sort of comes in two parts:

  1. In Linux, after a resize, if the user delays releasing the mouse button for a bit (and the code sets the back buffer size different from the current client bounds), the client window as you see it on screen ignores the code and stays at the size the user left it at when releasing the mouse button. This wouldn’t matter so much, except that Window.ClientBounds now reports the value you set it to in the code. So, Window.ClientBounds might say its 500x500 (you’re min desired window size perhaps), but on screen you’re looking at a 100x100 window.

  2. I thought I could work around this if I could determine when the user finished dragging, by just doing one forced resize no matter what, but if the user is still holding down the mouse button the client window ignores my resize.

Currently, I’m working around all this by running 5 forced screen resizes, one every 200ms after the last ClientSizeChanged event (restarting the process each time a new resize starts), but it’s not an ideal solution.

I’m going to try willmotil’s two pass solution tonight. If the event trigger for the resize event is in fact synced between updates, then it might do the trick. However, if two updates run between the resize events, then I’ll get multiple “end drag” executions. It depends on what’s triggering those resize events I guess.

Thanks

@willmotil,

I tried running the two pass method in Linux tonight. Unfortunately, it didn’t work. I got the same number of “DragEnd” calls as I got ClientSizeChanged events. Here’s the code I used (let me know if you see any mistakes):

using System;
using System.ComponentModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Techniques {
  
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // Game2
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  public class Game2 : Game {

    //Constants
    private const int MIN_SCREEN_WIDTH = 500;
    private const int MIN_SCREEN_HEIGHT = 500;
    
    //Standard assets
    private GraphicsDeviceManager _gdm;
    private SpriteBatch _sb;

    //Test Vars
    private SpriteFont _font;
    private int _endDragCounter;
    private int _resizeTriggeredCounter;
    private bool _isResizing;
    private bool _wasResizing;
    

    // INIT
    //------------------------------------------------------------------------------------------------------------------------------
    //========================================
    // Constructor
    //========================================
    public Game2() {
      
      //Vanilla
      _gdm = new GraphicsDeviceManager(this);
      
      Content.RootDirectory = "pipeline";
    }

    
    // Private Mathods
    //------------------------------------------------------------------------------------------------------------------------------
    //========================================
    // ClientWindowResized
    //========================================
    private void ClientWindowResized(object sender, EventArgs e) {
      _resizeTriggeredCounter++;
      _isResizing = true;
    }

    //========================================
    // DragEnd
    //========================================
    private void DragEnd() {
      
      _endDragCounter++;

      //Jump out if there are no changes
      if (_gdm.PreferredBackBufferWidth == Window.ClientBounds.Width &&
          _gdm.PreferredBackBufferHeight == Window.ClientBounds.Height) {
        return;
      }
      
      //Set the new (clamped) sizes
      _gdm.PreferredBackBufferWidth = Math.Max(MIN_SCREEN_WIDTH, Window.ClientBounds.Width);
      _gdm.PreferredBackBufferHeight = Math.Max(MIN_SCREEN_HEIGHT, Window.ClientBounds.Height);
      _gdm.ApplyChanges();
    }


    // LIFECYCLE
    //------------------------------------------------------------------------------------------------------------------------------
    //========================================
    // Initialize
    //========================================
    protected override void Initialize() {
      base.Initialize();
      
    }

    //========================================
    // LoadContent
    //========================================
    protected override void LoadContent() {
      
      // Create a new SpriteBatch, which can be used to draw textures.
      _sb = new SpriteBatch(GraphicsDevice);

      //Setup
      Window.AllowUserResizing = true;
      IsMouseVisible = true;
      _endDragCounter = 0;
      
      //Load assets
      _font = Content.Load<SpriteFont>("mainfont16");
      
      //Attach subscribers
      Window.ClientSizeChanged += ClientWindowResized;
    }

    //========================================
    // Update
    //========================================
    protected override void Update(GameTime gameTime) {
      
      if (Keyboard.GetState().IsKeyDown(Keys.Escape)) { Exit(); }

      if (_wasResizing && !_isResizing) {
        _wasResizing = false;
        DragEnd();
      }

      if (_isResizing) {
        _wasResizing = true;
        _isResizing = false;
      }
      
      base.Update(gameTime);
    }

    //========================================
    // Draw
    //========================================
    protected override void Draw(GameTime gameTime) {

      //I like gray
      _gdm.GraphicsDevice.Clear(new Color(64,64,64));
      
      //Start a batch
      _sb.Begin();
      
      //Draw our counts
      _sb.DrawString(_font, $"Resize Count:{_resizeTriggeredCounter}", new Vector2(5,5), Color.White);
      _sb.DrawString(_font, $"Drag End Count:{_endDragCounter}", new Vector2(5,35), Color.White);
      
      _sb.DrawString(_font, $"ClientBounds:({Window.ClientBounds.Width},{Window.ClientBounds.Height})", new Vector2(5,75), Color.White);
      _sb.DrawString(_font, $"BackBuffer:({_gdm.PreferredBackBufferWidth},{_gdm.PreferredBackBufferHeight})", new Vector2(5,105), Color.White);
      
      
      //Done with batch
      _sb.End();
      
      base.Draw(gameTime);
    }
    
  }//class
}//ns

You should copy paste that test class into a new project turn on your apps console and run it. After it auto runs resize the window and drop the outputted console results in a text file. Then move the window by the title area and post the output results to that as well.

Id like to see what is or is not changing as far as all the values for the backbuffer, viewport bounds and all the others ect… in the outputted text. Might be able to just forceably reset things by setting a timed bool from the Resize Callback. Then in draw or update make a call to a method that for a time constantly corrects for whatever is not being properly changed.

I imagine draw is being called as well non stop i don’t really like the idea of the flags to be honest anyways or a counter or a timer. This actually smells of a sdl bug or simply some not fully implemented handling reading of the os message pumps basically a chop it in and im done pass that was never improved.

Im thinking this deserves a topic on the sdl forum.

This is what a quick search came up with on the forum.
https://discourse.libsdl.org/search?q=resizing%20on%20linux%20category%3A6

If there isn’t a proper solution to this then. Maybe monogame should intercept this event and provide its own registerable events in it’s place that defines resizing or resized events. In this case that under the hood this original callback is held by monogame and handles the resize by user set options, so there is some consistency were or when it is possible for us to make it consistent.

That is what I’m doing in my actual project. It works well enough, but its obviously not what I’d prefer to be doing.

As soon as I get a chance, I’ll change that test to spit to console so I can post them for you.


I’m quite new to MonoGame and it wasn’t till very recently that I understood what SDL even was. I tried to reach through MonoGame’s assets into SDL to see if I could talk to the game window directly, but I got lost very quickly. Everything’s internal anyway and only shows up because I’m “decompiling” it with JetBrain’s tools.

I also tried to see if I could register some of the the SDL2.DLL methods directly and just talk to SDL with the window handles (because the size reporting comes back wrong), but I never got that to work either (it just keeps saying it can’t find the dll):

[DllImport("SDL2.dll", EntryPoint = "SDL_GetWindowSize", CallingConvention = CallingConvention.Cdecl)]
public static extern void GetSize(IntPtr window, out int w, out int h);

Ok, here’s links to the code and console output for a quick test:

Test Code

Console Output

Here’s a YouTube so you can see what happens. Sorry about the quality, but you can still get the idea. Note that the actual client window on screen is NOT the reported size. Also note that the YouTube test is not the same as the console output above, because I did it too slowly in the video and the console buffer overran. I ran the same test for the console output, but more quickly so the console output would all fit.

YouTube: Issue Demo

NOTE: In the video, at the end of the drag, I keep the mouse button down for a few seconds before releasing it. Had I released it immediately after the drag, the window on screen would have resized properly. It’s the fact that I delay releasing the mouse button that causes this issue. That condition seems to prevent the ApplyChanges() call from succeeding to tell SDL to change the window size.

If SDL would just tell us when the mouse was released, I’m thinking this would be cake to fix.

If you copy the record diff class i posted, below the game1 class.
Then add in the method to your game1.

        private void DisplayGdmGdWindowRecordedInfo()
        {
            gd = gdm.GraphicsDevice; // just to show gd is in fact current
            new RecordDiff(gdm, gd, Window);
            if (just_display_changes && RecordDiff.EnoughRecords)
            {
                RecordDiff.DisplayDifferences(display_unchanged_stuff);
            }
            else
            {
                RecordDiff.DisplayCurrentStatus(gdm, gd, Window);
            }
        }

Then you can just call that where ever you would display the changes you are testing. It will output any and all changes across all the possible values listed.
Instead of just showing what changes are made to the back buffer only.