Windows フォーム アプリで Windows ランタイム Composition API (Visual レイヤーとも呼ばれます) を使用して、Windows ユーザーに合った最新のエクスペリエンスを作成できます。
このチュートリアルの完全なコードは、GitHub: Windows フォーム HelloComposition サンプルで入手できます。
前提条件
UWP ホスティング API には、これらの前提条件があります。
- Windows フォームと UWP を使用したアプリ開発に関する知識があることを前提としています。 詳細については、参照してください。
- .NET Framework 4.7.2 以降
- Windows 10 バージョン 1803 以降
- Windows 10 SDK 17134 以降
Windows フォームで Composition API を使用する方法
このチュートリアルでは、単純なWindows フォーム UI を作成し、それにアニメーション化されたコンポジション要素を追加します。 Windows フォームコンポーネントとコンポジション コンポーネントはどちらも単純に保たれますが、表示される相互運用コードは、コンポーネントの複雑さに関係なく同じです。 完成したアプリは次のようになります。
Windows フォーム プロジェクトを作成する
最初の手順では、アプリケーション定義と UI のメイン フォームを含むWindows フォーム アプリ プロジェクトを作成します。
Visual C# で HelloComposition という名前の新しいWindows フォーム アプリケーション プロジェクトを作成するには>:
- Visual Studio を開き、[ファイル]>[新規作成]>[プロジェクト] の順に選択します。
[新しいプロジェクト] ダイアログ ボックスが開きます。 - Installed カテゴリで、Visual C# ノードを展開し、Windows Desktop を選択します。
- Windows フォーム App (.NET Framework) テンプレートを選択します。
- 名前をHelloCompositionと入力し、Framework .NET Framework 4.7.2 を選択して、OK をクリックします。
Visual Studioプロジェクトを作成し、Form1.csという名前の既定のアプリケーション ウィンドウのデザイナーを開きます。
Windows ランタイム API を使用するようにプロジェクトを構成する
Windows フォーム アプリで Windows ランタイム (WinRT) API を使用するには、Windows ランタイムにアクセスするようにVisual Studio プロジェクトを構成する必要があります。 さらに、ベクターは Composition API によって広く使用されるため、ベクターを使用するために必要な参照を追加する必要があります。
NuGet パッケージは、これらの両方のニーズに対応するために使用できます。 これらのパッケージの最新バージョンをインストールして、プロジェクトに必要な参照を追加します。
- Microsoft.Windows。Sdk。Contracts (既定のパッケージ管理形式が PackageReference に設定されている必要があります)。
- System.Numerics.Vectors
Note
NuGet パッケージを使用してプロジェクトを構成することをお勧めしますが、必要な参照を手動で追加できます。 詳細については、Windows用デスクトップ アプリケーションの強化をご覧ください。 次の表に、参照を追加する必要があるファイルを示します。
| ファイル | ロケーション |
|---|---|
| System.Runtime.WindowsRuntime | C:\Windows\Microsoft.NET\Framework\v4.0.30319 |
| Windows.Foundation.UniversalApiContract.winmd | C:\Program Files (x86)\Windows Kits\10\References<sdk version>\Windows.Foundation.UniversalApiContract<version> |
| Windows.Foundation.FoundationContract.winmd | C:\Program Files (x86)\Windows Kits\10\References<sdk version>\Windows\Foundation.FoundationContract<version> |
| System.Numerics.Vectors.dll | C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Numerics.Vectors\v4.0_4.0.0.0__b03f5f7f11d50a3a |
| System.Numerics.dll | C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.7.2 |
相互運用機能を管理するカスタム コントロールを作成する
ビジュアル レイヤーで作成したコンテンツをホストするには、Control から派生したカスタム コントロールを作成 します。 このコントロールを使用すると、ビジュアル レイヤー コンテンツのコンテナーを作成するために必要なウィンドウ ハンドルにアクセスできます。
ここでは、コンポジション API をホストするためのほとんどの構成を行います。 このコントロールでは、Platform Invocation Services (PInvoke) と COM Interop を使用して、コンポジション API をWindows フォーム アプリに取り込みます。 PInvoke と COM 相互運用の詳細については、「 アンマネージ コードとの相互運用」を参照してください。
ヒント
チュートリアルを進めながら、必要に応じて、チュートリアルの最後にある完全なコードを確認して、すべてのコードが適切な場所にあることを確認してください。
Control から派生した新しいカスタム コントロール ファイルをプロジェクトに追加します。
- ソリューション エクスプローラーで、HelloComposition プロジェクトを右クリックします。
- コンテキスト メニューの [ 追加>新しい項目...] を選択します。
- [ 新しい項目の追加 ] ダイアログで、[ カスタム コントロール] を選択します。
- コントロールに CompositionHost.cs名前を付け、[ 追加] をクリックします。 CompositionHost.cs がデザイン ビューで開きます。
CompositionHost.csのコード ビューに切り替え、次のコードをクラスに追加します。
// Add // using Windows.UI.Composition; IntPtr hwndHost; object dispatcherQueue; protected ContainerVisual containerVisual; protected Compositor compositor; private ICompositionTarget compositionTarget; public Visual Child { set { if (compositor == null) { InitComposition(hwndHost); } compositionTarget.Root = value; } }コンストラクターにコードを追加します。
コンストラクターでは、 InitializeCoreDispatcher メソッドと InitComposition メソッドを呼び出します。 これらのメソッドは、次の手順で作成します。
public CompositionHost() { InitializeComponent(); // Get the window handle. hwndHost = Handle; // Create dispatcher queue. dispatcherQueue = InitializeCoreDispatcher(); // Build Composition tree of content. InitComposition(hwndHost); }CoreDispatcher を使用してスレッドを初期化します。 コア ディスパッチャーは、ウィンドウ メッセージの処理と WinRT API のイベントのディスパッチを担当します。 Compositor の新しいインスタンスは、CoreDispatcher を持つスレッドに作成する必要があります。
- InitializeCoreDispatcher という名前のメソッドを作成し、ディスパッチャー キューを設定するコードを追加します。
// Add // using System.Runtime.InteropServices; private object InitializeCoreDispatcher() { DispatcherQueueOptions options = new DispatcherQueueOptions(); options.apartmentType = DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_STA; options.threadType = DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT; options.dwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions)); object queue = null; CreateDispatcherQueueController(options, out queue); return queue; }- ディスパッチャー キューには PInvoke 宣言が必要です。 この宣言は、クラスのコードの末尾に配置します。 (クラス コードを整理するために、このコードをリージョン内に配置します)。
#region PInvoke declarations //typedef enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE //{ // DQTAT_COM_NONE, // DQTAT_COM_ASTA, // DQTAT_COM_STA //}; internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE { DQTAT_COM_NONE = 0, DQTAT_COM_ASTA = 1, DQTAT_COM_STA = 2 }; //typedef enum DISPATCHERQUEUE_THREAD_TYPE //{ // DQTYPE_THREAD_DEDICATED, // DQTYPE_THREAD_CURRENT //}; internal enum DISPATCHERQUEUE_THREAD_TYPE { DQTYPE_THREAD_DEDICATED = 1, DQTYPE_THREAD_CURRENT = 2, }; //struct DispatcherQueueOptions //{ // DWORD dwSize; // DISPATCHERQUEUE_THREAD_TYPE threadType; // DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType; //}; [StructLayout(LayoutKind.Sequential)] internal struct DispatcherQueueOptions { public int dwSize; [MarshalAs(UnmanagedType.I4)] public DISPATCHERQUEUE_THREAD_TYPE threadType; [MarshalAs(UnmanagedType.I4)] public DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType; }; //HRESULT CreateDispatcherQueueController( // DispatcherQueueOptions options, // ABI::Windows::System::IDispatcherQueueController** dispatcherQueueController //); [DllImport("coremessaging.dll", EntryPoint = "CreateDispatcherQueueController", CharSet = CharSet.Unicode)] internal static extern IntPtr CreateDispatcherQueueController(DispatcherQueueOptions options, [MarshalAs(UnmanagedType.IUnknown)] out object dispatcherQueueController); #endregion PInvoke declarationsこれでディスパッチャー キューの準備が整い、コンポジション コンテンツの初期化と作成を開始できます。
コンポジターを初期化します。 コンポジターは、視覚レイヤー、エフェクトシステム、アニメーションシステムにまたがるWindows.UI.Composition名前空間内でさまざまな型を作成するファクトリです。 Compositor クラスは、ファクトリから作成されたオブジェクトの有効期間も管理します。
private void InitComposition(IntPtr hwndHost) { ICompositorDesktopInterop interop; compositor = new Compositor(); object iunknown = compositor as object; interop = (ICompositorDesktopInterop)iunknown; IntPtr raw; interop.CreateDesktopWindowTarget(hwndHost, true, out raw); object rawObject = Marshal.GetObjectForIUnknown(raw); compositionTarget = (ICompositionTarget)rawObject; if (raw == null) { throw new Exception("QI Failed"); } containerVisual = compositor.CreateContainerVisual(); Child = containerVisual; }- ICompositorDesktopInterop と ICompositionTarget には COM インポートが必要です。 このコードは CompositionHost クラスの後に配置しますが、名前空間宣言内に配置します。
#region COM Interop /* #undef INTERFACE #define INTERFACE ICompositorDesktopInterop DECLARE_INTERFACE_IID_(ICompositorDesktopInterop, IUnknown, "29E691FA-4567-4DCA-B319-D0F207EB6807") { IFACEMETHOD(CreateDesktopWindowTarget)( _In_ HWND hwndTarget, _In_ BOOL isTopmost, _COM_Outptr_ IDesktopWindowTarget * *result ) PURE; }; */ [ComImport] [Guid("29E691FA-4567-4DCA-B319-D0F207EB6807")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface ICompositorDesktopInterop { void CreateDesktopWindowTarget(IntPtr hwndTarget, bool isTopmost, out IntPtr test); } //[contract(Windows.Foundation.UniversalApiContract, 2.0)] //[exclusiveto(Windows.UI.Composition.CompositionTarget)] //[uuid(A1BEA8BA - D726 - 4663 - 8129 - 6B5E7927FFA6)] //interface ICompositionTarget : IInspectable //{ // [propget] HRESULT Root([out] [retval] Windows.UI.Composition.Visual** value); // [propput] HRESULT Root([in] Windows.UI.Composition.Visual* value); //} [ComImport] [Guid("A1BEA8BA-D726-4663-8129-6B5E7927FFA6")] [InterfaceType(ComInterfaceType.InterfaceIsIInspectable)] public interface ICompositionTarget { Windows.UI.Composition.Visual Root { get; set; } } #endregion COM Interop
コンポジション要素をホストするカスタム コントロールを作成する
コンポジション要素を生成および管理するコードを、CompositionHost から派生する別のコントロールに配置することをお勧めします。 これにより、CompositionHost クラスで作成した相互運用コードが再利用できます。
ここでは、CompositionHost から派生したカスタム コントロールを作成します。 このコントロールは、フォームに追加できるように、Visual Studio ツールボックスに追加されます。
CompositionHost から派生した新しいカスタム コントロール ファイルをプロジェクトに追加します。
- ソリューション エクスプローラーで、HelloComposition プロジェクトを右クリックします。
- コンテキスト メニューの [ 追加>新しい項目...] を選択します。
- [ 新しい項目の追加 ] ダイアログで、[ カスタム コントロール] を選択します。
- コントロールに CompositionHostControl.cs名前を付け、[ 追加] をクリックします。 CompositionHostControl.csがデザインビューで開きます。
デザイン ビューの [プロパティ] ウィンドウ CompositionHostControl.csで、 BackColor プロパティを ControlLight に設定します。
背景色の設定は省略可能です。 ここでは、フォームの背景に対してカスタム コントロールを表示できるようにします。
CompositionHostControl.csのコード ビューに切り替え、CompositionHost から派生するようにクラス宣言を更新します。
class CompositionHostControl : CompositionHostコンストラクターを更新して、基本コンストラクターを呼び出します。
public CompositionHostControl() : base() { }
コンポジション要素を追加する
インフラストラクチャが配置されたので、コンポジション コンテンツをアプリ UI に追加できるようになりました。
この例では、単純な SpriteVisual を作成してアニメーション化するコードを CompositionHostControl クラスに追加します。
コンポジション要素を追加します。
CompositionHostControl.csで、これらのメソッドを CompositionHostControl クラスに追加します。
// Add // using Windows.UI.Composition; public void AddElement(float size, float offsetX, float offsetY) { var visual = compositor.CreateSpriteVisual(); visual.Size = new Vector2(size, size); // Requires references visual.Brush = compositor.CreateColorBrush(GetRandomColor()); visual.Offset = new Vector3(offsetX, offsetY, 0); containerVisual.Children.InsertAtTop(visual); AnimateSquare(visual, 3); } private void AnimateSquare(SpriteVisual visual, int delay) { float offsetX = (float)(visual.Offset.X); Vector3KeyFrameAnimation animation = compositor.CreateVector3KeyFrameAnimation(); float bottom = Height - visual.Size.Y; animation.InsertKeyFrame(1f, new Vector3(offsetX, bottom, 0f)); animation.Duration = TimeSpan.FromSeconds(2); animation.DelayTime = TimeSpan.FromSeconds(delay); visual.StartAnimation("Offset", animation); } private Windows.UI.Color GetRandomColor() { Random random = new Random(); byte r = (byte)random.Next(0, 255); byte g = (byte)random.Next(0, 255); byte b = (byte)random.Next(0, 255); return Windows.UI.Color.FromArgb(255, r, g, b); }
フォームにコントロールを追加する
コンポジション コンテンツをホストするカスタム コントロールが用意されたので、アプリ UI に追加できます。 ここでは、前の手順で作成した CompositionHostControl のインスタンスを追加します。 CompositionHostControl は、Visual Studio ツールボックスの プロジェクト名 Components に自動的に追加されます。
デザイン ビュー Form1.CSで、ボタンを UI に追加します。
- ツールボックスから Form1 にボタンをドラッグします。 フォームの左上隅に配置します。 (コントロールの配置を確認するには、チュートリアルの開始時の画像を参照してください)。
- [プロパティ] ウィンドウで、 Text プロパティを button1 から [構成要素の追加] に変更します。
- すべてのテキストが表示されるようにボタンのサイズを変更します。
(詳細については、「
方法: Windows フォーム を参照してください。CompositionHostControl を UI に追加します。
- ツールボックスから Form1 に CompositionHostControl をドラッグします。 ボタンの右側に配置します。
- CompositionHost のサイズを変更して、フォームの残りの部分を埋めます。
ボタン クリック イベントを処理します。
- [プロパティ]ペインで、稲妻をクリックして[イベント]ビューに切り替えます。
- イベントの一覧で Click イベントを選択し、「 Button_Click」と入力して Enter キーを押します。
- このコードは、Form1.csに追加されます。
private void Button_Click(object sender, EventArgs e) { }ボタン クリック ハンドラーにコードを追加して、新しい要素を作成します。
- Form1.csで、前に作成した Button_Click イベント ハンドラーにコードを追加します。 このコードは CompositionHostControl1.AddElement を呼び出して、ランダムに生成されたサイズとオフセットを持つ新しい要素を作成します。 (CompositionHostControl のインスタンスは、フォームにドラッグしたときに compositionHostControl1 という名前が自動的に付けられました)。
// Add // using System; private void Button_Click(object sender, RoutedEventArgs e) { Random random = new Random(); float size = random.Next(50, 150); float offsetX = random.Next(0, (int)(compositionHostControl1.Width - size)); float offsetY = random.Next(0, (int)(compositionHostControl1.Height/2 - size)); compositionHostControl1.AddElement(size, offsetX, offsetY); }
これで、Windows フォーム アプリをビルドして実行できるようになりました。 ボタンをクリックすると、UI にアニメーション化された四角形が追加されます。
次のステップ
同じインフラストラクチャに基づくより完全な例については、GitHubの Windows フォーム ビジュアル レイヤー統合サンプルを参照してください。
その他のリソース
- Windows フォーム の使い始め (.NET)
- アンマネージ コードでの相互運用 (.NET)
- Windows アプリを始めよう (UWP)
- Windows 向けのデスクトップ アプリケーションの強化 (UWP)
- Windows.UI.Composition 名前空間 (UWP)
完成したコード
このチュートリアルの完全なコードを次に示します。
Form1.cs
using System;
using System.Windows.Forms;
namespace HelloComposition
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Button_Click(object sender, EventArgs e)
{
Random random = new Random();
float size = random.Next(50, 150);
float offsetX = random.Next(0, (int)(compositionHostControl1.Width - size));
float offsetY = random.Next(0, (int)(compositionHostControl1.Height/2 - size));
compositionHostControl1.AddElement(size, offsetX, offsetY);
}
}
}
CompositionHostControl.cs
using System;
using System.Numerics;
using Windows.UI.Composition;
namespace HelloComposition
{
class CompositionHostControl : CompositionHost
{
public CompositionHostControl() : base()
{
}
public void AddElement(float size, float offsetX, float offsetY)
{
var visual = compositor.CreateSpriteVisual();
visual.Size = new Vector2(size, size); // Requires references
visual.Brush = compositor.CreateColorBrush(GetRandomColor());
visual.Offset = new Vector3(offsetX, offsetY, 0);
containerVisual.Children.InsertAtTop(visual);
AnimateSquare(visual, 3);
}
private void AnimateSquare(SpriteVisual visual, int delay)
{
float offsetX = (float)(visual.Offset.X);
Vector3KeyFrameAnimation animation = compositor.CreateVector3KeyFrameAnimation();
float bottom = Height - visual.Size.Y;
animation.InsertKeyFrame(1f, new Vector3(offsetX, bottom, 0f));
animation.Duration = TimeSpan.FromSeconds(2);
animation.DelayTime = TimeSpan.FromSeconds(delay);
visual.StartAnimation("Offset", animation);
}
private Windows.UI.Color GetRandomColor()
{
Random random = new Random();
byte r = (byte)random.Next(0, 255);
byte g = (byte)random.Next(0, 255);
byte b = (byte)random.Next(0, 255);
return Windows.UI.Color.FromArgb(255, r, g, b);
}
}
}
CompositionHost.cs
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Windows.UI.Composition;
namespace HelloComposition
{
public partial class CompositionHost : Control
{
IntPtr hwndHost;
object dispatcherQueue;
protected ContainerVisual containerVisual;
protected Compositor compositor;
private ICompositionTarget compositionTarget;
public Visual Child
{
set
{
if (compositor == null)
{
InitComposition(hwndHost);
}
compositionTarget.Root = value;
}
}
public CompositionHost()
{
// Get the window handle.
hwndHost = Handle;
// Create dispatcher queue.
dispatcherQueue = InitializeCoreDispatcher();
// Build Composition tree of content.
InitComposition(hwndHost);
}
private object InitializeCoreDispatcher()
{
DispatcherQueueOptions options = new DispatcherQueueOptions();
options.apartmentType = DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_STA;
options.threadType = DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT;
options.dwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions));
object queue = null;
CreateDispatcherQueueController(options, out queue);
return queue;
}
private void InitComposition(IntPtr hwndHost)
{
ICompositorDesktopInterop interop;
compositor = new Compositor();
object iunknown = compositor as object;
interop = (ICompositorDesktopInterop)iunknown;
IntPtr raw;
interop.CreateDesktopWindowTarget(hwndHost, true, out raw);
object rawObject = Marshal.GetObjectForIUnknown(raw);
compositionTarget = (ICompositionTarget)rawObject;
if (raw == null) { throw new Exception("QI Failed"); }
containerVisual = compositor.CreateContainerVisual();
Child = containerVisual;
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
}
#region PInvoke declarations
//typedef enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE
//{
// DQTAT_COM_NONE,
// DQTAT_COM_ASTA,
// DQTAT_COM_STA
//};
internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE
{
DQTAT_COM_NONE = 0,
DQTAT_COM_ASTA = 1,
DQTAT_COM_STA = 2
};
//typedef enum DISPATCHERQUEUE_THREAD_TYPE
//{
// DQTYPE_THREAD_DEDICATED,
// DQTYPE_THREAD_CURRENT
//};
internal enum DISPATCHERQUEUE_THREAD_TYPE
{
DQTYPE_THREAD_DEDICATED = 1,
DQTYPE_THREAD_CURRENT = 2,
};
//struct DispatcherQueueOptions
//{
// DWORD dwSize;
// DISPATCHERQUEUE_THREAD_TYPE threadType;
// DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType;
//};
[StructLayout(LayoutKind.Sequential)]
internal struct DispatcherQueueOptions
{
public int dwSize;
[MarshalAs(UnmanagedType.I4)]
public DISPATCHERQUEUE_THREAD_TYPE threadType;
[MarshalAs(UnmanagedType.I4)]
public DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType;
};
//HRESULT CreateDispatcherQueueController(
// DispatcherQueueOptions options,
// ABI::Windows::System::IDispatcherQueueController** dispatcherQueueController
//);
[DllImport("coremessaging.dll", EntryPoint = "CreateDispatcherQueueController", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateDispatcherQueueController(DispatcherQueueOptions options,
[MarshalAs(UnmanagedType.IUnknown)]
out object dispatcherQueueController);
#endregion PInvoke declarations
}
#region COM Interop
/*
#undef INTERFACE
#define INTERFACE ICompositorDesktopInterop
DECLARE_INTERFACE_IID_(ICompositorDesktopInterop, IUnknown, "29E691FA-4567-4DCA-B319-D0F207EB6807")
{
IFACEMETHOD(CreateDesktopWindowTarget)(
_In_ HWND hwndTarget,
_In_ BOOL isTopmost,
_COM_Outptr_ IDesktopWindowTarget * *result
) PURE;
};
*/
[ComImport]
[Guid("29E691FA-4567-4DCA-B319-D0F207EB6807")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICompositorDesktopInterop
{
void CreateDesktopWindowTarget(IntPtr hwndTarget, bool isTopmost, out IntPtr test);
}
//[contract(Windows.Foundation.UniversalApiContract, 2.0)]
//[exclusiveto(Windows.UI.Composition.CompositionTarget)]
//[uuid(A1BEA8BA - D726 - 4663 - 8129 - 6B5E7927FFA6)]
//interface ICompositionTarget : IInspectable
//{
// [propget] HRESULT Root([out] [retval] Windows.UI.Composition.Visual** value);
// [propput] HRESULT Root([in] Windows.UI.Composition.Visual* value);
//}
[ComImport]
[Guid("A1BEA8BA-D726-4663-8129-6B5E7927FFA6")]
[InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
public interface ICompositionTarget
{
Windows.UI.Composition.Visual Root
{
get;
set;
}
}
#endregion COM Interop
}