WPF ウインドウ上に直接 XNA のビューを表示する

Page creation date :

The page you are currently viewing does not support the selected display language.

概要

WPF ウインドウに直接 XNA でレンダリングする方法について説明しています。

WPF ウインドウ上に直接 XNA のビューを表示する

動作環境

必須環境

対応 XNA バージョン
  • 2.0
  • 3.0
対応プラットフォーム
  • Windows (XP SP2 以降, Vista)
Windows 必須頂点シェーダ バージョン 1.1
Windows 必須ピクセルシェーダ バージョン 1.1

動作確認環境

プラットフォーム

内容

WPF アプリケーションで XNA を使用する」では WindowsFormsHost コントロールを使用してビューを表示させていましたが、今回は WPF ウインドウのみを使ってポリゴンを表示させてみたいと思います。「WPF アプリケーションで XNA を使用する」と少し作りを変えているので、違う部分を説明していきます。

名前空間エイリアス

// 名前空間エイリアス
using Xna = Microsoft.Xna.Framework;
using XnaGraphics = Microsoft.Xna.Framework.Graphics;

Color や Matrix の構造体などが XNA Framework と WPF の名前空間でかぶってしまうため、構造体の名前を使うには名前空間から指定しないといけません。しかし、毎回つけると長ったらしいので名前空間のエイリアスを作成しています。たとえば「Microsoft.Xna.Framework.Rectangle」は「Xna.Rectangle」と指定できるようになります。

タイマー

.NET Framework 3.0 からは「System.Windows.Threading.DispatcherTimer」というタイマークラスが使えるようになっており、今回このタイマークラスを使って毎フレームレンダリングするようにしています。WPF では DispatcherTimer を使うことにより、UI と同じスレッドで処理することができるようになっています。

/// <summary>
/// タイマー
/// </summary>
private DispatcherTimer timer = null;

フィールドで DispatcherTimer を宣言しています。

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

DispatcherTimer が一定時間ごとに呼ぶメソッドを定義します。

// タイマー
this.timer = new DispatcherTimer();
this.timer.Tick += new EventHandler(dispatcherTimer_Tick);
this.timer.Interval = new TimeSpan(0, 0, 0, 0, 16);
this.timer.Start();

タイマーの設定処理です。DispatcherTimer のインスタンスを作成し、コールするメソッドと時間間隔を設定したら、DispatcherTimer.Start メソッドでタイマーが開始します。

ウインドウハンドルの取得

Direct3D デバイスを作成するにはウインドウハンドルが必要です。しかし WPF ではウインドウハンドルという概念は基本的にないため、Window クラスから直接取得する方法がありません。

ただ、今回のようにウインドウハンドルが必要な場合もあるため、Win32 コードの相互運用として「System.Windows.Interop.WindowInteropHelper」クラスを経由してウインドウハンドルを取得できるようになっています。(this は Window クラスです)

// ウィンドウハンドルを取得する
IntPtr handle = new WindowInteropHelper(this).Handle;

Direct3D デバイスを作成するときはこのハンドルを使用します。ただ、OnInitialized メソッドでウインドウハンドルを取得しようとすると、ウインドウがまだ作成されていないためか 0 が返ってきます。今回のサンプルではデバイスの作成を OnSourceInitialized メソッドで行っています。

指定領域にレンダリング

GraphicsDevice.Present の第2引数に描画先の領域を指定することができます。サンプルではオフセットとして(50, 50)の位置に描画するようにしています。サイズはバックバッファのサイズと同じ(300, 300)です。第1引数は描画元の領域で、null を指定すると元のバッファがそのまま使われます。

// 描画先を指定する
Xna.Rectangle rect = new Xna.Rectangle(
    50, 50,
    (int)this.viewSize.Width, (int)this.viewSize.Height);

this.device.Present(null, rect, this.windowHandle);

デバイスの破棄

ウインドウを閉じたときにデバイスを破棄しています。本来 Dispose メソッドなどで処理したかったのですが、WPF の Window クラスはデフォルトでは IDisposable が継承されていないので、かわりにここに記述しました。

/// <summary>
/// ウインドウが閉じたとき
/// </summary>
/// <param name="e"></param>
protected override void OnClosed(EventArgs e)
{
    if (this.device != null)
    {
        this.device.Dispose();
        this.device = null;
    }

    base.OnClosed(e);
}

実行

サンプルプログラムを実行すると、記事の最初にあるような画面イメージで実行することができます。

一見うまくレンダリングされていますが、マウスでウインドウのサイズをドラッグで変更すると、サイズ変更中にビューがちらついたり非表示になったりします。どうも WPF のレンダリングと競合しているようで、いろいろ試してみたのですが解決できませんでした。

XNA を使う場合は WindowsFormsHost コントロールを使ったほうが無難なような気がします。

プログラムを実行させるには「.NET Framework 3.0」と「最新の DirectX ランタイム」「Microsoft XNA Framework Redistributable 2.0」が必要です。また、ハードウェア要件として「ピクセルシェーダ 1.1 以上に対応したグラフィックカード」が必要です。

また、プロジェクトは「Visual Studio 2008 Professional Edition」で作成しています。Visual Studio 2008 の環境を用意してください。