Friends XNA and WPF
First, I will answer the question “Why make them friends”. The answer is simple - WPF is good for user interfaces, XNA for complex 3D graphics, and if you are creating a client application with a complex interface and 3D elements in it, then the XNA & WPF bundle is just for you.
I will illustrate the article with the example of a simple media player that I am writing now.
So, we have a cover scroller written in XNA. It looks like this:
And we also have a WPF project for the future media player, here is a screenshot of it: Our task is to put the first into the second. We will do the same as we did earlier with applications on Windows Forms: we will transfer the handle to the control of the class of the Game class to the control on top of which we will draw. But there is one small problem: only windows have handles in WPF. However, this problem is easily solved: we add the WindowsFormsHost control to the window and place the Panel in it from the System.Windows.Forms namespace. And here we will transfer the handle of this panel to the game constructor. So, proceed in order:
I will explain the last point:
Well, our “game” is drawn in the panel assigned to it, but there are several points on which this solution does not suit me:
We will solve these problems. To do this, I propose to stop using the game class and write a small device manager. We will have to manually initialize the device, redraw the scene by the timer, respond to the resizing of the control and its destruction. We transfer the control to the manager, on which we will draw, subscribe to the resize and draw control events, create a device and a timer by which we will trigger Update and Draw events. You can see the source code of the graphical manager here.or in the attached draft. We just have to deal with the panel. It turns out you just need to apply the appropriate styles and the panel will stop blinking. The only inconvenience is that the access modifier of the SetStyle method is protected. Well, nothing, we’ll inherit from the panel and apply the styles we need in the constructor. The source code of the optimized panel is simple to disgrace:
So, everything is ready for us, we start the project and see that everything works as it should: the panel does not blink, the dimensions change correctly, the application does not open additional windows.
Here you can download the initial project application with a bunch of WPF & XNA. Well, here's what happened to me:
I will illustrate the article with the example of a simple media player that I am writing now.
So, we have a cover scroller written in XNA. It looks like this:
And we also have a WPF project for the future media player, here is a screenshot of it: Our task is to put the first into the second. We will do the same as we did earlier with applications on Windows Forms: we will transfer the handle to the control of the class of the Game class to the control on top of which we will draw. But there is one small problem: only windows have handles in WPF. However, this problem is easily solved: we add the WindowsFormsHost control to the window and place the Panel in it from the System.Windows.Forms namespace. And here we will transfer the handle of this panel to the game constructor. So, proceed in order:
- Combine XNA and WPF projects in one solution
- We delete the Program.cs file from the XNA project and change the type from the Windows application to the class library in the project properties.
- Add Microsoft.Xna.Framework.Game to the WPF References project
- Add IntPtr handle parameter to the game constructor declaration
- In the WPF form constructor, create an instance of the game class and pass it the handle to the panel and call the game's Run () method in a separate thread
- During device initialization, we assign our handle
I will explain the last point:
IntPtr Handle;
public MainGame(IntPtr handle)
{
Handle = handle;
graphics = new GraphicsDeviceManager(this);
graphics.PreparingDeviceSettings += new EventHandler(PreparingDeviceSettings);
}
public void PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
{
e.GraphicsDeviceInformation.PresentationParameters.DeviceWindowHandle = Handle;
}
* This source code was highlighted with Source Code Highlighter.
Well, our “game” is drawn in the panel assigned to it, but there are several points on which this solution does not suit me:
- When the application starts, some left window is created in parallel. Of course, you can hide it, but still it's not good.
- When resizing the form, we get a dull stretching / compression of the picture. I would like to reinitialize the device to ensure the best picture quality.
- In my opinion, the most important thing: the game and the application live in different streams. This leads to errors when trying to work with WPF controls from XNA code.
- The panel blinks treacherously as its size changes.
We will solve these problems. To do this, I propose to stop using the game class and write a small device manager. We will have to manually initialize the device, redraw the scene by the timer, respond to the resizing of the control and its destruction. We transfer the control to the manager, on which we will draw, subscribe to the resize and draw control events, create a device and a timer by which we will trigger Update and Draw events. You can see the source code of the graphical manager here.or in the attached draft. We just have to deal with the panel. It turns out you just need to apply the appropriate styles and the panel will stop blinking. The only inconvenience is that the access modifier of the SetStyle method is protected. Well, nothing, we’ll inherit from the panel and apply the styles we need in the constructor. The source code of the optimized panel is simple to disgrace:
public class OptimizedPanel : Panel
{
public OptimizedPanel()
: base()
{
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.Opaque, true);
}
}
* This source code was highlighted with Source Code Highlighter.
So, everything is ready for us, we start the project and see that everything works as it should: the panel does not blink, the dimensions change correctly, the application does not open additional windows.
Here you can download the initial project application with a bunch of WPF & XNA. Well, here's what happened to me: