Using XNA in WPF Applications

Page update date :
Page creation date :

summary

Describes how to use the XNA Framework in WPF applications.

WPF アプリケーションで XNA を使用する

Operating environment

Prerequisites

Supported XNA Versions
  • 2.0
  • 3.0
Supported Platforms
  • Windows (XP SP2 or later, Vista)
Windows Required Vertex Shader Version 1.1
Windows Required Pixel Shader Version 1.1

Operating environment

platform

substance

To render on a particular control using DirectX, you need to get the window handle for that control. However, unlike Windows Form controls, WPF controls do not have window handles (in WPF they simply "draw" the control).

However, WPF provides a control called "WindowsFormsHost" that allows you to use Windows Forms controls.

In this article, we will create a sample of drawing a polygon as it is rotated as shown above, but I will omit the details of XNA itself because it would be too long to explain it. I'll just talk about the relationship between WPF and XNA.

First, open a WPF window (in this case, Window1.xaml) and place a WindowsFormHost control from the toolbar. The slider on the right is a bonus.

WPF ウインドウデザイン

I really need to do a little more work on the XAML, but I'll leave it for later because I have to create the controls to use XNA first.

Add a "Windows Forms" "Custom Control" to your project. Leave the name GraphicsDeviceControl.

Windows Forms のカスタムコントロールを追加

Don't forget to add the "Microsoft.Xna.Framework" reference beforehand. There is also "Microsoft.Xna.Framework.Game", but this is used in a game-only project, so there is no need to include it.

Managed DirectX の参照

To keep the sample code short, all XNA-related programs are summarized in GraphicsDeviceControl.cs. It is not a very versatile writing style, so please apply it and rewrite it.

The full code for the GraphicsDeviceControl is shown below (except for the designer part). If you've ever used XNA, you'll know that it doesn't do anything out of the ordinary. I'm using a Timer component to make the polygon appear to rotate all the time.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace XNAOnWPF
{
    /// <summary>
    /// グラフィックデバイスコントロール
    /// </summary>
    public partial class GraphicsDeviceControl : Control
    {
        /// <summary>
        /// グラフィックデバイス
        /// </summary>
        private GraphicsDevice device = null;

        /// <summary>
        /// エフェクト
        /// </summary>
        private BasicEffect effect = null;

        /// <summary>
        /// 頂点データ
        /// </summary>
        private VertexPositionColor[] vertices = new VertexPositionColor[3];
        /// <summary>
        /// 頂点データ
        /// </summary>
        public VertexPositionColor[] Vertices
        {
            get { return this.vertices; }
        }


        /// <summary>
        /// コンストラクタ
        /// </summary>
        public GraphicsDeviceControl()
        {
            InitializeComponent();
        }

        /// <summary>
        /// コントロールが作成されるとき
        /// </summary>
        protected override void OnCreateControl()
        {
            if (this.DesignMode == false)
            {
                try
                {
                    // デバイス作成
                    PresentationParameters pp = new PresentationParameters();
                    pp.SwapEffect = SwapEffect.Discard;
                    pp.BackBufferWidth = 300;
                    pp.BackBufferHeight = 300;
                    pp.EnableAutoDepthStencil = true;
                    pp.AutoDepthStencilFormat = DepthFormat.Depth16;
                    this.device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter,
                        DeviceType.Hardware, this.Handle, pp);

                    // 頂点データの設定
                    this.vertices[0] = new VertexPositionColor(
                        new Vector3(0.0f, -2.0f + (float)Math.Sqrt(3) * 3.0f, 0.0f),
                        new Microsoft.Xna.Framework.Graphics.Color(255, 0, 0));
                    this.vertices[1] = new VertexPositionColor(
                        new Vector3(3.0f, -2.0f, 0.0f),
                        new Microsoft.Xna.Framework.Graphics.Color(0, 255, 0));
                    this.vertices[2] = new VertexPositionColor(
                        new Vector3(-3.0f, -2.0f, 0.0f),
                        new Microsoft.Xna.Framework.Graphics.Color(0, 0, 255));

                    // 頂点定義
                    this.device.VertexDeclaration =
                        new VertexDeclaration(this.device, VertexPositionColor.VertexElements);

                    // エフェクト
                    this.effect = new BasicEffect(this.device, null);
                    this.effect.VertexColorEnabled = true;

                    // ビュー変換行列を設定
                    this.effect.View = Matrix.CreateLookAt(
                        new Vector3(0.0f, 0.0f, -10.0f),
                        new Vector3(0.0f, 0.0f, 0.0f),
                        Vector3.Up);

                    // 射影変換を設定
                    this.effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                        MathHelper.ToRadians(45.0f), 1.0f, 1.0f, 100.0f);

                    // レンダリングステート設定
                    this.device.RenderState.CullMode = CullMode.None;
                    this.device.RenderState.AlphaBlendEnable = true;
                    this.device.RenderState.SourceBlend = Blend.SourceAlpha;
                    this.device.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.ToString());
                }
            }

            base.OnCreateControl();
        }

        /// <summary>
        /// 使用中のリソースをすべてクリーンアップします。
        /// </summary>
        /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }

            if (disposing)
            {
                if (this.device != null)
                {
                    this.device.Dispose();
                }
            }
            base.Dispose(disposing);
        }

        /// <summary>
        /// 描画イベント
        /// </summary>
        /// <param name="pe"></param>
        protected override void OnPaint(PaintEventArgs pe)
        {
            this.Draw();

            base.OnPaint(pe);
        }

        /// <summary>
        /// 描画
        /// </summary>
        private void Draw()
        {
            if (this.device == null)
            {
                return;
            }

            this.device.Clear(Microsoft.Xna.Framework.Graphics.Color.DarkBlue);

            // ポリゴンを描画する
            this.effect.Begin();
            this.effect.Techniques[0].Passes[0].Begin();

            this.effect.World = Matrix.CreateRotationY((float)Environment.TickCount / 1000.0f);
            this.device.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList, vertices, 0, 1);

            this.effect.Techniques[0].Passes[0].End();
            this.effect.End();

            this.device.Present();
        }

        /// <summary>
        /// タイマーイベント
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer_Tick(object sender, EventArgs e)
        {
            this.Draw();
        }
    }
}

Now that you've created the control, let's look at the XAML. Since we are going to place the control that we created, we will add a namespace to the root tag. Here, it is defined as "xw".

<Window&nbsp;x:Class="ManagedDirectXOnWPF.Window1"
&nbsp;&nbsp;&nbsp;&nbsp;xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
&nbsp;&nbsp;&nbsp;&nbsp;xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
&nbsp;&nbsp;&nbsp;&nbsp;Title="WPFウインドウ上でManaged DirectXを使用してポリゴン描画"
&nbsp;&nbsp;&nbsp;&nbsp;Height="338"&nbsp;Width="422"
&nbsp;&nbsp;&nbsp;&nbsp;xmlns:my="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
&nbsp;&nbsp;&nbsp;&nbsp;xmlns:xw="clr-namespace:ManagedDirectXOnWPF">
    <!-- 省略 -->
</Window>

Next, expand the tag for the WindowsFormsHost control that you just placed, and add a GraphicsDeviceControl, as shown below.

<my:WindowsFormsHost&nbsp;Name="windowsFormsHostManagedDirectX"&nbsp;Width="300"&nbsp;Height="300"
                    HorizontalAlignment="Left"&nbsp;VerticalAlignment="Top">
  <xw:GraphicsDeviceControl&nbsp;x:Name="GraphicsDeviceControl"&nbsp;/>
</my:WindowsFormsHost>

You don't have to have an "x:Name", but the sample uses it to access vertex data with a slider.

If you do this, you can run a scene on WPF where polygons are drawn with a rotating polygon, as shown in the sample. Access by slider is a bonus, so please download the sample data and check it.

The program requires the .NET Framework 3.0, the latest DirectX runtime, and the Microsoft XNA Framework Redistributable 2.0. In addition, as a hardware requirement, "a graphics card that supports Pixel Shader 1.1 or higher" is required.

In addition, the project was created in "Visual Studio 2008 Professional Edition". Prepare an environment for Visual Studio 2008.