「WPF上でManaged DirectXを使用する」や「WPF上でXNAを使用する」では WindowsFormsHost コントロールを使用してビューを表示させていましたが、今回は WPF ウインドウのみを使ってポリゴンを表示させてみたいと思います。Managed DirectX も XNA も同じようなコードになるので、今回は XNA Framework の方を使ってみたいと思います。
下が実行結果です。
今までと少し作りを変えているので、違う部分を説明していきます。
名前空間エイリアス
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 と同じスレッドで処理することができるようになっています。
private DispatcherTimer timer = null;
フィールドで DispatcherTimer を宣言しています。
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 が継承されていないので、かわりにここに記述しました。
protected override void OnClosed(EventArgs e)
{
if (this.device != null)
{
this.device.Dispose();
this.device = null;
}
base.OnClosed(e);
}
実行
サンプルプログラムを実行すると、記事の最初のほうに画面イメージで実行することができます。
一見うまくレンダリングされていますが、マウスでウインドウのサイズをドラッグで変更すると、サイズ変更中にビューがちらついたり非表示になったりします。どうも WPF のレンダリングと競合しているようで、いろいろ試してみたのですが解決できませんでした。
XNA や Managed DirectX を使う場合は WindowsFormsHost コントロールを使ったほうが無難なような気がします。
配布ファイル
以下、実行ファイルです。「.NET Framework 3.0」と「最新の DirectX ランタイム」「Microsoft XNA Framework Redistributable 2.0」が必要です。また、ハードウェア要件として「シェーダモデルバージョン 1.1 以上に対応したグラフィックカード」が必要です(たぶん)。
サンプルプロジェクト一式です。「Visual Studio 2008 Professional Edition」で作成しています。