Available on NuGet

By adding the NuGet package to a project it is possible to host MonoGame inside WPF windows.

Example

publicclassMyGame : WpfGame
{
privateIGraphicsDeviceService_graphicsDeviceManager;
privateWpfKeyboard_keyboard;
privateWpfMouse_mouse;
protectedoverridevoidInitialize()
{
// must be initialized. required by Content loading and rendering (will add itself to the Services)// note that MonoGame requires this to be initialized in the constructor, while WpfInterop requires it to// be called inside Initialize (before base.Initialize())_graphicsDeviceManager=newWpfGraphicsDeviceService(this);
// wpf and keyboard need reference to the host control in order to receive input// this means every WpfGame control will have it's own keyboard & mouse manager which will only react if the mouse is in the control_keyboard=newWpfKeyboard(this);
_mouse=newWpfMouse(this);
// must be called after the WpfGraphicsDeviceService instance was createdbase.Initialize();
// content loading now possible
}
protectedoverridevoidUpdate(GameTimetime)
{
// every update we can now query the keyboard & mouse for our WpfGamevarmouseState=_mouse.GetState();
varkeyboardState=_keyboard.GetState();
}
protectedoverridevoidDraw(GameTimetime)
{
}
}

Now you can use it in any of your WPF forms:

<MyGame Width="800" Height="480" />

Breaking changes (compared to monogame)

Some of the Monogame classes are incompatible with WPF (Game always spawns its own window, Mouse doesn't care which control has focus, ..) so they had to be reimplemented.

As a convention, all reimplemented classes will have the prefix Wpf:

WpfGame as a replacement for Game class. Note that due to WPF limitations the WpfGame will always run at a maximum 60 FPS in fixed step (Update and Draw are always called, no Updates are skipped). It is possible to lower the framerate via TargetElapsedTime)

WpfMouse and WpfKeyboard provide input per host instance. When multiple WpfGame instances are spawned, only one will receive input at any time

WpfGraphicsDeviceService as an implementation of IGraphicsDeviceService and IGraphicsDeviceManager (required by the content manager)

WpfGameComponent and WpfDrawableGameComponent as a replacement for the original ones which required a reference to a Game instance

Mouse behaviour

Focus

By default the game takes focus on mouse (h)over. This can be disabled via the FocusOnMouseOver property of WpfGame

Mouse capture

By default the game captures the mouse. This allows capture of mouse events outside the game (e.g. user holds and drags the mouse outside the window, then releases it -> game will still receive mouse up event). The downside is that no overlayed controls (e.g. textboxes on top of the game) will ever receive focus.

Alternatively this can be toggled off via CaptureMouseWithin property of WpfMouse and then allows focus on overlayed controls. The downside is that mouse events outside the game window are no longer registered (e.g. user holds and drags the mouse outside the window, then releases it -> game will still think the mouse is down until the window receives focus again)

Instead the WPF interop always uses a rendertarget (internally used to display the renderoutput in WPF), thus in a WPF control the code needs to look like this instead:

// get and cache the wpf rendertarget (there is always a default rendertarget)
var wpfRenderTarget = (RenderTarget2D)GraphicsDevice.GetRenderTargets()[0].RenderTarget;
// Draw into custom rendertarget
GraphicsDevice.SetRenderTarget(_rendertarget);
GraphicsDevice.Clear(Color.Transparent);
_spriteBatch.Begin();
_spriteBatch.Draw(_texture, Vector2.Zero, Color.White);
_spriteBatch.End();
// instead of setting null, set it back to the wpf rendertarget
// this will ensure that the output will end up visible to the user
GraphicsDevice.SetRenderTarget(wpfRenderTarget);
// these draw calls will now render onto backbuffer
GraphicsDevice.Clear(Color.CornflowerBlue);
_spriteBatch.Begin();
_spriteBatch.Draw(this.rendertarget, Vector2.Zero, Color.White);
_spriteBatch.End();

The reason for this behaviour that the interop sample cannot use the backbuffer (null) and instead needs to use its own rendertarget to interop with WPF.

TabControls

It is perfectly possible to use the WpfGame controls inside TabControls.

By default, WPF fully unloads any tab that is deactivated (e.g. when switching to another tab) and fully reloads the tab when switching back.

The WpfGame does not unload in these cases. See the next few events for details.

WpfGame.Activated/Deactivated

When a WpfGame is hosted inside a tab and the tab is changed, the WpfGame instance is not unloaded. Instead, Deactivated is fired. Likewise when the tab is activated again, Activated is fired.

If the parent window loses focus, Deactivated is fired (but only for the active tab) and when the window receives focus, Activated is called again (only for the active tab).

Initialize/Dispose

Initialize is only called once (per instance, when the window is created) and Dispose is called for every game instance once the window closes.

This means, that initialize is called even for those instances that are in "disabled" tabs. However IsActive can be used to determine whether the current game is inside the active tab (see below).

WpfGame.IsActive

Update/Draw are still called for all inactive tabs and any inactive tab has the IsActive property on WpfGame set to false. Only the active tab has IsActive set to true (and only when the window is the currently active window).