Bringing your XNA WinForms Controls to MonoGame + OpenGL

If you’ve been embedding XNA into your WinForms-based applications for games or editors, you probably built your solution off of the WinForms Series 1: Graphics Device code sample on MSDN.  This solution implemented a GraphicsDeviceControl that could essentially embed the underlying DirectX buffer into a Panel, and a new GraphicsDeviceService implementation to manage one or more instances of your controls.

A modified version of the WinForms Series demo using two SpinningTriangleControls.

A modified version of the WinForms Series demo using two SpinningTriangleControls.

If you’ve tried porting your entire stack to MonoGame, you likely discovered that the several parts of the GraphicsDevice API entries that the WinForms Series implementation depended on are not implemented.  The missing entries are specific overloads of the GraphicsDevice constructor, GrpahicsDevice.Present, and GraphicsDevice.Reset.  At this time of writing, these entries are still not implemented.  I’m not sure if it would be possible to port GraphicsDeviceControl to MonoGame’s SharpDX backend without them, but it turns out that we don’t need them if we use the OpenTK backend instead.  Given that OpenTK will make your applications friendlier to port across to other systems, I’d say it’s the better choice anyway.

Updated 7/28/2013 to reflect changes in public MonoGame API.

Getting Started

Before we get started, you’ll need a copy of OpenTK’s GLControl library.  MonoGame only ships with the base OpenTK library, so unless you want to build your own copy of OpenTK and GLControl from source to replace the MonoGame dependency, I suggest grabbing a copy of the GLControl source and directly embedding it in your project.  It’s a tiny code base and MIT-licensed, so there should be no barriers to doing so.  You can download a tarball of the latest GLControl source directly from their SVN repository here.

I’ll assume you’re either starting with a stock set of files from the WinForms Series, or are working off of something very close in an existing project.  Swap out all of your XNA references for MonoGame.Framework and OpenTK, add the GLControl code to your project, and we’ll move straight to the remaining code changes.

GraphicsDeviceControl.cs

  1. Modify the class definition of GraphicsDeviceControl to extend from GLControl instead of Control.

    abstract public class GraphicsDeviceControl : GLControl
    
  2. Modify the BeginDraw method and add the following code snippet above Viewport viewport = new Viewport();

    GLControl control = GLControl.FromHandle(graphicsDeviceService.GraphicsDevice.PresentationParameters.DeviceWindowHandle) as GLControl;
    if (control != null) {
        control.Context.MakeCurrent(WindowInfo);
        graphicsDeviceService.GraphicsDevice.PresentationParameters.BackBufferHeight = ClientSize.Height;
        graphicsDeviceService.GraphicsDevice.PresentationParameters.BackBufferWidth = ClientSize.Width;
    }
    

    Change identifiers as necessary to match the rest of your class. The purpose of this block is to make the current control the “current” target for the shared OpenGL rendering context. Since OpenGL also has a different concept of the backbuffer dimensions than DirectX, we can simply update the PresentationParameters of the GraphicsDevice with the current control size, rather than relying on an implementation of GraphicsDevice.Reset to do it for us.

  3. Modify the EndDraw method and replace all the code within the try block with this single call:

    SwapBuffers();
    

    This will display everything held in the back buffer, rather then relying on an implementation of GraphicsDevice.Present to do it.

GraphicsDeviceService.cs

  1. Modify the GraphicsDeviceService constructor and replace its entire contents with:

    GraphicsAdapter adapter = GraphicsAdapter.DefaultAdapter;
    GraphicsProfile profile = GraphicsProfile.Reach;
    PresentationParameters pp = new PresentationParameters() {
        DeviceWindowHandle = windowHandle,
        BackBufferWidth = Math.Max(width, 1),
        BackBufferHeight = Math.Max(height, 1),
    };
    graphicsDevice = new GraphicsDevice(adapter, profile, pp);        
    

    Most of the PresentationParameters are no longer relevent.

    Note: If you are using an older snapshot of MonoGame (I don’t know the exact cutoff), then you may find that there is no 3-argument constructor for GraphicsDevice. If you would like to compile against these older snapshots of MonoGame, then use this code instead:

    graphicsDevice = new GraphicsDevice();
    graphicsDevice.PresentationParameters.DeviceWindowHandle = windowHandle;
    graphicsDevice.PresentationParameters.BackBufferWidth = Math.Max(width, 1);
    graphicsDevice.PresentationParameters.BackBufferHeight = Math.Max(height, 1);
    

    This was the code in place at the original publication of the guide. The downloadable sample uses the newer revision.

  2. Delete all of the code within the ResetDevice method, making this call a no-op.

That’s it!

You should be able to render multiple independent controls. If you’re working off the WinForms Series example, you may notice that the left control is rendering with a giant red X and threw an exception. That control depends on loading a SpriteFont from a content project which you may not have translated to MonoGame. Modifying your example to use two TriangleControls instead would demonstrate multiple controls in action.

If you’re looking for techniques to embed an actual game into your new GraphicsDevice Control, check this next entry in the series. If you need to use MonoGame keyboard input with your control, there’s an entry for that too. An example repository can be found here.

A complete sample project is available using the download link below. Another usage example can be found in the demo application of my MonoGame drawing library LilyPath.

GraphicsDeviceControl Demo
31.24 kB - Downloaded 1942 times

About jaquadro

I am a full time software developer and part time game and game tooling developer.
This entry was posted in Tutorials and tagged , , . Bookmark the permalink.

24 Responses to Bringing your XNA WinForms Controls to MonoGame + OpenGL

  1. Luis says:

    When I run my project, and use a SpriteBatch in my control, i get an argument is null exception. I dug further and the _d3dContext of the GraphicsDevice is null. Any ideas why?

    • jaquadro says:

      Anything D3D would indicate that you’re using a SharpDX back-end, which this guide is specifically not for. You may want to consider compiling MonoGame from source, so you can know for sure that it’s built from the MonoGame.Framework.WindowsGL project.

      I haven’t actually bothered with MonoGame’s installer at all, so if it’s shipping the SharpDX build by default for Windows, I should make a note about it in the guide.

      • Luis says:

        Yea, I used the monogame installer. Even though I chose the WindowsGL dll it was a windows 8….

        After I got the code from the repository everything worked.

        Another question though, how would you handle MouseEvents in this control? I am using protected override void OnMouseDown(MouseEventArgs e). Should i be using the WindowsGL implementation?

        • jaquadro says:

          Basically just do whatever you did for your XNA-based controls. GLControl is just a plain old Control with a little bit of extra GL work going on underneath.

          In my current editor I attach to the control’s Mouse* events from an outside controller, but you can also override the OnMouse* methods.

  2. This is brilliant. I was just wondering today how hard it might be to create a GUI editor in WinForms with MonoGame. After some googling I found this and it works great! Thanks a bunch.

  3. KaseyBee says:

    Thanks very much for this extremely useful post.

    I’ve been trying to adapt it to my situation but am not having much luck. Essentially, I have a GameControl class that extends GraphicsDeviceControl and allows a Game instance to be passed in. A Timer then calls Tick() on the Game.

    To test, I created the simplest possible Game that just clears the background to a specific color. I then passed an instance of this to GameControl and hosted it in a Form. It worked.

    Next, I tried adding a 2D texture to my game. I then use a SpriteBatch to render that texture in the same position on every update. This works when running my game directly, but not when hosting inside Winforms via my GameControl.

    I’m a little confused as to why this would be. Do you have any idea what might causes this?

    Here’s my GameControl:

    namespace WinformsHost
    {
    using EmbedInWinformsTest;
    using Microsoft.Xna.Framework;
    using System.Windows.Forms;
    using WinFormsGraphicsDevice;

    public class GameControl : GraphicsDeviceControl
    {
    Timer timer;
    Game game;

    protected override void Initialize()
    {
    // a test Game to play with
    game = new Game1();

    // this works (ie. the texture renders correctly), but obviously blocks
    //game.Run(GameRunBehavior.Synchronous);

    // this is just to ensure the Game initializes
    game.RunOneFrame();

    timer = new Timer();
    timer.Interval = 30;
    timer.Tick += (s, e) => this.Invalidate();
    timer.Enabled = true;
    }

    protected override void Draw()
    {
    game.Tick();
    //game.RunOneFrame();
    }

    protected override void Dispose(bool disposing)
    {
    base.Dispose(disposing);

    if (disposing)
    {
    timer.Dispose();
    game.Dispose();
    }
    }
    }
    }

    • justin says:

      If I understand correctly, you’re trying to get a Game() object to render into your GameControl?

      I remember trying to tackle this with XNA a long time ago, and my conclusion at the time was that it can’t be done with the stock Game object. Game contains its own window-derived Control that ultimately comes from a private instance of GamePlatform.

      Now while this fell completely flat on its face with DirectX-based XNA, OpenGL is a bit different, and your test behavior of being able to clear the background color but not do much else seems to exactly mirror my initial experimentation with getting multiple GL controls to work together, as described here: http://monogame.codeplex.com/discussions/431834

      OpenGL has this concept of a single active context that intercepts all of the GL API calls, like Clear. But it also has a concept of rendering contexts that also need to be applied to the actual control you’re drawing to. The line control.Context.MakeCurrent(WindowInfo); from the guide is the magic sauce that makes that work. To make this work for Game, you’re would need to get at a very deep underlying OpenTK.GameWindow object which is of course not part of the public API.

      Since MonoGame is open you’ve got a couple choices here. The first, which didn’t apply to XNA, its to modify your copy of MonoGame to expose an OpenTK.GameWindow, and use that in creating your form’s GraphicsDeviceService. But I’m really only speculating here because I haven’t tried it.

      The way I dealt with this in XNA was to avoid using the Game class altogether. I created my own GameControl that extended GraphicsDeviceControl and implemented the important parts of the Game API. Then I extended that with a new class that implemented my custom game logic, which would be akin to your Game1 class. How well this works depends on how you’ve architected your engine. I didn’t need instances of Game anywhere except as the main execution wrapper, so it was easy to replace. If you pass Game instances all over, then this approach would not work.

      • KaseyBee says:

        Thanks for the prompt and helpful reply. I’m trying to apply what you’ve pointed me to, but am having trouble tracking down the Engine type you’ve used in your AmphibianGameControl class. Can you tell me what that is?

        • KaseyBee says:

          Aha! Nevermind – I think I’ve figured it out. Engine was obviously specific to your game engine, right?

          I’ve just gotten a rudimentary proof of concept working. I now have my texture rendering inside a winforms app. I will need to clean it all up before using it for real, but now I at least know what I’m working towards.

          Thank you so much for your help – it was invaluable.

  4. John says:

    Hey nice tutorial!

    I have a question, does your Input work?

    I can’t get any from mine.

    Example:

    KeyboardState keyState = Keyboard.GetState();
    if(keyState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.A))
    MessageBox.Show("AA");

    • jaquadro says:

      Thanks for pointing this out. I don’t think I ever tried. For my Windows.Forms-based usage, I’ve always used mouse events on the Windows control itself, or the Windows.Forms keyboard events/state.

      I went digging deeper and decided it was worth a separate post. I hope that provides an acceptable solution.

      • John says:

        I started working on my approach before seeing your post and I did exactly what you did 🙂

        I searched in the OpenTKGameWindow Code and saw how they managed to work with keyboard input.

        This post was very helpful thanks

  5. winsrp says:

    turns out that installing the new 3.2 monogame dev branch breaks this solutions, =/

  6. EvilConifer says:

    Hey! I really loved this tutorial, great job (Even though OpenTK was a nightmare to build :p)

    Anyways, I’m probably missing something incredibly obvious here, but when I try to instantiate graphicsDevice, I get an error saying:

    ‘Microsoft.Xna.Framework.Graphics.GraphicsDevice’ does not contain a constructor that takes 3 arguments

    What am I missing? Some help would be greatly appreciated :3

  7. Vladimir says:

    Hey jaquadro,

    first of all I wanna say thank you for this article. It helped me to migrate my 3d model preview control from XNA to mono (migration is not successfully finished because I run onto a problem 🙁 ) When i try drawing model in normal WindowsGL game project, it draws perfect, but when i draw it in GLControl it is messed up. Here is a link where you can see what I’m talking about:

    http://postimg.org/image/5xdkxha9l/

    Left one is one from GLControl and right one is from WindwsGL project, for both I used same code to draw them.

    foreach (BasicEffect effect in model.Meshes[i].Effects)
    {
    effect.EnableDefaultLighting();
    effect.SpecularPower = 100000;
    Matrix worldTransformation = currentTransformation [model.Meshes[i].ParentBone.Index] * Matrix.CreateRotationY(modelRotation) * Matrix.CreateTranslation(modelPosition);
    effect.World = worldTransformation;
    effect.View = view;
    effect.Projection = projection;
    }
    model.Meshes[i].Draw();
    }

    Do you have any idea how can i fix this?

    Thanks in advance!

  8. Mattredsox says:

    All I get is a black screen when I use the code from the github. The game works perfectly in the non-embedded project but the Winform one just has the little buttons and a black screen where the game is supposed to be.

    Any ideas?

  9. Serg says:

    It didn’t works for me, ill do all what you propose, but got an error when i’m trying to create device
    _device = new GraphicsDevice(adapter, profile, pp);
    just null reference exception 🙁

    • Damien says:

      Stay posted on the MonoGame website my friend, im making a DLL called MonoGame.Framework.Control and it will also be listed on Nuget it will support GL and DX(ITS ALREADY WORKING!) it just needs one small glitch fixed which im working on then I will be uploading it tonight or tomorrow.

  10. Frankie Albin says:

    Hello,

    Thank you so much for this! I’m having a problem though. I’m getting a FileNotFoundException in the GraphicsDeviceService.cs on the line:

    graphicsDevice = new GraphicsDevice(adapter, profile, pp);

    Additional information: Could not load file or assembly ‘System.Runtime.WindowsRuntime, Version=4.0.10.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’ or one of its dependencies. The system cannot find the file specified.

    I’m not sure what’s wrong, I followed your tutorial here exactly, if you have any ideas to help that would be great. Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *