Using XNA in a Windows Forms ApplicationSo, running an XNA game on it's own is easy, using the Game class. Now we want to run it in a Windows Forms environment, perhaps as the graphical component to a larger application. The first instinct may be to find some way to insert a Game Class into a Form. Besides being completely impractical (although possible, I actually found somebody who did it), this is the wrong idea. The Game class is meant as a standalone - it would be completely independent of any other controls on the form, which defeats the purpose of using it in the first place. So, we have to build our own XNA environment. Difficult, but not too difficult.
Step 1: FoundationsThe main component any XNA environment needs is a Graphics Device. The easiest and most useful place to create this is in a Control, which will fit nicely into the Forms framework. Lucky for us, the code for this has already been provided in this sample project:
http://create.msdn.com/en-US/education/catalog/sample/winforms_series_1.
Download the code, and copy ServiceContainer, GraphicsDeviceService, and GraphicsDeviceControl over to your project, making sure to edit their namespace to match your project. Now, you have GraphicsDeviceControl, a control class that provides a GraphicsDevice for XNA to use.
Step 2: ContentAnother key part of XNA is it's ability to load content. If you have static content that you want to include with the program, just create a content project and reference it in the forms application. However, if you are using a Forms application, chances are that you want to load content on the fly. Now we have another sample project:
http://create.msdn.com/en-US/education/catalog/sample/winforms_series_2.
This time, you just need ContentBuilder and Error Logger, although it may be useful to keep ModelViewerControl as a reference. ContentBuilder has additional instructions in case you want to add pipeline assemblies for custom formats. That takes care of dynamically loading content.
Step 3: TimeWe seem to be so close, but we are so far away. We don't have an Update function to work with, and we don't have a GameTime object to give us time. Clearly, these are big problems.
Thankfully, we have the System.Diagnostics.Stopwatch class. It keeps time in Ticks, which are 100 nanosecond intervals, but you don't really need to know that. All that matters is that we have something that keeps time, so now we can have an update function that gets called at specific intervals. To do this processing, we will use a function inside the top level control (the outermost one, called form1 by default) named MainLoop:
/// <summary>
/// Performs main processing for the form, maintaining a certain FPS.
/// </summary>
public void MainLoop()
{
//FPS set up
double FPS = 30.0;
long ticks1 = 0;
long ticks2 = 0;
double interval = (double)Stopwatch.Frequency / FPS;
while (!this.IsDisposed)
{
Application.DoEvents();
ticks2 = Stopwatch.GetTimestamp();
if (ticks2 >= ticks1 + interval)
{
// Insert timekeeper code here
// ---------------------------
ticks1 = Stopwatch.GetTimestamp();
UpdateXNA();
}
}
}
This code uses the Stopwatch class to call a method named UpdateXNA() on precise intervals. Now you just need to create your own UpdateXNA() method, and you have fully functioning XNA...
Except you are still missing a replacement for GameTime. You need to know how much time has passed since the last update. There are two solutions, but we will try the obvious one first. Two lines before we call UpdateXNA(), where we see space to insert timekeeper code, we can store information in a float variable. All frame-by-frame processing can be based off of this float value, which is measured in seconds. You could simply pass it to UpdateXNA, or keep it as a public variable. Here is the code:
float elapsedTime = ((float)(ticks2 - ticks1)) / Stopwatch.Frequency;
That is all good if you are starting from scratch, but if you are transferring code from a standalone project, you may need an instance of GameTime, as all your methods require it. Again, this instance would be passed to your UpdateXNA function, to be passed around in your game. In the timekeeper code area, we must instead insert different code:
GameTime g = new GameTime(new TimeSpan(ticks2), new TimeSpan(ticks2 - ticks1));
Now, for the finishing touch: you want this MainLoop to be running at all times. There is a simple solution using only a bool variable and an Activate event:
protected bool running;
/// <summary>
/// Start processing.
/// </summary>
private void MainForm_Activated(object sender, EventArgs e)
{
if (!running)
{
running = true;
MainLoop(); //start the animation first time window appears
}
}
Just link the event in the forms editor, and everything should be running smoothly. Congratulations, you just created an XNA environment.
Step 4: Working with ControlsAlright, the environment is set, let's get into it. The first thing to do is to create a control to house your game. The declaration could look like:
public class GameControl : GraphicsDeviceControl
simply give your class an UpdateXNA() function, possibly requiring either a float or a GameTime, and take it from there.
To place your control on the main form, open up the windows forms toolbox and scroll all the way up. It should be sitting under a group named after your project. Simply drag and drop, and resize as needed. Of course, you can also add it using code, just like any other control.
Step 5: Interaction between XNA and FormsHey, wouldn't it be cool if other controls could interact with XNA? Maybe a toolbar that loads savegames, or dynamic color changing panels. In any case, you can give any custom control an UpdateXNA() function, which will link it to the same timing as the XNA environment. You can also use this to have multiple XNA environments that are synchronized.
Author's NotesYeah, that was fun. A week's worth or research summarized in a few pages. Hopefully, this gives you enough options to allow you to implement whatever you want in XNA, whether it be a game, and rendering environment, or some other project. There's a lot more to be said about manipulating 2D and 3D environments, but I'll save that for a little later when I've worked out a few more issues.