Condividi tramite


Uso del livello visivo con Win32

Puoi usare le API di composizione di Windows Runtime (note anche come API livello visivo) nelle app Win32 per creare esperienze moderne disponibili per gli utenti di Windows.

Il codice completo per questa esercitazione è disponibile in GitHub: campione Win32HelloComposition.

Le applicazioni di Windows universale che devono esercitare un controllo accurato sulla composizione dell'interfaccia utente hanno accesso allo spazio dei nomi Windows.UI.Composition per esercitare un controllo granulare sulla composizione e il rendering dell'interfaccia utente. Questa API di composizione non è tuttavia limitata alle app UWP. Le applicazioni desktop Win32 possono sfruttare i moderni sistemi di composizione disponibili su piattaforma UWP e in Windows.

Prerequisiti

L'API di hosting UWP presenta questi prerequisiti.

Come usare le API di composizione da un'applicazione desktop Win32

In questa esercitazione creerai una semplice app Win32 C++ e aggiungerai elementi di composizione UWP. Questo argomento è incentrato su come configurare correttamente il progetto, creare il codice di interoperabilità ed eseguire una semplice progettazione tramite le API di composizione di Windows. L'app completata ha un aspetto simile al seguente.

Interfaccia utente dell'app in esecuzione

Creare un progetto Win32 C++ in Visual Studio

Il primo passaggio consiste nel creare il progetto di un'app Win32 in Visual Studio.

Per creare un nuovo progetto di un'applicazione Win32 in C++ denominato HelloComposition:

  1. Apri Visual Studio e seleziona File>Nuovo>Progetto.

    Verrà visualizzata la finestra di dialogo Nuovo progetto.

  2. Nella categoria Installati espandi il nodo Visual C++ e quindi seleziona Windows Desktop.

  3. Seleziona il modello Applicazione desktop di Windows.

  4. Immetti il nome HelloComposition e quindi fai clic su OK.

    Visual Studio creerà il progetto e aprirà l'editor per il file dell'app principale.

Configurare il progetto per l'uso delle API di Windows Runtime

Per usare le API di Windows Runtime (WinRT) in un'app Win32, viene usato C++/WinRT. Devi configurare il progetto di Visual Studio in modo da aggiungere il supporto di C++/WinRT.

Per informazioni dettagliate, vedi Introduzione a C++/WinRT - Modificare un progetto di applicazione desktop di Windows per aggiungere il supporto di C++/WinRT.

  1. Dal menu Progetto apri le proprietà del progetto (Proprietà di HelloComposition) e assicurati che per le impostazioni seguenti siano definiti i valori specificati:

    • Per Configurazione seleziona Tutte le configurazioni. Per Piattaforma seleziona Tutte le piattaforme.
    • Proprietà di configurazione>Generale>Versione di Windows SDK = 10.0.17763.0 o versioni successive.

    Impostare la versione dell'SDK

    • C/C++>Linguaggio>Standard del linguaggio C++ = Standard C++ 17 ISO (/stf:c++17)

    Impostare lo standard del linguaggio

    • Linker>Input>Dipendenze aggiuntive deve includere "windowsapp.lib". Se questa dipendenza non è inclusa nell'elenco, aggiungila.

    Aggiungere la dipendenza linker

  2. Aggiorna l'intestazione precompilata

    • Cambia il nome di stdafx.h e stdafx.cpp rispettivamente in pch.h e pch.cpp.

    • Imposta la proprietà del progetto C/C++>Intestazioni precompilate>File di intestazione precompilata su pch.h.

    • Trova e sostituisci #include "stdafx.h" con #include "pch.h" in tutti i file.

      Modifica>Trova e sostituisci>Cerca nei file

      Trova e sostituisci stdafx.h

    • In pch.h includi winrt/base.h e unknwn.h.

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

A questo punto è consigliabile compilare il progetto per assicurarsi che non vi siano errori prima di procedere.

Creare una classe per ospitare elementi di composizione

Per ospitare il contenuto creato con il livello visivo, crea una classe (CompositionHost) per gestire l'interoperabilità e creare elementi di composizione. In questa classe viene eseguita la maggior parte della configurazione per l'hosting delle API di composizione, tra cui:

Questa classe è un singleton per evitare problemi relativi al threading. Puoi ad esempio creare una sola coda del dispatcher per thread. La creazione di una seconda istanza di CompositionHost nello stesso thread causerebbe pertanto un errore.

Suggerimento

Se necessario, controlla il codice completo alla fine dell'esercitazione per assicurarti che tutto il codice sia nelle posizioni corrette man mano che procedi con l'esercitazione.

  1. Aggiungere un nuovo file di classe al progetto.

    • In Esplora soluzioni fai clic con il pulsante destro del mouse sul progetto HelloComposition.
    • Scegli Aggiungi>Classe... dal menu di scelta rapida.
    • Nella finestra di dialogo Aggiungi classe assegna il nome CompositionHost.cs alla classe e quindi fai clic su Aggiungi.
  2. Includi intestazioni e istruzioni using necessarie per l'interoperabilità di composizione.

    • In CompositionHost.h aggiungi queste direttive include all'inizio del file.
    #pragma once
    #include <winrt/Windows.UI.Composition.Desktop.h>
    #include <windows.ui.composition.interop.h>
    #include <DispatcherQueue.h>
    
    • In CompositionHost.cpp aggiungi queste istruzioni using all'inizio del file, dopo le direttive include.
    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. Modifica la classe in modo che usi il modello singleton.

    • In CompositionHost.h rendi privato il costruttore.
    • Dichiara un metodo GetInstance statico pubblico.
    class CompositionHost
    {
    public:
        ~CompositionHost();
        static CompositionHost* GetInstance();
    
    private:
        CompositionHost();
    };
    
    • In CompositionHost.cpp aggiungi la definizione del metodo GetInstance.
    CompositionHost* CompositionHost::GetInstance()
    {
        static CompositionHost instance;
        return &instance;
    }
    
  4. In CompositionHost.h dichiara le variabili membro private per Compositor, DispatcherQueueController e 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. Aggiungi un metodo pubblico per inizializzare gli oggetti di interoperabilità di composizione.

    Nota

    In Initialize chiama i metodi EnsureDispatcherQueue, CreateDesktopWindowTarget e CreateCompositionRoot. Questi metodi vengono creati nei passaggi successivi.

    • In CompositionHost.h dichiara un metodo pubblico denominato Initialize che accetta come argomento un HWND.
    void Initialize(HWND hwnd);
    
    • In CompositionHost.cpp aggiungi la definizione del metodo Initialize.
    void CompositionHost::Initialize(HWND hwnd)
    {
        EnsureDispatcherQueue();
        if (m_dispatcherQueueController) m_compositor = Compositor();
    
        CreateDesktopWindowTarget(hwnd);
        CreateCompositionRoot();
    }
    
  6. Crea una coda del dispatcher sul thread che userà Windows Composition.

    È necessario creare un elemento Compositor in un thread che dispone di una coda del dispatcher, in modo che questo metodo venga chiamato per primo durante l'inizializzazione.

    • In CompositionHost.h dichiara un metodo privato denominato EnsureDispatcherQueue.
    void EnsureDispatcherQueue();
    
    • In CompositionHost.cpp aggiungi la definizione del metodo 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. Registra la finestra dell'app come destinazione della composizione.

    • In CompositionHost.h dichiara un metodo privato denominato CreateDesktopWindowTarget che accetta come argomento un HWND.
    void CreateDesktopWindowTarget(HWND window);
    
    • In CompositionHost.cpp aggiungi la definizione del metodo 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. Crea un contenitore radice di oggetti visivi.

    • In CompositionHost.h dichiara un metodo privato denominato CreateCompositionRoot.
    void CreateCompositionRoot();
    
    • In CompositionHost.cpp aggiungi la definizione del metodo CreateCompositionRoot.
    void CompositionHost::CreateCompositionRoot()
    {
        auto root = m_compositor.CreateContainerVisual();
        root.RelativeSizeAdjustment({ 1.0f, 1.0f });
        root.Offset({ 124, 12, 0 });
        m_target.Root(root);
    }
    

Compila ora il progetto per assicurarti che non siano presenti errori.

Questi metodi configurano i componenti necessari per l'interoperabilità tra il livello visivo UWP e le API Win32. A questo punto puoi aggiungere contenuto all'app.

Aggiungere elementi di composizione

Dopo aver definito l'infrastruttura, puoi generare il contenuto di composizione che vuoi mostrare.

Per questo esempio, aggiungi il codice che crea un quadrato SpriteVisual colorato in modo casuale con un'animazione che ne provoca l'eliminazione dopo un breve intervallo.

  1. Aggiungi un elemento di composizione.

    • In CompositionHost.h dichiara un metodo pubblico denominato AddElement che accetta tre valori di float come argomenti.
    void AddElement(float size, float x, float y);
    
    • In CompositionHost.cpp aggiungi la definizione del metodo 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);
        }
    }
    

Creare e visualizzare la finestra

A questo punto puoi aggiungere un pulsante e il contenuto della composizione UWP all'interfaccia utente Win32.

  1. In HelloComposition.cpp, all'inizio del file, includi CompositionHost.h, definisci BTN_ADD e ottieni un'istanza di CompositionHost.

    #include "CompositionHost.h"
    
    // #define MAX_LOADSTRING 100 // This is already in the file.
    #define BTN_ADD 1000
    
    CompositionHost* compHost = CompositionHost::GetInstance();
    
  2. Nel metodo InitInstance modifica le dimensioni della finestra creata. In questa riga modifica il valore di CW_USEDEFAULT, 0 impostando 900, 672.

    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, 900, 672, nullptr, nullptr, hInstance, nullptr);
    
  3. Nella funzione WndProc aggiungi case WM_CREATE al blocco switch message. In questo caso inizializza CompositionHost e crea il pulsante.

    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. Nella funzione WndProc gestisci anche il clic del pulsante per aggiungere un elemento di composizione all'interfaccia utente.

    Aggiungi case BTN_ADD al blocco switch wmId nel blocco 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;
    }
    

A questo punto puoi compilare ed eseguire l'app. Se necessario, controlla il codice completo alla fine dell'esercitazione per assicurarti che tutto il codice sia nelle posizioni corrette.

Quando esegui l'app e fai clic sul pulsante, dovresti visualizzare quadrati animati aggiunti all'interfaccia utente.

Risorse aggiuntive

Codice completo

Ecco il codice completo per la classe CompositionHost e il metodo 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 (parziale)

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