How does MonoGame work with multiple windows like Blender, 3D Max Studio or Unreal Editor?

Hello everyone,

I have tried to make multiple windows like any program Blender with Preference Window and Unreal Editor has many child windows like Mesh Editor etc…

like any programs Blender:

Or Max Studio 2017

Or Unreal Editor
( Can’t find other :frowning: )

I have made Buttons from youtube

And I would like to know about multiple windows of MonoGame if VERTICES ENGIN would like to expand more childwindows -

How do I implement if I close 2. window than I use back to 1. window.

Do I need to use Thread Class?

Thanks foir explanation!

  1. Button “Setting” If I open SettingWindow of MonoGame Engine.
  2. Button “Change” it changes for background from 1. window But I have tried. no success. How do I know like very old Gtk Window with MonoGame

Do you think that MonoGame 3.6 / 3.7 works for multiple windows too?

Or I need merge with pInvokes with Window.Handle?

// EDIT:

I have tried to Thread like Gtk App and MonoGame.
But It throws exception “NoSuitableGraphicsDeviceException” problem.

How do I fix? I need try catch statement? And it looks crazy if I click button “Setting” than mousecusor will hide. It looks crazy.

How do I fix? Thanks!

Hello there!

As I understand, you wish to open a separate window and impart some functionality
to it? Not sure, that I can give you a complete solution, though can try.

You can do something like this inside your code to create either instance of WinForm or GameWindow:

private void CreateWindowForm()
        {
            System.Windows..Forms.Form _form = new Form();
            _form.Show();
        }

private void CreateGameWindow()
        {
            var x = GameWindow.Create(this, 300, 300);
            x.Position = (Vector2.One * 300).ToPoint() ;
            var f = System.Windows.Forms.Control.FromHandle(x.Handle).FindForm();
            f.Show();
        }

To be honest, I’m not aware of how the Microsoft.Xna.Framework.GameWindow class works and if there is a significant difference between this one and WinForm.

And to draw some content to a new window you might use RenderTarget: Similar discussion && SwapChainRenderTarget.cs documentation

Hello @dryhfth Thanks for answer!

But I really don’t like WinForms. Because I hate cause Linux and Mac users argue that.

I really want know how do I make sub window / multiple window only DesktopGL ( WindowGL )

I can’t find example of RenderTargets with sub- or multiple-windows.
Do not use SharpDX!

Only DesktopGL!!

I have tried example:

MyGame.cs:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MonoCrraft.Controls;
using MonoCrraft.Properties;
using System;
using System.Collections.Generic;
using System.Threading;

namespace MonoCrraft
{
    public class MyGame : Game
    {

        public GraphicsDeviceManager graphics { get; set; }
        SpriteBatch spriteBatch;

        Model model;

        public Color ClearColor { get; set; }

        List<Component> _gameComponents;

        public MyGame()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = 1400;
            graphics.PreferredBackBufferHeight = 840;

            ResourceContentManager resxContent = new ResourceContentManager(Services, Resources.ResourceManager);
            Content = resxContent;
        }

        protected override void Initialize()
        {
            Window.Title = "My Game Example";
            IsMouseVisible = true;

            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            model = Content.Load<Model>("box");

            var SettingButton = new Button(Content.Load<Texture2D>("button"), Content.Load<SpriteFont>("font"))
            {
                Position = new Vector2(10, 10),
                Text = "Setting",
                Rectangle = new Rectangle(10, 10, 100, 30)
            };
            SettingButton.Click += SettingHandler;

            _gameComponents = new List<Component>()
            {
                SettingButton
            };
        }

        private void SettingHandler(object sender, EventArgs e)
        {
            using (var settingWindow = new SettingWindow(this))
            {
                while(settingWindow != null)
                    settingWindow.Run();
            }
        }

        protected override void UnloadContent()
        {
            
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            foreach (var component in _gameComponents)
                component.Update(gameTime);

            base.Update(gameTime);
        }

        private void CreateCube(Vector3 position, Color diffuseColor, GameTime gt)
        {
            foreach(ModelMesh mesh in model.Meshes)
            {

                foreach(BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.AmbientLightColor = Color.DimGray.ToVector3();
                    effect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(-1, -1.5f, 0));
                    effect.DirectionalLight0.DiffuseColor = diffuseColor.ToVector3() * Color.DimGray.ToVector3();
                    effect.DirectionalLight0.SpecularColor = Color.Wheat.ToVector3();
                    effect.DirectionalLight1.Enabled = true;
                    effect.DirectionalLight1.DiffuseColor = Color.DimGray.ToVector3() * Color.Black.ToVector3();
                    effect.DirectionalLight1.Direction = Vector3.Left;
                    effect.World = Matrix.CreateTranslation(position);
                    effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 1.0f, 10000.0f);
                    effect.View = Matrix.CreateLookAt(new Vector3(2, 3, 3), Vector3.Zero, Vector3.Up);
                    effect.PreferPerPixelLighting = true;
                    effect.SpecularPower = (float)gt.TotalGameTime.TotalSeconds * 1.5f;
                    effect.SpecularColor = diffuseColor.ToVector3() * Color.White.ToVector3();
                    effect.DiffuseColor = diffuseColor.ToVector3();
                }

                mesh.Draw();
            }
        }

        protected override void Draw(GameTime gameTime)
        {
            ClearColor = Color.CornflowerBlue;
            GraphicsDevice.Clear(ClearColor);

            spriteBatch.Begin();

            foreach (var component in _gameComponents)
                component.Draw(gameTime, spriteBatch);

            CreateCube(new Vector3(-2, -0.5f, -2), Color.DarkGreen, gameTime);
            CreateCube(new Vector3(-2, -0.5f, 0), Color.YellowGreen, gameTime);
            CreateCube(new Vector3(-2, -0.5f, 2), Color.Aqua, gameTime);

            CreateCube(new Vector3(0, -0.5f, -2), Color.Red, gameTime);
            CreateCube(new Vector3(0, -0.5f, 0), Color.Orange, gameTime);
            CreateCube(new Vector3(0, -0.5f, 2), Color.DimGray, gameTime);

            CreateCube(new Vector3(2, -0.5f, -2), Color.Brown, gameTime);
            CreateCube(new Vector3(2, -0.5f, 0), Color.Blue, gameTime);
            CreateCube(new Vector3(2, -0.5f, 2), Color.Purple, gameTime);

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

SettingWindow.cs: It is sub window of MonoGame

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using MonoCrraft.Controls;
using MonoCrraft.Properties;
using System.Collections.Generic;
using System;

namespace MonoCrraft
{
    public class SettingWindow : Game
    {
        private MyGame MyGame;
        SpriteBatch spriteBatch;

        List<Component> comps;

        public SettingWindow(MyGame g)
        {
            MyGame = g;
            g.graphics = new GraphicsDeviceManager(this);
            g.graphics.PreferredBackBufferWidth = 400;
            g.graphics.PreferredBackBufferHeight = 300;
            
            ResourceContentManager resxContent = new ResourceContentManager(Services, Resources.ResourceManager);
            Content = resxContent;
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            Window.Title = "Setting .... ";
            IsMouseVisible = true;

            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            var randonButton = new Button(Content.Load<Texture2D>("button"), Content.Load<SpriteFont>("font"))
            {
                Position = new Vector2(10, 10),
                Text = "Change",
            };
            randonButton.Click += new EventHandler(randonHandler);

            var quitButton = new Button(Content.Load<Texture2D>("button"), Content.Load<SpriteFont>("font"))
            {
                Position = new Vector2(10, 50),
                Text = "Quit",
            };
            quitButton.Click += new EventHandler(quitHandler);

            comps = new List<Component>()
            {
                randonButton,
                quitButton
            };

            base.LoadContent();
        }

        /**
         *  Exit sub window but it doesn't work :(
         */
        private void quitHandler(object sender, EventArgs e)
        {
            Exiting += new EventHandler<EventArgs>(exitHandler);
            
        }

        private void exitHandler(object sender, EventArgs e)
        {
            while(true)
            {
                Exit();
            }
        }

        /**
         *  Change color from MyGame's background - But it doesn't work :(
         */
        private void randonHandler(object sender, EventArgs e)
        {
            Random randon = new Random();
            if(MyGame.ClearColor != Color.CornflowerBlue)
                MyGame.ClearColor = new Color(randon.Next(0, 255), randon.Next(0, 255), randon.Next(0, 255));
        }

        protected override void Update(GameTime gameTime)
        {

            foreach (var component in comps)
                component.Update(gameTime);

            base.Update(gameTime);
        }

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

            spriteBatch.Begin();
            foreach (var component in comps)
                component.Draw(gameTime, spriteBatch);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

Program.cs ( Runnable file of C#

using System;
using System.IO;

namespace MonoCrraft
{
    public static class Program
    {
        [STAThread]
        static void Main()
        {
            /*
             * For Mono bundled if you don't know since you have bundled net apps into single executable
             * It happens bundled single executable after you run executable and it throws exceptions.
             */
            //if (Environment.Is64BitProcess)
              //  Environment.CurrentDirectory = Path.Combine("x64");
            //else
              //  Environment.CurrentDirectory = Path.Combine("x84");
            /*
             *  End of bundled execuatble
             */

            using (var game = new MyGame())
                game.Run();
        }
    }
}

Component.cs and Button.cs are made from Create simple button.

That is why I can’t resolve that. If you want open sub window like @willmotil he created thread since May 2017 and they don’t know what is “sub window” or “multiple window” meaning?

Sorry I fight that because LWJGL or LibGDX ( Java ) has multiple windows now and they move to Java. Hey hey hey C# will loose many million of people.

I dont like C# happens. That is why I wish we have big success with C# like many users play Unity3D, Unreal Engine and Xenko Studio and Wave Engine and CocoaEngine and UhroEngine are happy because they have multiple windows.

MonoGame has not multiple windows. What do we should that?

How do I use RenderTarget for sub window? Does it have example?

:sweat_smile: OK, let me see, if I can implement it to show you how it is possible, I’ve not tried it my self though, but generally I kinda understand how it work, so give me some time I will try to come up with some example.

1 Like

Yeah I think GameWindow has class from MonoGame here and I try class like this.

I will create simple SubGameWindow with class and implements

like this:

public class SubGameWindow : GameWindow, IGraphicsDeviceService
{
 ...
}

I am sure we can have create simple SubGameWindow for MonoGame.

class Viewport should “catch” to GameWindow ( 0, 0, width and height )

I try if it works.

I can’t speak for the others, but Unity does not support multiple game windows. It supports multiple displays, which is different.

1 Like

Ah yes I forget that Unity’s implements

Yes you’re right. I mean not displays just multiple windows like any programs have sub or child windows.

Thanks for observance!

// EDIT:
I have tried to custom class with GameWindow:

public class SubGameWindow : GameWindow, IGraphicsDeviceService
{
    private static GraphicsDevice graphicsDevice;
    private static PresentationParameters parameters;
    private RenderTarget2D target;

    public GraphicsDevice GraphicsDevice
    {
        get
        {
            return graphicsDevice;
        }
    }

    public SubGameWindow()
    {

    }

    public event EventHandler OnInitialize;
    public event EventHandler OnDraw;

    protected void Initialize()
    {
        if (OnInitialize != null)
            OnInitialize(this, null);
    }

    protected void Draw()
    {
        if (OnDraw != null)
            OnDraw(this, null);
    }

    private static void CreateGraphicsDevice(IntPtr windowHandle, int width, int height)
    {
        parameters = new PresentationParameters
        {
            BackBufferWidth = Math.Max(width, 1),
            BackBufferHeight = Math.Max(height, 1),
            BackBufferFormat = SurfaceFormat.Color,
            DepthStencilFormat = DepthFormat.Depth24,
            DeviceWindowHandle = windowHandle,
            PresentationInterval = PresentInterval.Immediate,
            IsFullScreen = false
        };

        graphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, parameters);

    }

    private bool CanDraw()
    {
        if (graphicsDevice == null || IsDeviceAvailable() == false)
            return false;

        GraphicsDevice.Viewport = new Viewport
        {
            X = 0,
            Y = 0,
            Width = GraphicsDevice.Viewport.Bounds.Width,
            Height = GraphicsDevice.Viewport.Bounds.Height,
            MinDepth = 0,
            MaxDepth = 1
        };
        GraphicsDevice.SetRenderTarget(target);

        return true;
    }

    private void FinishDraw()
    {
        try
        {
            Rectangle sourceRectangle = new Rectangle(0, 0, GraphicsDevice.Viewport.Bounds.Width, GraphicsDevice.Viewport.Bounds.Height);

            // GraphicsDevice.Present(sourceRectangle, null, Handle);
            GraphicsDevice.Present();
        }
        catch
        {
            // GULP
        }
    }

    private bool IsDeviceAvailable()
    {
        bool deviceNeedsReset;

        switch (graphicsDevice.GraphicsDeviceStatus)
        {
            case GraphicsDeviceStatus.Lost:
                return false;
            case GraphicsDeviceStatus.NotReset:
                deviceNeedsReset = true;
                break;
            default:
                deviceNeedsReset = (GraphicsDevice.Viewport.Bounds.Width > parameters.BackBufferWidth) || (GraphicsDevice.Viewport.Bounds.Height > parameters.BackBufferHeight);
                break;
        }

        if (deviceNeedsReset)
        {
            try
            {
                ResetDevice(GraphicsDevice.Viewport.Bounds.Width, GraphicsDevice.Viewport.Bounds.Height);
            }
            catch
            {
                return false;
            }
        }

        return true;
    }

    public event EventHandler<EventArgs> DeviceCreated;
    public event EventHandler<EventArgs> DeviceDisposing;
    public event EventHandler<EventArgs> DeviceReset;
    public event EventHandler<EventArgs> DeviceResetting;

    public void ResetDevice(int width, int height)
    {
        if (DeviceResetting != null)
            DeviceResetting(this, EventArgs.Empty);

        parameters.BackBufferWidth = Math.Max(parameters.BackBufferWidth, width);
        parameters.BackBufferHeight = Math.Max(parameters.BackBufferHeight, height);

        graphicsDevice.Reset(parameters);
        if (DeviceReset != null)
            DeviceReset(this, EventArgs.Empty);
    }

    public override bool AllowUserResizing { get; set; }

    public override Rectangle ClientBounds
    {
        get
        {
            return graphicsDevice.Viewport.Bounds;
        }
    }

    public override Point Position { get; set; }

    public override DisplayOrientation CurrentOrientation => throw new NotImplementedException();

    public override IntPtr Handle => throw new NotImplementedException();

    public override string ScreenDeviceName => throw new NotImplementedException();

    public override void BeginScreenDeviceChange(bool willBeFullScreen)
    {
        throw new NotImplementedException();
    }

    public override void EndScreenDeviceChange(string screenDeviceName, int clientWidth, int clientHeight)
    {
        if (CanDraw())
        {
            Draw();
            FinishDraw();
        }
    }

    protected override void SetSupportedOrientations(DisplayOrientation orientations)
    {
        //
    }

    protected override void SetTitle(string title)
    {
        title = "Sub Game Window";
    }
}

And I have tried but GameWindow has not implements like Close() and Show()

How do I implement any Close() and Show() or Hide()

Do I need to add pInvoke into SubGameWindow with GameWindow?

:expressionless: As far as I could get is to make a window and draw triangle there…

Image

Seems like some features in GameWindow class are not supported by OpenGL. Using OpenTK library I could create window and draw primitives there.

//Create window using OpenTK
        private void CreateNewWindow()
        {
            x = new OpenTK.GameWindow();
            x.Visible = true;
            x.RenderFrame += X_RenderFrame;
            x.Run();
        }

        //Draw triangle into window
        private void X_RenderFrame(object sender, OpenTK.FrameEventArgs e)
        {
            OpenTK.Graphics.OpenGL.GL.Clear(OpenTK.Graphics.OpenGL.ClearBufferMask.ColorBufferBit);
            OpenTK.Graphics.OpenGL.GL.ClearColor(255, 0, 0, 255);
            OpenTK.Graphics.OpenGL.GL.MatrixMode(OpenTK.Graphics.OpenGL.MatrixMode.Projection);
            OpenTK.Graphics.OpenGL.GL.LoadIdentity();
            OpenTK.Graphics.OpenGL.GL.Begin(OpenTK.Graphics.OpenGL.PrimitiveType.Triangles);
            OpenTK.Graphics.OpenGL.GL.Color3(draw.Color.MidnightBlue);
            OpenTK.Graphics.OpenGL.GL.Vertex3(-0f, 1.0f, 0.0f);
            OpenTK.Graphics.OpenGL.GL.Color3(draw.Color.Blue);
            OpenTK.Graphics.OpenGL.GL.Vertex3(-1f, .0f, 0.0f);
            OpenTK.Graphics.OpenGL.GL.Color3(draw.Color.Violet);
            OpenTK.Graphics.OpenGL.GL.Vertex3(0.5f, 0.0f, 0.0f);
            OpenTK.Graphics.OpenGL.GL.End();
            x.SwapBuffers();
        }

I guess it is not the best solution, bypassing Monogame framework and using OpenTK directly :pensive: Though, if you don’t want to use winform probably you could get along with OpenTK, but it is more complex stuff.

@dryhfth I think you are right I will create as abstract class of OpenTK 's GameWindow and with MonoGame features than it will implement from UpdateFrame with abstract Update(GameTime gt) and RenderFrame with abstract Draw(GameTime gt) and Load with abstract LoadContent(), Unload with abstract UnloadContent()

Is it correct or wrong? But i am not sure if GameWindow of OpenTK will parent to MonoGame 's GameWindow ( Game ), i think it uses with pInvoke like SetWindow() or SetParent()

I will try after my work and after watching with football South Korea vs Germany.

I will create as abstract class of OpenTK 's GameWindow and with
MonoGame features than it will implement from UpdateFrame with abstract
Update(GameTime gt) and RenderFrame with abstract Draw(GameTime gt) and
Load with abstract LoadContent(), Unload with abstract UnloadContent()

Is it correct or wrong? But i am not sure if GameWindow of OpenTK will parent to MonoGame 's
GameWindow ( Game ), i think it uses with pInvoke like SetWindow() or
SetParent()

Sorry, I can’t tell you exactly, maybe some devs with answer)) As far as I understood, Monogame Engine uses DirectX API/OpenGL as a backend, depending on your environment (windows/linux/mac…), regardless of what you are doing in your code, unless you are using some specific features of that API & directly calling DX/GL functions.
I feel there must be a simpler way to do this… :pensive:

:rolling_eyes: I’ve found few dev discussions on github, seems like multiple windows for OpenGL are not supported at the moment, so you can either comment this issue & wait for them to answer or try to implement something by yourself.
Multiple Window Input Handling
Multiple Windows for DirectX
Multiple Windows for OpenGL <=

You can also try to work with different viewports, but it is more to deal with split screen, though might be helpful.
Vieports Guide

Good luck!:slight_smile:

@dryhfth Thanks for showing I am sorry I don’t see link about your example picture.

I have tried . But It seems like crazy.

If I use GetWindow() from User32.dll for Windows only

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using System;
using System.Runtime.InteropServices;

namespace MonoCrraft
{
    public abstract class SubGameWindow : OpenTK.GameWindow, IGraphicsDeviceService
    {
        private static GraphicsDevice graphicsDevice;
        private static Color clearColor;
        private static PresentationParameters presentationParameters;

        // For Windows
        [DllImport("User32")]
        private static extern IntPtr GetTopWindow(IntPtr hWnd);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr GetWindow(IntPtr hWnd, GetWindow_Cmd uCmd);

        public enum GetWindow_Cmd : uint
        {
            GW_HWNDFIRST = 0,
            GW_HWNDLAST = 1,
            GW_HWNDNEXT = 2,
            GW_HWNDPREV = 3,
            GW_OWNER = 4,
            GW_CHILD = 5,
            GW_ENABLEDPOPUP = 6
        }

        public SubGameWindow(IntPtr Parent)
        {
            presentationParameters = new PresentationParameters();
            presentationParameters.BackBufferWidth = Width;
            presentationParameters.BackBufferHeight = Height;
            graphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.HiDef, presentationParameters);
            GetWindow(Parent, GetWindow_Cmd.GW_OWNER);
        }

        public GraphicsDevice GraphicsDevice
        {
            get
            {
                return graphicsDevice;
            }
        }

        public Color ClearColor
        {
            get
            {
                return clearColor;
            }
            set
            {
                clearColor = value;
            }
        }

        public event EventHandler<EventArgs> DeviceCreated;
        public event EventHandler<EventArgs> DeviceDisposing;
        public event EventHandler<EventArgs> DeviceReset;
        public event EventHandler<EventArgs> DeviceResetting;

        protected override void OnLoad(EventArgs e)
        {
            LoadContent();
            base.OnLoad(e);
        }

        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            Update();
            base.OnUpdateFrame(e);
        }

        protected override void OnRenderFrame(FrameEventArgs e)
        {
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit);
            GL.ClearColor(ClearColor.R, ClearColor.B, ClearColor.G, ClearColor.A);
            graphicsDevice.Clear(ClearColor);
            Draw();
            base.OnRenderFrame(e);
            SwapBuffers();
        }

        protected override void OnResize(EventArgs e)
        {
            GL.Viewport(graphicsDevice.Viewport.Bounds.X, graphicsDevice.Viewport.Bounds.Y, graphicsDevice.Viewport.Bounds.Width, graphicsDevice.Viewport.Bounds.Height);
            base.OnResize(e);
        }

        protected abstract void LoadContent();
        protected abstract void Update();
        protected abstract void Draw();
    }
}

SettingWindow with SubGameWindow.cs

using Microsoft.Xna.Framework;
using System;

namespace MonoCrraft
{
    /**
     *  It is simple setting window
     */
    public class SettingWindow : SubGameWindow
    {
        MyGame MyGame;
        public SettingWindow(IntPtr Parent) : base(Parent)
        {
            Width = 300;
            Height = 300;
            Visible = true;

        }

        protected override void LoadContent()
        {
            
        }

        protected override void Update()
        {
            
        }

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

And MyGame.cs with SettingButton:

private void SettingHandler(object sender, EventArgs e)
{
    var setWin = new SettingWindow(Window.Handle);
    setWin.Run(60);
}

Than It looks ugly… If I close sub window than sub window moves to main window. I really don’t understand. But i really want fix that MonoGame’s Game.cs should catch / own to sub / child windows. . I think I need resolve…