Spanish Version

XNA and Windows Forms

Introduction

XNA's framework isn't prepared by default to be used with WindowsForms. This is because the graphicsDeviceManager, which is the one responsible of creating the window and initializing the device, won't let us specify the handle of the surface on which we want to draw. We do have the option of initializing the device manually, but then we wouldn't be taking advantage of the great functionality that the graphicsDeviceManager offers us. In this link, http://creators.xna.com, you can find two examples of how to use WindowsForms and XNA. The first one is called WindowsForms Series 1: Graphics Device, and it shows you how to create the device manually and how to use it to paint a triangle:

XNA_WinForms1.jpg

The problem with this example is that you don't have the ContentManager, so you can't load things such as textures, models, sounds, etc using XNA's contentloaders, and you have to create the triangle manually.
The other example, WinForms Serie 2: Content Loading, loads a texturized FBX:

XNA_Winform.jpg

If we take a look at the source code of this example, we will find that it is larger than the previous one. This is because a series of classes are created to make us of the ContentBuilder. To do this, it gets all the content files from our project, it uses a loader to compile this, and generates the xnb files in our working space.

I personally think that these aren't the best solutions. In the first one, you can't take advantage of the framework, so you might as well use DirectX. In the second one, the code that uses the ContentBuilder is a little messy. After investigating a bit, I found a solution that seemed to be partially ok. Its main feature is that it is very simple to implement, and you can use the default ContentManager.

I personally think that these aren't the best solutions. In the first one, you can't take advantage of the framework, so you might as well use DirectX. In the second one, the code that uses the ContentBuilder is a little messy. After investigating a bit, I found a solution that seemed to be partially ok. Its main feature is that it is very simple to implement, and you can use the default ContentManager.

Image1.jpg
Image2.jpg

Now we make it look like a small editor, with a menuStrip, a propertyGrid on the left, and a panel on the right, all separated using a splitter. It should look like this:

Image3.jpg

Now that we have this, let's make XNA draw on our panel. We go to our form's code and we add this:

public Control Panel
{
   get { return splitContainer.Panel; }
}


Now we only need to know that there is a way of changing the surface's handle. This surface is where XNA's device draws:

GraphicsDeviceInformation.PresentationParameters.DeviceWindowHandle


We will include this property right when Graphics is preparing the parameters to create the device. We make some changes in the program.cs file and the game.cs files:

The program file:

    static class Program
    {
        static Game1 game;

        [STAThread]
        static void Main(string[] args)
        {
            Main form = new Main();
            form.Disposed += new EventHandler(form_Disposed);
            using (game = new Game1(form))
            {
                form.Show();
                form.TopMost = true;
                game.Run();
            }
        }

        static void form_Disposed(object sender, EventArgs e)
        {
            game.Exit();
        }
    }


As you can see, we created a Game1 static variable, and then we initialized our form in the Main method. We need to tell Game to exit when the user closes the form, because we will let Game take care of our app's thread by using Game.Run(). For this we registered a method to the Disposed event. Next we created an instance of the Game1 class, which will take the form as a parameter. We called the form's Show method, and activated the TopMost option. Finally, we called game.Run().

You might be wondering why we activated the TopMost option. The reason is that the graphicsDeviceManager will create a window by default, but this is not the one that we want to use, so we will have two windows on the screen. To hide this we tell our form to be over all the other windows, so that the default window will be hidden behind our form. By then, we will have already changed XNA's handle where we are going to draw, so it will draw on our form's panel. Since there is no way of telling the graphicsDeviceManager not to create the default window, we will make it invisible once it has the focus, using the Visible property and restoring the TopMost property of our form to false.

If you don't do this, you will see the windows that XNA creates pop up and disappear quickly. This is the reason why I said that this solution is ok but not perfect, although this is the only objection.

Now we write code in Game. To start with, we need an atribute of the same type as our form, and we need to initialize it in our constructor:

Main form;
public Game1(Main form)
{
   this.form = form;


Next we have to:
 - Change the handle on which XNA will draw.
 - When the default window gets the focus, we will make it invisible and we will change the TopMost property of our form.
- Adapt the device and the camera's aspectRatio every time that the panel's size changes, in order to keep proportions.


Changing the handle on which XNA will draw

We add this to Game's constructor to register to the event that prepares the device's parameters:

graphics.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(graphics_PreparingDeviceSettings);


Now we change the handle on which the device will paint, in the method:

void graphics_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
{
    e.GraphicsDeviceInformation.PresentationParameters.DeviceWindowHandle = form.Panel.Handle;
}


When the default window receives focus, we make it invisible and we change the TopMost property of our form

We write this in the constructor, to get the windows that XNA creates by default:

Form xnaWindow = (Form)Control.FromHandle((this.Window.Handle));


Next, we register the GotFocus method. This method will make the window invisible and will change the TopMost property's value:

xnaWindow.GotFocus += new EventHandler(xnaWindow_GotFocus);
void xnaWindow_GotFocus(object sender, EventArgs e)
{
    ((Form)sender).Visible = false;
    form.TopMost = false;
}


Adapt the devide and the camera's aspectRatio each time the panel's size changes

We will register a method to the panel's Resize event in the constructor, so that we can change the size properties of the device and the camera's aspectRatio:

form.Panel.Resize += new EventHandler(Panel_Resize);
void Panel_Resize(object sender, EventArgs e)
{
   graphics.PreferredBackBufferWidth = form.Panel.Width;
   graphics.PreferredBackBufferHeight = form.Panel.Height;
   aspectRatio = (float)form.Panel.Width / form.Panel.Height;
   graphics.ApplyChanges();
}


That's it! Now we can use XNA and its ContentManager to create hybrid applications with WindowsForms. Here is an example:

Image4.jpg


Ejemplo

You can download it in the spanish version**


Traslated by Juliet R. Moreiro Bockhop

Created by Javier Cantón Ferrero.
MVP XNA-DirectX 2007/2009
MSP 2006/2008
Date 06/09/2008
Web www.codeplex.com/XNACommunity
Email javiuniversidad@gmail.com
blog: mirageproject.blogspot.com

Last edited Sep 6, 2008 at 2:20 PM by khronos, version 2

Comments

CodePlexd Sep 20, 2012 at 12:34 AM 
I agree, the official solutions to this problem are horrible.

I've tried to convince Shawn to fix it properly, I even gave him code from my personal SlimDX solution, however, XNA has yet to implement anything to address this issue. (Seriously, I GAVE you the code, WHAT is the problem... ???)

In short, don't use XNA, use SlimDX, it's more work, but at least it gets done properly. :P

RolandasR Jul 27, 2012 at 6:12 PM 
Nice solution, however, topmost is not needed. What I did, just changed Initialize method for the Game class:
protected override void Initialize()
{
base.Initialize();
xnaWindow = (System.Windows.Forms.Form)System.Windows.Forms.Control.FromHandle((this.Window.Handle));
xnaWindow.GotFocus += OnGotFocus;
form.Panel.Resize += OnPanelResize;
form.Show();
}
Also, in main method this is enough:
using (game= new Game(form))
{
game.Run();
}
And no visible window disappearance with that. Also, DoubleBuffering set to true for the Windows Form.

devarde Jan 15, 2011 at 8:17 PM 
davbeer actually this is a piece of tutorial that is misleading. It doesn't comment but Main should be replaced by the class that represents the form. In my class the line is written of the folow way:
Editor form = new Editor

davbeer Nov 23, 2009 at 2:22 PM 
Hey thanks for the tutorial, but something won't work. I have a problem with: ... Main form = new Main(); .... Main doesn't work ?!