DirectX を使用して特定のコントロール上にレンダリングを行うには、そのコントロールのウインドウハンドルを取得する必要があります。しかし、WPF
のコントロールは Windows Form のコントロールと違い、ウインドウハンドルというものがありません(WPF では単にコントロールを「描画」しているだけに過ぎません)。
しかし、WPF には「WindowsFormsHost」というコントロールがあり、これを使うことによって
Windows Form コントロールを使用することができます。
今回は下のようにポリゴンが回転されながら描画するサンプルを作成しますが、Managed DirectX
自体に関しては説明を入れると長くなってしまうので詳細は省きます。WPF と Managed DirectX の関連性だけ説明していきます。
まず、WPF のウインドウ(ここでは Window1.xaml)を開き、ツールバーから「WindowsFormHost」コントロールを配置します。右にあるスライダーはおまけです。
本当は XAML の方にもう少し手を加えなければいけないのですが、Managed DirectX
を使うためのコントロールを先に作成しなければいけないので後に回します。
プロジェクトに「Windows Forms」の「カスタムコントロール」を追加してください。名前は「GraphicsDeviceControl」としておきます。
事前に「Microsoft.DirectX」「Microsoft.DirectX.Direct3D」「Microsoft.DirectX.Direct3DX」の参照を忘れずに追加しておいてください。
サンプルコードを手短にするために、Managed DirectX に関係するプログラムはすべて GraphicsDeviceControl.cs
にまとめています。あまり汎用性がある書き方ではないので、各自応用して書き換えてください。
GraphicsDeviceControl の全コードは下のようにしました(デザイナ部分は除く
)。Managed DirectX を使ったことがある人なら、特に変わった事はしていないことがわかるかと思います。常にポリゴンが回転するように見せるために
Timer
コンポーネントを使用しています。
ほんとは「ApplicationSystem.Windows.Forms.Application.Idle」イベントを使用したかったのですが、WPF
ではこのイベントは使えないので Timer を変わりに使っています。WPF ではアイドルイベントってどうやって拾うのかな?
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.DirectX;
using Microsoft.DirectX.Direct3D;
namespace ManagedDirectXOnWPF
{
public partial class GraphicsDeviceControl : Control
{
private Device device = null;
private CustomVertex.PositionColored[] vertices = new CustomVertex.PositionColored[3];
public CustomVertex.PositionColored[] Vertices
{
get { return this.vertices; }
}
public GraphicsDeviceControl()
{
InitializeComponent();
}
protected override void OnCreateControl()
{
if (this.DesignMode == false)
{
try
{
PresentParameters pp = new PresentParameters();
pp.Windowed = true;
pp.SwapEffect = SwapEffect.Discard;
pp.BackBufferWidth = 300;
pp.BackBufferHeight = 300;
pp.EnableAutoDepthStencil = true;
pp.AutoDepthStencilFormat = DepthFormat.D16;
this.device = new Device(0, DeviceType.Hardware, this.Handle,
CreateFlags.SoftwareVertexProcessing, pp);
this.vertices[0] = new CustomVertex.PositionColored(
0.0f, -2.0f + (float)Math.Sqrt(3) * 3.0f, 0.0f,
Color.FromArgb(255, 0, 0).ToArgb());
this.vertices[1] = new CustomVertex.PositionColored(
3.0f, -2.0f, 0.0f,
Color.FromArgb(0, 255, 0).ToArgb());
this.vertices[2] = new CustomVertex.PositionColored(
-3.0f, -2.0f, 0.0f,
Color.FromArgb(0, 0, 255).ToArgb());
this.device.Transform.View = Matrix.LookAtRH(
new Vector3(0.0f, 0.0f, -10.0f),
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(0.0f, 1.0f, 0.0f));
this.device.Transform.Projection = Matrix.PerspectiveFovRH(
Geometry.DegreeToRadian(45.0f), 1.0f, 1.0f, 100.0f);
this.device.RenderState.Lighting = false;
this.device.RenderState.CullMode = Cull.None;
this.device.RenderState.AlphaBlendEnable = true;
this.device.RenderState.SourceBlend = Blend.SourceAlpha;
this.device.RenderState.DestinationBlend = Blend.InvSourceAlpha;
}
catch (Exception ex)
{
Trace.WriteLine(ex.ToString());
}
}
base.OnCreateControl();
}
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
if (disposing)
{
if (this.device != null)
{
this.device.Dispose();
}
}
base.Dispose(disposing);
}
protected override void OnPaint(PaintEventArgs pe)
{
this.Draw();
base.OnPaint(pe);
}
private void Draw()
{
if (this.device == null)
{
return;
}
this.device.BeginScene();
this.device.Clear(ClearFlags.ZBuffer | ClearFlags.Target, Color.DarkBlue, 1.0f, 0);
this.device.Transform.World = Matrix.RotationY((float)Environment.TickCount / 1000.0f);
this.device.VertexFormat = CustomVertex.PositionColored.Format;
this.device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, this.vertices);
this.device.EndScene();
this.device.Present();
}
private void timer_Tick(object sender, EventArgs e)
{
this.Draw();
}
}
}
コントロールを作成したら、XAML の方を見てみます。作成したコントロールを配置するのでルートタグに名前空間を追加します。ここでは「xw」と定義しています。
<Window x:Class="ManagedDirectXOnWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPFウインドウ上でManaged DirectXを使用してポリゴン描画"
Height="338" Width="422"
xmlns:my="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
xmlns:xw="clr-namespace:ManagedDirectXOnWPF">
</Window>
続いて配置した WindowsFormsHost コントロールのタグを展開し、下のように GraphicsDeviceControl を追加します。
<my:WindowsFormsHost Name="windowsFormsHostManagedDirectX" Width="300" Height="300"
HorizontalAlignment="Left" VerticalAlignment="Top">
<xw:GraphicsDeviceControl x:Name="GraphicsDeviceControl" />
</my:WindowsFormsHost>
「x:Name」はなくてもかまいませんが、サンプルではスライダーで頂点データにアクセスするために使用しています。
これを実行すれば、サンプルみたいにポリゴンが回転して描画されるシーンを WPF 上で実行することができます。スライダーによるアクセスはおまけなのでサンプルデータをダウンロードして確認してみてください。
以下、実行ファイルです。「.NET Framework 3.0」と「最新の DirectX ランタイム」が必要です。Windows Vista
の場合はそのまま実行できます。
サンプルプロジェクト一式です。「Visual Studio 2008 Professional Edition」で作成しています。