Introduction
After writing my first article on XNA, my next interest in XNA moved me to figuring out how to host an XNA Window inside a WinForm, so that I can provide additional controls and other UI elements as part of this concept I'm trying to develop. This has proven to be a considerably arduous task.
And while I've made a blog post about my research, I felt that it would be of benefit to the Code Project community to actually have a brief article on the topic, even though I did not come up with the actual solution. Instead, this article:
- briefly describes the problem
- takes a quick look at two other solutions I tried
- presents a list of useful references that I discovered along the way
In the end, I found this website, which provides the C# source code for hosting an XNA Window as a user control. I'm not providing the download here as I didn't write it, so please visit Nuclex's website for the download.
So, the point of making this a Code Project article is to condense into a single article all the blogs and websites I visited to undestand the problem and find the solution. I certainly didn't come across the solution googling for "XNA WinForm". Hopefully, in a few days, other people will find this Code Project article using those keywords!
The First Problem: The Game Class Constructor
The primary problem is that the Game
class initializes its own Form
rather than letting you specify your Form/Control. This is a silly oversight by the designers. Let's use Reflector to understand the problem:
- The
Game
constructor has a call to EnsureHost
EnsureHost
is implemented as:
if (this.host == null)
{
this.host = new WindowsGameHost(this);
WindowsGameHost
instantiates a WindowsGameWindow
:
public WindowsGameHost(Game game)
{
this.game = game;
this.LockThreadToProcessor();
this.gameWindow = new WindowsGameWindow();
- And
WindowsGameWindow
finally instantiates the WindowsGameForm
, which is derived from Form
:
public WindowsGameWindow()
{
this.mainForm = new WindowsGameForm();
That's problem number one.
The Second Problem: The GraphicsDeviceManager CreateDevice Method
Problem number two is the GraphicsDeviceManager
. In your game's derived Game
class, you'll have in the constructor:
graphics = new GraphicsDeviceManager(this);
The GraphicsDeviceManager
class adds two services:
game.Services.AddService(typeof(IGraphicsDeviceManager), this);
game.Services.AddService(typeof(IGraphicsDeviceService), this);
and then, in the CreateDevice
method, the Form
control created by the Game
constructor is specified as the control host:
GraphicsDevice device = new GraphicsDevice(
newInfo.Adapter,
newInfo.DeviceType,
this.game.Window.Handle,
newInfo.CreationOptions,
newInfo.PresentationParameters);
So the problem is twofold -- the Game
class creates its own Form
and then that Window is passed to the GraphicsDevice
constructor.
Silly Solution #1: Adding Controls to the Game Window
The very first idea I came across cast the GameWindow
to a Control
and added controls to it. These actually rendered on the XNA Window and worked for things like buttons but did not work for edit controls, I suspect because there is some issue with message processing in the host form, though I never investigated it further.
A Better Solution, But Not Great
The second workaround I found was almost right, hooking the PreparingDeviceSettings
and overriding the DeviceWindowHandle
:
public Game1(Form form)
{
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
this.form = form;
graphics.PreparingDeviceSettings +=
new EventHandler<PreparingDeviceSettingsEventArgs>
(OnPreparingDeviceSettings);
}
void OnPreparingDeviceSettings(object sender,
PreparingDeviceSettingsEventArgs e)
{
e.GraphicsDeviceInformation.PresentationParameters.DeviceWindowHandle =
form.Handle;
}
This (and other solutions I came across) worked fine except that the XNA Window would still appear, requiring a kludge to hide it in the Draw
window:
protected override void Draw(GameTime gameTime)
{
if (firstTime)
{
System.Windows.Forms.Control xnaWindow =
System.Windows.Forms.Control.FromHandle((this.Window.Handle));
xnaWindow.Visible = false;
firstTime = false;
}
...
As well as properly disposing of it:
static class Program
{
static Game1 game;
static void Main(string[] args)
{
Form form = new Form();
form.Disposed += new EventHandler(OnDisposed);
form.ClientSize = new System.Drawing.Size(800, 600);
form.Show();
using (game = new Game1(form))
{
game.Run();
}
}
static void OnDisposed(object sender, EventArgs e)
{
game.Exit();
}
}
Other implementations, which are actually very straight forward (see references) typically eliminate the Game
class. This wasn't really my goal, as I wanted to preserve the features of the Game
class if at all possible. This led me to the conclusion that I would probably have to use Reflector to recreate specific classes of the framework. At this point, I shelved the whole effort and went back to work that actually pays the bills.
The Nuclex Solution
After putting in some billable hours, I revisited this and I came across a link in a reply to one of the references below that I hadn't checked out, and to my amazement, it was exactly what I was looking for. And indeed, the author does use reflector to recreate certain classes so as to gain control over the dual problems mentioned above. The link is here, and the implementation is very nice -- it's a user control, from which you derive your own class and work with the overridable methods just as you would from the Game
class. I ported my test application over in minutes using the Nuclex.GameControl
class.
I must say though that I'm still not happy with having to rewrite major sections of the framework. Hopefully the XNA framework architects will fix this problem in the future, and second, I still have a nagging feeling that there must be an easier way.
References
Various references (I found a lot of good information reading the comments as well):