How To (Properly) Use Windows Forms With XNA

Only works with XNA GSE 1.0 Refresh, not 2.0!

Update : See this post for a working sample!

Here’s something I had some difficulty doing “gracefully” with XNA : force a custom render device and user controls in the window while still using the Game framework. It’s quite easy to initialize XNA in a Managed DirectX fashion but you lose the links to the graphics device manager, the content pipeline and a lot of very useful (and well written) code behind the Game class.

I initially tried to mimic the functionality of the Game class with a home-made class, make my own ServiceProvider and everything. But by using Lutz Roeder’s .NET Reflector I found out that Microsoft actually made everything better than I usually did; the elapsed-time management, how to hook the events on the form, the overall structure… So I started reverse-engineering bits of it, then I ended up copying the whole goddamn assembly (because of course most of it is internal and sealed) just to allow me to use my own form as the viewport! Ridiculous. There had to be a better way.

And there is!! In the comments of the above article, a user named chrisf suggested to use Control.FromHandle() on the handle provided by the GameWindow class, and use the resulting Control to add whatever controls inside of it.

I just extended the technique by casting the Control to a Form (the implementation class is WindowsGameForm, an internal class that extends Form) and doing pretty much everything I want on it. The operations are done in a standard GameComponent that represents the form’s controls, and is the host of all the events.

First, make a standard GameComponent :

public class EditorControls : GameComponent
{
	public EditorControls(Game game) : base(game) { }

Then make a reference to the form (and to the graphics device) :

	Form windowsGameForm;
	IGraphicsDeviceService graphicsService;
	GraphicsDevice graphics;

	public override void Initialize()
	{
		graphicsService = Game.Services.GetService(typeof(IGraphicsDeviceService)) as IGraphicsDeviceService;
		graphics = graphicsService.GraphicsDevice;

		windowsGameForm = Control.FromHandle(Game.Window.Handle) as Form;

And add a method to add whatever components you want, in this example a MenuStrip and a rendering Panel. The cool thing is that you can do the form editing in a real form, then copy the code from the “designer” partial class underneath it :

		InitializeComponent();
	}

	Panel RenderPanel;
	MenuStrip MainMenu;

	void InitializeComponent()
	{
		MainMenu = new MenuStrip();
		RenderPanel = new Panel();
		MainMenu.SuspendLayout();
		windowsGameForm.SuspendLayout();
		MainMenu.Location = new System.Drawing.Point(0, 0);
		MainMenu.Name = "MainMenu";
		MainMenu.Size = new Size(741, 24);
		MainMenu.TabIndex = 0;
		RenderPanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right ;
		RenderPanel.Location = new System.Drawing.Point(0, 49);
		RenderPanel.Name = "RenderPanel";
		RenderPanel.Size = new Size(800, 600);
		RenderPanel.TabIndex = 2;
		windowsGameForm.Controls.Add(MainMenu);
		windowsGameForm.Controls.Add(RenderPanel);
		MainMenu.ResumeLayout(false);
		MainMenu.PerformLayout();
		windowsGameForm.ResumeLayout(false);
		windowsGameForm.PerformLayout();
	}

So this is all swell, but how does one render to the panel now? We need to change the device window manually, but it’s quite easy to do :

		// In Initialize() :
		graphicsService .DeviceResetting += new EventHandler(OnDeviceReset);
		graphicsService.DeviceCreated += new EventHandler(OnDeviceCreated);
		graphics.Reset();

	void OnDeviceCreated(object sender, EventArgs e)
	{
	    graphics = graphicsService.GraphicsDevice;
	    graphics.Reset();
	}

	void OnDeviceReset(object sender, EventArgs e)
	{
	    graphics.PresentationParameters.DeviceWindowHandle = RenderPanel.Handle;
	    graphics.PresentationParameters.BackBufferWidth = RenderPanel.Width;
	    graphics.PresentationParameters.BackBufferHeight = RenderPanel.Height;
	}

And that’s it! Now you can “easily” make your own form (for a level editor it’s a necessity) with controls and events just like you’d do in a normal Windows Form, but in a full XNA context.

Edits : The “Fill” docking mode doesn’t seem to work well with a render panel and a menu bar, anchors work better. Also, the OnDeviceCreated event fixes a bug on multiple screens.

11 thoughts on “How To (Properly) Use Windows Forms With XNA”

  1. Can you post a full sample? I’m only a beginner but I’m really interested in this. I tried to follow your brief instructions but I;m failing miserably without further pointers. :-(

  2. Hi,

    Just to let you know that the XNA Form eats all keyboard input, so while using the mouse will work fine you might have trouble entering any text into the controls.

    The simplest method I’ve found for adding controls is to create a new form, as a child of the game form. They will be seperate windows but it doesn’t make much difference.

    Cheers,
    Leaf.

  3. Hey – I got it working now… but is there a way to overlay buttons on the render panel? or is it never the two shall meet? ie. My buttons can only render into non ‘Render Panel’ area.

    It’s cool though – thanks.

  4. Leaf: Thanks for the input (uh, no pun intended), I didn’t know that. It doesn’t really matter to me, but it might to others…

    snarg: I don’t think you can overlay controls on the render target… To do that you’d better make your own GUI system directly in the XNA context. I’m sure you’d meet a lot of problems (like focus or grey outlines around the controls) by overlaying standard controls.
    Still interested in a sample?

  5. Definitely still interested. I can send you what I made too if you send me your email address?

    (boyle.adam@gmail.com)

  6. I tried, but can’t get it to work (how does the EditorControls link to the Form?). Any chance you can post a working project?
    Thanks so much!
    Bart

  7. Hey guys, I wrote this Button control in XNA. thought you might be intrested.
    Here are the textures that it uses: http://imgur.com/vQ0X2,NivZO,mPj3n,XQIOp#0

    the download of the following files are needed:

    https://dl-web.dropbox.com/get/Public/Enums.cs?w=32be619b
    https://dl-web.dropbox.com/get/Public/Button.cs?w=7e69a461
    https://dl-web.dropbox.com/get/Public/Animation.cs?w=fac0b5c0
    https://dl-web.dropbox.com/get/Public/Start.wav?w=db038b8f

    you might need some minor changes in Button.cs at asset names depending on where you image files are located within your ContentRootFolder. Also you mustn’t forget to change the namespace, by default it’s Blue_Ball_2, or you can also put a using statement in your Game1.cs. I hope this helped.

    cheers, Máté

Leave a Reply

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