XNA Multi Monitor Support
Ancient Knowledge
This article is getting old. It was written in the ancient times and the world of software development has changed a lot since then. I'm keeping it here for historical purposes, but I recommend you check out the newer articles on my site.
The information that follows pertains to an older version of XNA and may not be applicable Imagine an XNA game running two monitors, one for the 3d game, and a second having a full screen tactical map of the game. The easy part is actually splitting the game windows to multiple monitors, the difficult part is synchronizing the two parts to work together (which I will leave up to you). You can create a simple subclass of GraphicsDeviceManager and then create the two game windows on separate threads. When instantiating the Game class, pass in the index of the monitor you want to target (the index is visible in the display options dialog of your pc).
public class TargetedGraphicsDeviceManager : GraphicsDeviceManager
{
private int target = 1;
public TargetedGraphicsDeviceManager(Game game, int displayTarget) : base(game)
{
target = displayTarget;
}
protected override void OnPreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs args)
{
args.GraphicsDeviceInformation.PresentationParameters.FullScreenRefreshRateInHz = 0;
args.GraphicsDeviceInformation.PresentationParameters.IsFullScreen = false;
base.OnPreparingDeviceSettings(sender, args);
}
protected override void RankDevices(List<GraphicsDeviceInformation> foundDevices)
{
List<GraphicsDeviceInformation> removals = new List<GraphicsDeviceInformation>();
for (int i = 0; i < foundDevices.Count; i++)
{
if (!foundDevices[i].Adapter.DeviceName.Contains("DISPLAY" + target))
removals.Add(foundDevices[i]);
}
foreach (GraphicsDeviceInformation info in removals)
{
foundDevices.Remove(info);
}
base.RankDevices(foundDevices);
}
}
In your game class, just replace the GraphicsDeviceManager with your new subclass and provide a new constructor for targeting the display index. We also add a call in the Update method to toggle to full screen on the first pass.
public class AmazingGame : Microsoft.Xna.Framework.Game
{
TargetedGraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
public Game1()
{
}
public Game1(int monitorIndex) : this()
{
graphics = new TargetedGraphicsDeviceManager(this, monitorIndex);
Content.RootDirectory = "Content";
}
protected override void Update(GameTime gameTime)
{
// full screen
if (!graphics.IsFullScreen)
graphics.ToggleFullScreen();
// other code
base.Update(gameTime);
}
// ALL OTHER CODE FOR THE GAME CLASS
}
In your applications call to Main() you need each game window running on its own thread, so create a new thread for each window you want to run and call Join() to wait for it to exit.
static class Program
{
static void Main(string[] args)
{
ThreadStart runLeftSideDelegate = new ThreadStart(RunLeftSide);
Thread runLeftSideThread = new Thread(runLeftSideDelegate);
runLeftSideThread.IsBackground = false;
runLeftSideThread.Start();
ThreadStart runRightSideDelegate = new ThreadStart(RunRightSide);
Thread runRightSideThread = new Thread(runRightSideDelegate);
runRightSideThread.IsBackground = false;
runRightSideThread.Start();
runLeftSideThread.Join();
runRightSideThread.Join();
}
static void RunLeftSide()
{
int index = 1;
using (AmazingGame game = new AmazingGame(index))
{
game.Run();
}
}
static void RunRightSide()
{
int index = 2;
using (AmazingGame game = new AmazingGame(index))
{
game.Run();
}
}
}
That's pretty much as easy as it gets, and there may be some room for improvement there. You can use some other classes to help synchronize your game windows, just remember to thoroughly check for run/lock/race conditions in the shared classes and data.