Bagikan melalui


Menggunakan Visual Layer dengan Win32

Anda dapat menggunakan API Komposisi Runtime Windows (juga disebut lapisan Visual) di aplikasi Win32 Anda untuk menciptakan pengalaman modern yang menyala bagi pengguna Windows.

Kode lengkap untuk tutorial ini tersedia di GitHub: Sampel HelloComposition Win32.

Aplikasi Windows Universal yang membutuhkan kontrol yang tepat atas komposisi UI mereka memiliki akses ke namespace Windows.UI.Composition untuk memberikan kontrol terperinci atas bagaimana UI mereka disusam dan dirender. Namun, API komposisi ini tidak terbatas pada aplikasi UWP. Aplikasi desktop Win32 dapat memanfaatkan sistem komposisi modern di UWP dan Windows.

Prasyarat

API hosting UWP memiliki prasyarat ini.

Cara menggunakan API Komposisi dari aplikasi desktop Win32

Dalam tutorial ini, Anda membuat aplikasi Win32 C++ sederhana dan menambahkan elemen Komposisi UWP ke dalamnya. Fokusnya adalah mengonfigurasi proyek dengan benar, membuat kode interop, dan menggambar sesuatu yang sederhana menggunakan API Komposisi Windows. Aplikasi yang sudah selesai terlihat seperti ini.

UI aplikasi yang sedang berjalan

Membuat proyek C++ Win32 di Visual Studio

Langkah pertama adalah membuat proyek aplikasi Win32 di Visual Studio.

Untuk membuat proyek Aplikasi Win32 baru di C++ bernama HelloComposition:

  1. Buka Visual Studio dan pilih File>Proyek Baru.>

    Dialog Proyek Baru terbuka.

  2. Di bawah kategori Terinstal, perluas simpul Visual C++, lalu pilih Desktop Windows.

  3. Pilih templat Aplikasi Desktop Windows.

  4. Masukkan nama HelloComposition, lalu klik OK.

    Visual Studio membuat proyek dan membuka editor untuk file aplikasi utama.

Mengonfigurasi proyek untuk menggunakan WINDOWS Runtime API

Untuk menggunakan WINDOWS Runtime (WinRT) API di aplikasi Win32 Anda, kami menggunakan C++/WinRT. Anda perlu mengonfigurasi proyek Visual Studio untuk menambahkan dukungan C++/WinRT.

(Untuk detailnya, lihat Mulai menggunakan C++/WinRT - Ubah proyek aplikasi Windows Desktop untuk menambahkan dukungan C++/WinRT).

  1. Dari menu Proyek, buka properti proyek (Properti HelloComposition) dan pastikan pengaturan berikut diatur ke nilai yang ditentukan:

    • Untuk Konfigurasi, pilih Semua Konfigurasi. Untuk Platform, pilih Semua Platform.
    • Properti>Konfigurasi Umum>Windows SDK Versi = 10.0.17763.0 atau lebih tinggi

    Mengatur versi SDK

    • C/C++>Language>C++ Language Standard = ISO C++ 17 Standard (/stf:c++17)

    Mengatur standar bahasa

    • Dependensi Tambahan Input>Linker>harus menyertakan "windowsapp.lib". Jika tidak disertakan dalam daftar, tambahkan.

    Menambahkan dependensi linker

  2. Memperbarui header yang telah dikommpilasikan sebelumnya

    • Ganti nama stdafx.h dan stdafx.cpp menjadi pch.h dan pch.cpp, masing-masing.

    • Atur properti proyek C/C++>Precompiled Headers>Precompiled Header File ke pch.h.

    • Temukan dan ganti #include "stdafx.h" dengan #include "pch.h" di semua file.

      (Edit>Temukan dan Ganti>Temukan di File)

      Temukan dan ganti stdafx.h

    • Dalam pch.h, sertakan winrt/base.h dan unknwn.h.

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

Ada baiknya untuk membangun proyek pada saat ini untuk memastikan tidak ada kesalahan sebelum melanjutkan.

Membuat kelas untuk menghosting elemen komposisi

Untuk menghosting konten yang Anda buat dengan lapisan visual, buat kelas (CompositionHost) untuk mengelola interop dan membuat elemen komposisi. Di sinilah Anda melakukan sebagian besar konfigurasi untuk menghosting API Komposisi, termasuk:

Kami menjadikan kelas ini sebagai singleton untuk menghindari masalah utas. Misalnya, Anda hanya dapat membuat satu antrean dispatcher per utas, sehingga membuat instans kedua CompositionHost pada utas yang sama akan menyebabkan kesalahan.

Tip

Jika perlu, periksa kode lengkap di akhir tutorial untuk memastikan semua kode berada di tempat yang tepat saat Anda bekerja melalui tutorial.

  1. Tambahkan file kelas baru ke proyek Anda.

    • Di Penjelajah Solusi, klik kanan proyek HelloComposition.
    • Di menu konteks, pilih Tambahkan>Kelas....
    • Dalam dialog Tambahkan Kelas, beri nama kelas CompositionHost.cs, lalu klik Tambahkan.
  2. Sertakan header dan penggunaan yang diperlukan untuk interop komposisi.

    • Di CompositionHost.h, tambahkan ini termasuk di bagian atas file.
    #pragma once
    #include <winrt/Windows.UI.Composition.Desktop.h>
    #include <windows.ui.composition.interop.h>
    #include <DispatcherQueue.h>
    
    • Dalam CompositionHost.cpp, tambahkan penggunaan ini di bagian atas file, setelah termasuk.
    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. Edit kelas untuk menggunakan pola singleton.

    • Dalam CompositionHost.h, buat konstruktor privat.
    • Mendeklarasikan metode GetInstance statis publik.
    class CompositionHost
    {
    public:
        ~CompositionHost();
        static CompositionHost* GetInstance();
    
    private:
        CompositionHost();
    };
    
    • Dalam CompositionHost.cpp, tambahkan definisi metode GetInstance .
    CompositionHost* CompositionHost::GetInstance()
    {
        static CompositionHost instance;
        return &instance;
    }
    
  4. Dalam CompositionHost.h, deklarasikan variabel anggota privat untuk Compositor, DispatcherQueueController, dan 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. Tambahkan metode publik untuk menginisialisasi objek interop komposisi.

    Catatan

    Di Inisialisasi, Anda memanggil metode EnsureDispatcherQueue, CreateDesktopWindowTarget, dan CreateCompositionRoot . Anda membuat metode ini di langkah berikutnya.

    • Dalam CompositionHost.h, deklarasikan metode publik bernama Inisialisasi yang mengambil HWND sebagai argumen.
    void Initialize(HWND hwnd);
    
    • Di CompositionHost.cpp, tambahkan definisi metode Inisialisasi .
    void CompositionHost::Initialize(HWND hwnd)
    {
        EnsureDispatcherQueue();
        if (m_dispatcherQueueController) m_compositor = Compositor();
    
        CreateDesktopWindowTarget(hwnd);
        CreateCompositionRoot();
    }
    
  6. Buat antrean dispatcher pada utas yang akan menggunakan Komposisi Windows.

    Compositor harus dibuat pada utas yang memiliki antrean dispatcher, sehingga metode ini dipanggil terlebih dahulu selama inisialisasi.

    • Dalam CompositionHost.h, deklarasikan metode privat bernama EnsureDispatcherQueue.
    void EnsureDispatcherQueue();
    
    • Dalam CompositionHost.cpp, tambahkan definisi metode 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. Daftarkan jendela aplikasi Anda sebagai target komposisi.

    • Dalam CompositionHost.h, deklarasikan metode privat bernama CreateDesktopWindowTarget yang mengambil HWND sebagai argumen.
    void CreateDesktopWindowTarget(HWND window);
    
    • Dalam CompositionHost.cpp, tambahkan definisi metode 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. Buat kontainer visual akar untuk menyimpan objek visual.

    • Dalam CompositionHost.h, deklarasikan metode privat bernama CreateCompositionRoot.
    void CreateCompositionRoot();
    
    • Di CompositionHost.cpp, tambahkan definisi metode CreateCompositionRoot .
    void CompositionHost::CreateCompositionRoot()
    {
        auto root = m_compositor.CreateContainerVisual();
        root.RelativeSizeAdjustment({ 1.0f, 1.0f });
        root.Offset({ 124, 12, 0 });
        m_target.Root(root);
    }
    

Bangun proyek sekarang untuk memastikan tidak ada kesalahan.

Metode ini menyiapkan komponen yang diperlukan untuk interop antara lapisan visual UWP dan API Win32. Sekarang Anda dapat menambahkan konten ke aplikasi Anda.

Menambahkan elemen komposisi

Dengan infrastruktur di tempat, Anda sekarang dapat menghasilkan konten Komposisi yang ingin Anda tampilkan.

Untuk contoh ini, Anda menambahkan kode yang membuat SpriteVisual persegi berwarna acak dengan animasi yang menyebabkannya turun setelah penundaan singkat.

  1. Tambahkan elemen komposisi.

    • Dalam CompositionHost.h, deklarasikan metode publik bernama AddElement yang mengambil 3 nilai float sebagai argumen.
    void AddElement(float size, float x, float y);
    
    • Dalam CompositionHost.cpp, tambahkan definisi metode 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);
        }
    }
    

Membuat dan menampilkan jendela

Sekarang, Anda dapat menambahkan tombol dan konten komposisi UWP ke UI Win32 Anda.

  1. Dalam HelloComposition.cpp, di bagian atas file, sertakan CompositionHost.h, tentukan BTN_ADD, dan dapatkan instans CompositionHost.

    #include "CompositionHost.h"
    
    // #define MAX_LOADSTRING 100 // This is already in the file.
    #define BTN_ADD 1000
    
    CompositionHost* compHost = CompositionHost::GetInstance();
    
  2. InitInstance Dalam metode , ubah ukuran jendela yang dibuat. (Dalam baris ini, ubah CW_USEDEFAULT, 0 ke 900, 672.)

    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, 900, 672, nullptr, nullptr, hInstance, nullptr);
    
  3. Dalam fungsi WndProc, tambahkan case WM_CREATE ke blok sakelar pesan . Dalam hal ini, Anda menginisialisasi CompositionHost dan membuat tombol .

    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. Juga dalam fungsi WndProc, tangani klik tombol untuk menambahkan elemen komposisi ke UI.

    Tambahkan case BTN_ADD ke blok sakelar wmId di dalam blok WM_COMMAND.

    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;
    }
    

Sekarang Anda dapat membuat dan menjalankan aplikasi Anda. Jika perlu, periksa kode lengkap di akhir tutorial untuk memastikan semua kode berada di tempat yang tepat.

Saat Anda menjalankan aplikasi dan mengklik tombol , Anda akan melihat kotak animasi ditambahkan ke UI.

Sumber Daya Tambahan:

Kode lengkap

Berikut adalah kode lengkap untuk kelas CompositionHost dan metode 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 (Parsial)

#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 ...
// ...