Win32 でのビジュアル レイヤーの使用

Win32 アプリで Windows ランタイム コンポジション API (ビジュアル レイヤーとも呼ばれる) を使用して、Windows ユーザーの利便性を高める最新のエクスペリエンスを作成できます。

このチュートリアルの完全なコードは、GitHub で入手できます: Win32 HelloComposition サンプル

UI コンポジションを正確に制御する必要があるユニバーサル Windows アプリケーションでは、Windows.UI.Composition 名前空間にアクセスして、UI の構成とレンダリングの方法をきめ細かく制御できます。 ただし、このコンポジション API は UWP アプリに限定されません。 Win32 デスクトップ アプリケーションでは、UWP と Windows で最新のコンポジション システムを利用できます。

前提条件

UWP ホスティング API には、これらの前提条件があります。

Win32 デスクトップ アプリケーションからコンポジション API を使用する方法

このチュートリアルでは、単純な Win32 C++ アプリを作成し、UWP コンポジション要素を追加します。 Windows Composition API を使用して、プロジェクトを正しく構成し、相互運用コードを作成して、シンプルなものを描画することに焦点を合わせています。 完成したアプリは次のようになります。

The running app UI

Visual Studio で C++ Win32 プロジェクトを作成する

最初の手順は、Visual Studio で Win32 アプリ プロジェクトを作成することです。

C++ で HelloComposition という名前の新しい Win32 アプリケーションプロジェクトを作成するには:

  1. Visual Studio を開き、[ファイル]>[新規作成]>[プロジェクト] の順に選択します。

    [新しいプロジェクト] ダイアログ ボックスが開きます。

  2. [インストール済み] カテゴリで、[Visual C++] ノードを展開し、[Windows デスクトップ] を選択します。

  3. [Windows デスクトップ アプリケーション] テンプレートを選択します。

  4. 名前「HelloComposition」を入力し、[OK] をクリックします。

    Visual Studio によってプロジェクトが作成され、メイン アプリ ファイルのエディターが開きます。

Windows ランタイム API を使用するようにプロジェクトを構成する

Win32 アプリで Windows ランタイム (WinRT) API を使用するには、C++/WinRT を使用します。 C++/WinRT サポートを追加するには、Visual Studio プロジェクトを構成する必要があります。

(詳細については、「C++/WinRT の使用を開始する」 - 「Windows デスクトップ アプリケーション プロジェクトを変更して C++/WinRT のサポートを追加する」を参照してください)。

  1. [プロジェクト] メニューで、プロジェクトのプロパティ (HelloComposition Properties) を開き、次の設定が指定した値に設定されていることを確認します。

    • [構成] では、[すべての構成] を選択します。 [プラットフォーム] では、[すべてのプラットフォーム] を選択します。
    • [構成プロパティ]>[全般]>[Windows SDK バージョン] = 10.0.17763.0 以上

    Set SDK version

    • [C/C++]>[言語]>[C++ 言語標準] = ISO C++ 17 Standard (/stf:c++17)

    Set language standard

    • [リンカー]>[入力]>[追加の依存ファイル] に "windowsapp.lib" を含める必要があります。 リストに含まれていない場合は、それを追加します。

    Add linker dependency

  2. プリコンパイル済みヘッダーを更新します

    • stdafx.hstdafx.cpp の名前をそれぞれ pch.hpch.cpp に変更します。

    • プロジェクトのプロパティ [C/C++]>[プリコンパイル済みヘッダー]>[プリコンパイル済みヘッダー ファイル]pch.h に設定します。

    • すべてのファイルで、#include "stdafx.h" を検索して #include "pch.h" に置換します。

      ([編集]>[検索と置換]>[フォルダーを指定して検索])

      Find and replace stdafx.h

    • pch.h に、winrt/base.hunknwn.h を含めます。

      // reference additional headers your program requires here
      #include <unknwn.h>
      #include <winrt/base.h>
      

この時点でプロジェクトをビルドして、続行する前にエラーが発生しないことを確認することをお勧めします。

コンポジション要素をホストするクラスを作成する

ビジュアル レイヤーによって作成したコンテンツをホストするには、クラス (CompositionHost) を作成して、相互運用を管理し、コンポジション要素を作成します。 ここでは、以下のような Composition API をホストするための構成の大半を行います。

スレッドの問題を回避するため、このクラスはシングルトンにします。 たとえば、スレッドごとに作成できるディスパッチャー キューは 1 つだけであるため、同じスレッドで 2 つ目の CompositionHost のインスタンスをインスタンス化すると、エラーが発生します。

ヒント

チュートリアルを進めながら、必要に応じて、チュートリアルの最後にある完全なコードを確認して、すべてのコードが適切な場所にあることを確認してください。

  1. プロジェクトに新しいクラス ファイルを追加します。

    • ソリューション エクスプローラーで、HelloComposition プロジェクトを右クリックします。
    • コンテキスト メニューで、[追加]>[クラス...] を選択します。
    • [クラスの追加] ダイアログ ボックスで、クラスに CompositionHost.cs という名前を付けて、[追加] をクリックします。
  2. コンポジション相互運用機能に必要なヘッダーと using を含めます。

    • CompositionHost.h で、ファイルの先頭に、これらの include を追加します。
    #pragma once
    #include <winrt/Windows.UI.Composition.Desktop.h>
    #include <windows.ui.composition.interop.h>
    #include <DispatcherQueue.h>
    
    • CompositionHost.cpp で、ファイルの先頭のすべての include の後に、using を追加します。
    using namespace winrt;
    using namespace Windows::System;
    using namespace Windows::UI;
    using namespace Windows::UI::Composition;
    using namespace Windows::UI::Composition::Desktop;
    using namespace Windows::Foundation::Numerics;
    
  3. シングルトン パターンを使用するようにクラスを編集します。

    • CompositionHost.h で、コンストラクターをプライベートにします。
    • パブリック静的 GetInstance メソッドを宣言します。
    class CompositionHost
    {
    public:
        ~CompositionHost();
        static CompositionHost* GetInstance();
    
    private:
        CompositionHost();
    };
    
    • CompositionHost.cpp で、GetInstance メソッドの定義を追加します。
    CompositionHost* CompositionHost::GetInstance()
    {
        static CompositionHost instance;
        return &instance;
    }
    
  4. CompositionHost.h で、Compositor、DispatcherQueueController、および DesktopWindowTarget のプライベート メンバー変数を宣言します。

    winrt::Windows::UI::Composition::Compositor m_compositor{ nullptr };
    winrt::Windows::System::DispatcherQueueController m_dispatcherQueueController{ nullptr };
    winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget m_target{ nullptr };
    
  5. コンポジション相互運用オブジェクトを初期化するパブリック メソッドを追加します。

    注意

    Initialize では、EnsureDispatcherQueueCreateDesktopWindowTarget、および CreateCompositionRoot メソッドを呼び出します。 これらのメソッドは、次の手順で作成します。

    • CompositionHost.h で、HWND を引数として受け取る Initialize という名前のパブリック メソッドを宣言します。
    void Initialize(HWND hwnd);
    
    • CompositionHost.cpp で、Initialize メソッドの定義を追加します。
    void CompositionHost::Initialize(HWND hwnd)
    {
        EnsureDispatcherQueue();
        if (m_dispatcherQueueController) m_compositor = Compositor();
    
        CreateDesktopWindowTarget(hwnd);
        CreateCompositionRoot();
    }
    
  6. Windows Composition を使用するスレッドで、ディスパッチャー キューを作成します。

    Compositor は、ディスパッチャー キューがあるスレッドで作成される必要があるため、このメソッドは初期化中に最初に呼び出されます。

    • CompositionHost.h で、EnsureDispatcherQueue という名前のプライベート メソッドを宣言します。
    void EnsureDispatcherQueue();
    
    • CompositionHost.cpp で、EnsureDispatcherQueue メソッドの定義を追加します。
    void CompositionHost::EnsureDispatcherQueue()
    {
        namespace abi = ABI::Windows::System;
    
        if (m_dispatcherQueueController == nullptr)
        {
            DispatcherQueueOptions options
            {
                sizeof(DispatcherQueueOptions), /* dwSize */
                DQTYPE_THREAD_CURRENT,          /* threadType */
                DQTAT_COM_ASTA                  /* apartmentType */
            };
    
            Windows::System::DispatcherQueueController controller{ nullptr };
            check_hresult(CreateDispatcherQueueController(options, reinterpret_cast<abi::IDispatcherQueueController**>(put_abi(controller))));
            m_dispatcherQueueController = controller;
        }
    }
    
  7. アプリのウィンドウをコンポジション ターゲットとして登録します。

    • CompositionHost.h で、引数として HWND を受け取る CreateDesktopWindowTarget という名前のプライベート メソッドを宣言します。
    void CreateDesktopWindowTarget(HWND window);
    
    • CompositionHost.cpp で、CreateDesktopWindowTarget メソッドの定義を追加します。
    void CompositionHost::CreateDesktopWindowTarget(HWND window)
    {
        namespace abi = ABI::Windows::UI::Composition::Desktop;
    
        auto interop = m_compositor.as<abi::ICompositorDesktopInterop>();
        DesktopWindowTarget target{ nullptr };
        check_hresult(interop->CreateDesktopWindowTarget(window, false, reinterpret_cast<abi::IDesktopWindowTarget**>(put_abi(target))));
        m_target = target;
    }
    
  8. ビジュアル オブジェクトを保持するルート ビジュアル コンテナーを作成します。

    • CompositionHost.h で、CreateCompositionRoot という名前のプライベート メソッドを宣言します。
    void CreateCompositionRoot();
    
    • CompositionHost.cpp で、CreateCompositionRoot メソッドの定義を追加します。
    void CompositionHost::CreateCompositionRoot()
    {
        auto root = m_compositor.CreateContainerVisual();
        root.RelativeSizeAdjustment({ 1.0f, 1.0f });
        root.Offset({ 124, 12, 0 });
        m_target.Root(root);
    }
    

ここでプロジェクトをビルドして、エラーが発生しないことを確認します。

これらのメソッドでは、UWP ビジュアル レイヤーと Win32 API 間の相互運用に必要なコンポーネントをセットアップします。 これで、アプリにコンテンツを追加できるようになりました。

コンポジション要素を追加する

インフラストラクチャが配置されたので、表示するコンポジション コンテンツを生成できるようになりました。

この例では、短い遅延の後にドロップさせるアニメーションを使用して、ランダムに色付けされた正方形 SpriteVisual を作成するコードを追加します。

  1. コンポジション要素を追加します。

    • CompositionHost.h で、3 つの float 値を引数として受け取る AddElement という名前のパブリック メソッドを宣言します。
    void AddElement(float size, float x, float y);
    
    • CompositionHost.cpp で、AddElement メソッドの定義を追加します。
    void CompositionHost::AddElement(float size, float x, float y)
    {
        if (m_target.Root())
        {
            auto visuals = m_target.Root().as<ContainerVisual>().Children();
            auto visual = m_compositor.CreateSpriteVisual();
    
            auto element = m_compositor.CreateSpriteVisual();
            uint8_t r = (double)(double)(rand() % 255);;
            uint8_t g = (double)(double)(rand() % 255);;
            uint8_t b = (double)(double)(rand() % 255);;
    
            element.Brush(m_compositor.CreateColorBrush({ 255, r, g, b }));
            element.Size({ size, size });
            element.Offset({ x, y, 0.0f, });
    
            auto animation = m_compositor.CreateVector3KeyFrameAnimation();
            auto bottom = (float)600 - element.Size().y;
            animation.InsertKeyFrame(1, { element.Offset().x, bottom, 0 });
    
            using timeSpan = std::chrono::duration<int, std::ratio<1, 1>>;
    
            std::chrono::seconds duration(2);
            std::chrono::seconds delay(3);
    
            animation.Duration(timeSpan(duration));
            animation.DelayTime(timeSpan(delay));
            element.StartAnimation(L"Offset", animation);
            visuals.InsertAtTop(element);
    
            visuals.InsertAtTop(visual);
        }
    }
    

ウィンドウを作成して表示する

これで、Win32 UI にボタンと UWP コンポジション コンテンツを追加できるようになりました。

  1. HelloComposition.cpp で、ファイルの先頭に CompositionHost.h をインクルードし、BTN_ADD を定義して、CompositionHost のインスタンスを取得します。

    #include "CompositionHost.h"
    
    // #define MAX_LOADSTRING 100 // This is already in the file.
    #define BTN_ADD 1000
    
    CompositionHost* compHost = CompositionHost::GetInstance();
    
  2. InitInstance メソッドで、作成されるウィンドウのサイズを変更します。 (この行では、CW_USEDEFAULT, 0900, 672 に変更します)。

    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, 900, 672, nullptr, nullptr, hInstance, nullptr);
    
  3. WndProc 関数で、message switch ブロックに case WM_CREATE を追加します。 この場合は、CompositionHost を初期化して、ボタンを作成します。

    case WM_CREATE:
    {
        compHost->Initialize(hWnd);
        srand(time(nullptr));
    
        CreateWindow(TEXT("button"), TEXT("Add element"),
            WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
            12, 12, 100, 50,
            hWnd, (HMENU)BTN_ADD, nullptr, nullptr);
    }
    break;
    
  4. また、WndProc 関数で、ボタン クリックを処理し、コンポジション要素を UI に追加します。

    WM_COMMAND ブロック内の wmId switch ブロックに case BTN_ADD を追加します。

    case BTN_ADD: // addButton click
    {
        double size = (double)(rand() % 150 + 50);
        double x = (double)(rand() % 600);
        double y = (double)(rand() % 200);
        compHost->AddElement(size, x, y);
        break;
    }
    

これで、アプリをビルドして実行できるようになりました。 必要に応じて、チュートリアルの最後にある完全なコードを確認して、すべてのコードが適切な場所にあることを確認してください。

アプリを実行して、ボタンをクリックすると、UI に追加されたアニメーションの正方形が表示されるはずです。

その他のリソース

完成したコード

CompositionHost クラスと InitInstance メソッドの完全なコードを次に示します。

CompositionHost.h

#pragma once
#include <winrt/Windows.UI.Composition.Desktop.h>
#include <windows.ui.composition.interop.h>
#include <DispatcherQueue.h>

class CompositionHost
{
public:
    ~CompositionHost();
    static CompositionHost* GetInstance();

    void Initialize(HWND hwnd);
    void AddElement(float size, float x, float y);

private:
    CompositionHost();

    void CreateDesktopWindowTarget(HWND window);
    void EnsureDispatcherQueue();
    void CreateCompositionRoot();

    winrt::Windows::UI::Composition::Compositor m_compositor{ nullptr };
    winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget m_target{ nullptr };
    winrt::Windows::System::DispatcherQueueController m_dispatcherQueueController{ nullptr };
};

CompositionHost.cpp

#include "pch.h"
#include "CompositionHost.h"

using namespace winrt;
using namespace Windows::System;
using namespace Windows::UI;
using namespace Windows::UI::Composition;
using namespace Windows::UI::Composition::Desktop;
using namespace Windows::Foundation::Numerics;

CompositionHost::CompositionHost()
{
}

CompositionHost* CompositionHost::GetInstance()
{
    static CompositionHost instance;
    return &instance;
}

CompositionHost::~CompositionHost()
{
}

void CompositionHost::Initialize(HWND hwnd)
{
    EnsureDispatcherQueue();
    if (m_dispatcherQueueController) m_compositor = Compositor();

    if (m_compositor)
    {
        CreateDesktopWindowTarget(hwnd);
        CreateCompositionRoot();
    }
}

void CompositionHost::EnsureDispatcherQueue()
{
    namespace abi = ABI::Windows::System;

    if (m_dispatcherQueueController == nullptr)
    {
        DispatcherQueueOptions options
        {
            sizeof(DispatcherQueueOptions), /* dwSize */
            DQTYPE_THREAD_CURRENT,          /* threadType */
            DQTAT_COM_ASTA                  /* apartmentType */
        };

        Windows::System::DispatcherQueueController controller{ nullptr };
        check_hresult(CreateDispatcherQueueController(options, reinterpret_cast<abi::IDispatcherQueueController**>(put_abi(controller))));
        m_dispatcherQueueController = controller;
    }
}

void CompositionHost::CreateDesktopWindowTarget(HWND window)
{
    namespace abi = ABI::Windows::UI::Composition::Desktop;

    auto interop = m_compositor.as<abi::ICompositorDesktopInterop>();
    DesktopWindowTarget target{ nullptr };
    check_hresult(interop->CreateDesktopWindowTarget(window, false, reinterpret_cast<abi::IDesktopWindowTarget**>(put_abi(target))));
    m_target = target;
}

void CompositionHost::CreateCompositionRoot()
{
    auto root = m_compositor.CreateContainerVisual();
    root.RelativeSizeAdjustment({ 1.0f, 1.0f });
    root.Offset({ 124, 12, 0 });
    m_target.Root(root);
}

void CompositionHost::AddElement(float size, float x, float y)
{
    if (m_target.Root())
    {
        auto visuals = m_target.Root().as<ContainerVisual>().Children();
        auto visual = m_compositor.CreateSpriteVisual();

        auto element = m_compositor.CreateSpriteVisual();
        uint8_t r = (double)(double)(rand() % 255);;
        uint8_t g = (double)(double)(rand() % 255);;
        uint8_t b = (double)(double)(rand() % 255);;

        element.Brush(m_compositor.CreateColorBrush({ 255, r, g, b }));
        element.Size({ size, size });
        element.Offset({ x, y, 0.0f, });

        auto animation = m_compositor.CreateVector3KeyFrameAnimation();
        auto bottom = (float)600 - element.Size().y;
        animation.InsertKeyFrame(1, { element.Offset().x, bottom, 0 });

        using timeSpan = std::chrono::duration<int, std::ratio<1, 1>>;

        std::chrono::seconds duration(2);
        std::chrono::seconds delay(3);

        animation.Duration(timeSpan(duration));
        animation.DelayTime(timeSpan(delay));
        element.StartAnimation(L"Offset", animation);
        visuals.InsertAtTop(element);

        visuals.InsertAtTop(visual);
    }
}

HelloComposition.cpp (一部)

#include "pch.h"
#include "HelloComposition.h"
#include "CompositionHost.h"

#define MAX_LOADSTRING 100
#define BTN_ADD 1000

CompositionHost* compHost = CompositionHost::GetInstance();

// Global Variables:

// ...
// ... code not shown ...
// ...

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, 900, 672, nullptr, nullptr, hInstance, nullptr);

// ...
// ... code not shown ...
// ...
}

// ...

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
// Add this...
    case WM_CREATE:
    {
        compHost->Initialize(hWnd);
        srand(time(nullptr));

        CreateWindow(TEXT("button"), TEXT("Add element"),
            WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
            12, 12, 100, 50,
            hWnd, (HMENU)BTN_ADD, nullptr, nullptr);
    }
    break;
// ...
    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
// Add this...
        case BTN_ADD: // addButton click
        {
            double size = (double)(rand() % 150 + 50);
            double x = (double)(rand() % 600);
            double y = (double)(rand() % 200);
            compHost->AddElement(size, x, y);
            break;
        }
// ...
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        // TODO: Add any drawing code that uses hdc here...
        EndPaint(hWnd, &ps);
    }
    break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// ...
// ... code not shown ...
// ...