Dela via


Använda det visuella lagret med Win32

Du kan använda API:er för Windows Runtime-komposition (kallas även visuellt lager) i dina Win32-appar för att skapa moderna upplevelser som lyser upp för Windows-användare.

Den fullständiga koden för den här handledningen är tillgänglig på GitHub: Win32 HelloComposition-exempel.

Universella Windows-program som behöver exakt kontroll över användargränssnittets sammansättning har åtkomst till namnområdet Windows.UI.Composition för att utöva detaljerad kontroll över hur användargränssnittet består och återges. Det här API:et för sammansättning är dock inte begränsat till UWP-appar. Win32-skrivbordsprogram kan dra nytta av moderna kompositionssystem i UWP och Windows.

Förutsättningar

UWP-värd-API:et har dessa förutsättningar.

Så här använder du API:er för sammansättning från ett Win32-skrivbordsprogram

I den här självstudien skapar du en enkel Win32 C++-app och lägger till UWP-sammansättningselement i den. Fokus ligger på att korrekt konfigurera projektet, skapa interop-koden och rita något enkelt med hjälp av API:er för Windows-sammansättning. Den färdiga appen ser ut så här.

Användargränssnittet för den körande appen

Skapa ett C++ Win32-projekt i Visual Studio

Det första steget är att skapa Win32-appprojektet i Visual Studio.

Så här skapar du ett nytt Win32-programprojekt i C++ med namnet HelloComposition:

  1. Öppna Visual Studio och välj File>New>Project.

    Dialogrutan Nytt projekt öppnas.

  2. Under kategorin Installerad expanderar du noden Visual C++ och väljer sedan Windows Desktop.

  3. Välj mallen Windows Desktop-program .

  4. Ange namnet HelloComposition och klicka sedan på OK.

    Visual Studio skapar projektet och öppnar redigeraren för huvudappfilen.

Konfigurera projektet för att använda Windows Runtime-API:er

Om du vill använda Api:er för Windows Runtime (WinRT) i din Win32-app använder vi C++/WinRT. Du måste konfigurera Visual Studio-projektet för att lägga till C++/WinRT-stöd.

(Mer information finns i Komma igång med C++/WinRT – Ändra ett Windows Desktop-programprojekt för att lägga till stöd för C++/WinRT).

  1. Öppna projektegenskaperna (HelloComposition-egenskaper) på projektmenyn och se till att följande inställningar är inställda på de angivna värdena:

    • För Konfiguration väljer du Alla konfigurationer. För Plattformväljer du Alla plattformar.
    • Konfigurationsegenskaper>Allmänt>Windows SDK-version = 10.0.17763.0 eller senare

    Ange SDK-version

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

    Ange språkstandard

    • Linker>Inmatning>Ytterligare beroenden bör inkludera "windowsapp.lib". Om den inte ingår i listan lägger du till den.

    Lägga till länkberoendeobjekt

  2. Uppdatera den förkompilerade headern

    • stdafx.h Byt namn på respektive stdafx.cpp till pch.h och pch.cpp.

    • Ställ in projektegenskapen C/C++>Förkompilerade rubriker>Förkompilerad huvudfilen till pch.h.

    • Hitta och ersätt #include "stdafx.h" med #include "pch.h" i alla filer.

      (Redigera>Sök och ersätt>Sök i filer)

      Hitta och ersätt stdafx.h

    • I pch.h, inkludera winrt/base.h och unknwn.h.

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

Det är en bra idé att bygga projektet just nu för att se till att det inte finns några fel innan du fortsätter.

Skapa en klass som värd för sammansättningselement

Om du vill vara värd för innehåll som du skapar med det visuella lagret skapar du en klass (CompositionHost) för att hantera interop och skapa kompositionselement. Det är här du gör det mesta av konfigurationen för att hosta kompositions-API:er, inklusive:

Vi gör denna klass till en singleton för att undvika problem med trådning. Du kan till exempel bara skapa en dispatcher-kö per tråd, så att instansiera en andra instans av CompositionHost i samma tråd skulle orsaka ett fel.

Tips/Råd

Om du behöver det kontrollerar du den fullständiga koden i slutet av självstudien för att se till att all kod finns på rätt platser när du går igenom självstudien.

  1. Lägg till en ny klassfil i projektet.

    • Högerklicka på HelloComposition-projektet i Solution Explorer.
    • I snabbmenyn väljer du Lägg till>klass....
    • I dialogrutan Lägg till klass namnger du klassen CompositionHost.cs och klickar sedan på Lägg till.
  2. Inkludera rubriker och rader krävs för sammansättningsinterop.

    • I CompositionHost.h lägger du till dessa innehåller överst i filen.
    #pragma once
    #include <winrt/Windows.UI.Composition.Desktop.h>
    #include <windows.ui.composition.interop.h>
    #include <DispatcherQueue.h>
    
    • I CompositionHost.cpp lägger du till dessa användningar överst i filen, efter eventuella inkluderingar.
    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. Redigera klassen för att använda singleton-mönstret.

    • I CompositionHost.h gör du konstruktorn privat.
    • Deklarera en offentlig statisk metod GetInstance .
    class CompositionHost
    {
    public:
        ~CompositionHost();
        static CompositionHost* GetInstance();
    
    private:
        CompositionHost();
    };
    
    • I CompositionHost.cpp lägger du till definitionen av metoden GetInstance .
    CompositionHost* CompositionHost::GetInstance()
    {
        static CompositionHost instance;
        return &instance;
    }
    
  4. I CompositionHost.h deklarerar du privata medlemsvariabler för Compositor, DispatcherQueueController och 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. Lägg till en offentlig metod för att initiera sammansättningsinterop-objekt.

    Anmärkning

    I Initiera anropar du metoderna EnsureDispatcherQueue, CreateDesktopWindowTarget och CreateCompositionRoot . Du skapar dessa metoder i nästa steg.

    • I CompositionHost.h deklarerar du en offentlig metod med namnet Initialize som tar en HWND som argument.
    void Initialize(HWND hwnd);
    
    • Lägg till definitionen av metoden Initialize i CompositionHost.cpp.
    void CompositionHost::Initialize(HWND hwnd)
    {
        EnsureDispatcherQueue();
        if (m_dispatcherQueueController) m_compositor = Compositor();
    
        CreateDesktopWindowTarget(hwnd);
        CreateCompositionRoot();
    }
    
  6. Skapa en dispatcher-kö i tråden som ska använda Windows-komposition.

    En Compositor måste skapas i en tråd som har en dispatcher-kö, så denna metod anropas först under initialiseringen.

    • Deklarera en privat metod med namnet EnsureDispatcherQueue i CompositionHost.h.
    void EnsureDispatcherQueue();
    
    • I CompositionHost.cpp lägger du till definitionen av metoden 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. Registrera appens fönster som ett kompositionsmål.

    • I CompositionHost.h deklarerar du en privat metod med namnet CreateDesktopWindowTarget som tar en HWND som argument.
    void CreateDesktopWindowTarget(HWND window);
    
    • I CompositionHost.cpp lägger du till definitionen av metoden 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. Skapa en rotcontainer för visuella objekt.

    • Deklarera en privat metod med namnet CreateCompositionRoot i CompositionHost.h.
    void CreateCompositionRoot();
    
    • I CompositionHost.cpp lägger du till definitionen av metoden CreateCompositionRoot .
    void CompositionHost::CreateCompositionRoot()
    {
        auto root = m_compositor.CreateContainerVisual();
        root.RelativeSizeAdjustment({ 1.0f, 1.0f });
        root.Offset({ 124, 12, 0 });
        m_target.Root(root);
    }
    

Skapa projektet nu för att se till att det inte finns några fel.

Dessa metoder konfigurerar de komponenter som behövs för interop mellan det visuella UWP-lagret och Win32-API:erna. Nu kan du lägga till innehåll i din app.

Lägga till sammansättningselement

Med infrastrukturen på plats kan du nu generera det sammansättningsinnehåll som du vill visa.

I det här exemplet lägger du till kod som skapar en slumpmässigt färgad fyrkant SpriteVisual med en animering som gör att den släpps efter en kort fördröjning.

  1. Lägg till ett kompositionselement.

    • I CompositionHost.h deklarerar du en offentlig metod med namnet AddElement som tar 3 flyttalsvärden som argument.
    void AddElement(float size, float x, float y);
    
    • I CompositionHost.cpp lägger du till definitionen av metoden 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);
        }
    }
    

Skapa och visa fönstret

Nu kan du lägga till en knapp och UWP-kompositionsinnehållet i ditt Win32-användargränssnitt.

  1. I HelloComposition.cpp överst i filen tar du med CompositionHost.h, definierar BTN_ADD och hämtar en instans av CompositionHost.

    #include "CompositionHost.h"
    
    // #define MAX_LOADSTRING 100 // This is already in the file.
    #define BTN_ADD 1000
    
    CompositionHost* compHost = CompositionHost::GetInstance();
    
  2. InitInstance I -metoden ändrar du storleken på fönstret som skapas. (På den här raden ändrar du CW_USEDEFAULT, 0 till 900, 672.)

    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, 900, 672, nullptr, nullptr, hInstance, nullptr);
    
  3. I WndProc-funktionen lägger du till case WM_CREATE i meddelande växelblock. I det här fallet initierar du CompositionHost och skapar knappen.

    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. I WndProc-funktionen hanterar du även knappklicket för att lägga till ett kompositionselement i användargränssnittet.

    Lägg till case BTN_ADD i wmId växelblock i WM_COMMAND-blocket.

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

Nu kan du skapa och köra din app. Vid behov kontrollerar du den fullständiga koden i slutet av handledningen för att se till att all kod finns på rätt ställen.

När du kör appen och klickar på knappen bör du se animerade rutor som lagts till i användargränssnittet.

Ytterligare resurser

Fullständig kod

Här är den fullständiga koden för klassen CompositionHost och metoden 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 (delvis)

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