Verwenden der visuellen Ebene mit Win32

Sie können in Ihren Win32-Apps die Composition-APIs von Windows-Runtime (die auch als visuelle Ebene bezeichnet werden) verwenden, um moderne Umgebungen für Windows-Benutzer zu erstellen.

Den gesamten Code für dieses Tutorial findest du auf GitHub: Win32-Beispiel HelloComposition.

Universelle Windows-Anwendungen, die eine präzise Steuerung der Komposition ihrer Benutzeroberfläche benötigen, haben Zugriff auf den Namespace Windows.UI.Composition, der eine detaillierte Steuerung der Komposition und Darstellung der Benutzeroberfläche ermöglicht. Diese Composition-API ist jedoch nicht auf UWP-Apps beschränkt. Win32-Desktopanwendungen können die modernen Kompositionssysteme in UWP und Windows nutzen.

Voraussetzungen

Für die UWP hostende API gelten diese Voraussetzungen.

Verwenden von Composition-APIs in einer Win32-Desktopanwendung

In diesem Tutorial erstellst du eine einfache Benutzeroberfläche für eine Win32 C++-App und fügst ihr Composition-Elemente von UWP hinzu. Der Schwerpunkt liegt auf der ordnungsgemäßen Konfiguration des Projekts, der Erstellung des Interopcodes und dem Zeichnen von etwas Einfachem mithilfe von Composition-APIs von Windows. Die fertige App sieht wie folgt aus.

Benutzeroberfläche der laufenden App

Erstellen eines C++ Win32-Projekts in Visual Studio

Der erste Schritt ist das Erstellen des Win32-App-Projekts in Visual Studio.

So erstellst du ein Win32-Anwendungsprojekt in C++ namens HelloComposition

  1. Öffne Visual Studio, und wähle Datei>Neu>Projekt aus.

    Das Dialogfeld Neues Projekt wird geöffnet.

  2. Erweitere unter der Kategorie Installiert den Knoten Visual C++ , und wähle dann Windows-Desktop aus.

  3. Wähle die Vorlage Windows-Desktopanwendung aus.

  4. Gib den Namen HelloComposition ein, und klicke dann auf OK.

    Visual Studio erstellt das Projekt und öffnet den Editor für die Hauptdatei der App.

Konfigurieren des Projekts für die Verwendung der Windows-Runtime-APIs

Zum Verwenden von Windows-Runtime-APIs (WinRT) in deiner Win32-APP nutzen wir C++/WinRT. Du musst dein Visual Studio-Projekt so konfigurieren, dass es C++/WinRT-Unterstützung bietet.

(Einzelheiten findest du unter Erste Schritte mit C++/WinRT: Ändern eines Windows Desktop-Anwendungsprojekts, um C++/WinRT-Unterstützung hinzuzufügen).

  1. Öffne im Menü Projekt die Projekteigenschaften (HelloComposition-Eigenschaften) und achte darauf, dass die folgenden Einstellungen auf die angegebenen Werte festgelegt sind:

    • Wähle für Konfiguration die Option Alle Konfigurationen aus. Wähle für Plattform die Option Alle Plattformen aus.
    • Konfigurationseigenschaften>Allgemein>Windows SDK ab Version = 10.0.17763.0

    Festlegen der SDK-Version

    • C/C++>Sprache>C++-Sprachstandard = ISO C++ 17 Standard (/stf:c++17)

    Festlegen des Sprachstandards

    • Linker>Eingabe>Zusätzliche Abhängigkeiten muss windowsapp.lib enthalten. Falls nicht in der Liste enthalten, füge sie hinzu.

    Hinzufügen von Linkerabhängigkeit

  2. Aktualisieren des vorkompilierten Headers

    • Benenne stdafx.h und stdafx.cpp in pch.h und pch.cpp um.

    • Lege die Projekteigenschaft C/C++>Vorkompilierte Header>Vorkompilierter Headerdatei auf pch.h fest.

    • Suche und ersetze #include "stdafx.h" durch #include "pch.h" in allen Dateien.

      (Bearbeiten>Suchen und ersetzen>In Dateien suchen)

      Suchen und Ersetzen von „stdafx.h“

    • Schließe winrt/base.h und unknwn.h in pch.h ein.

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

Es empfiehlt sich, das Projekt zu diesem Zeitpunkt zu kompilieren, um sicherzustellen, dass keine Fehler vorliegen, ehe fortgefahren wird.

Erstellen einer Klasse zum Hosten von Kompositionselementen

Um Inhalte zu hosten, die du mit der visuellen Ebene erstellst, erstelle eine Klasse (CompositionHost) zum Verwalten von Interop-Elementen und Erstellen von Kompositionselementen. In dieser Klasse führst du den größten Teil der Konfiguration für das Hosting von Composition-APIs aus, wie z. B.:

Wir machen diese Klasse zu einem Singleton, um Threadingprobleme zu vermeiden. Du kannst z. B. nur eine Verteilerwarteschlange pro Thread erstellen, weshalb die Instanziierung einer zweiten Instanz von CompositionHost im selben Thread zu einem Fehler führen würde.

Tipp

Überprüfe bei Bedarf am Ende des Tutorials den vollständigen Code, um sicherzustellen, dass sich der gesamte Code während des Durcharbeitens an den richtigen Stellen befindet.

  1. Fügen Sie Ihrem Projekt eine neue Klassendatei hinzu.

    • Klicke im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt HelloComposition.
    • Wähle im Kontextmenü Hinzufügen>Klasse... aus.
    • Benenne die Klasse im Dialogfeld Klasse hinzufügen mit CompositionHost.cs, und klicke dann auf Hinzufügen.
  2. Schließe Header und using-Anweisungen ein, die für die Interop der Komposition erforderlich sind.

    • Füge in „CompositionHost.h“ diese include-Anweisungen am Anfang der Datei hinzu.
    #pragma once
    #include <winrt/Windows.UI.Composition.Desktop.h>
    #include <windows.ui.composition.interop.h>
    #include <DispatcherQueue.h>
    
    • Füge in „CompositionHost.cpp“ diese using-Anweisungen am Anfang der Datei hinter beliebigen include-Anweisungen hinzu.
    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. Bearbeite die Klasse so, dass das Singletonmuster verwendet wird.

    • Lege den Konstruktor in „CompositionHost.h“ als privat fest.
    • Deklariere eine öffentliche statische GetInstance-Methode.
    class CompositionHost
    {
    public:
        ~CompositionHost();
        static CompositionHost* GetInstance();
    
    private:
        CompositionHost();
    };
    
    • Füge in „CompositionHost.cpp“ die Definition der GetInstance-Methode hinzu.
    CompositionHost* CompositionHost::GetInstance()
    {
        static CompositionHost instance;
        return &instance;
    }
    
  4. Deklariere in „CompositionHost.h“ private Membervariablen für Compositor, DispatcherQueueController und 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. Füge eine öffentliche Methode hinzu, um die Interop-Objekte der Komposition zu initialisieren.

    Hinweis

    Rufe in Initialize die Methoden EnsureDispatcherQueue, CreateDesktopWindowTarget und CreateCompositionRoot auf. Du erstellst diese Methoden in den nächsten Schritten.

    • Deklariere in „CompositionHost.h“ eine öffentliche Methode namens Initialize, die ein HWND als Argument verwendet.
    void Initialize(HWND hwnd);
    
    • Füge in „CompositionHost.cpp“ die Definition der Initialize-Methode hinzu.
    void CompositionHost::Initialize(HWND hwnd)
    {
        EnsureDispatcherQueue();
        if (m_dispatcherQueueController) m_compositor = Compositor();
    
        CreateDesktopWindowTarget(hwnd);
        CreateCompositionRoot();
    }
    
  6. Erstelle eine Verteilerwarteschlange für den Thread, die Windows Composition verwendet.

    Ein Compositor muss für einen Thread erstellt werden, der über eine Verteilerwarteschlange verfügt. Daher wird diese Methode bei der Initialisierung zuerst aufgerufen.

    • Deklariere in „CompositionHost.h“ eine private Methode mit dem Namen EnsureDispatcherQueue.
    void EnsureDispatcherQueue();
    
    • Füge in „CompositionHost.cpp“ die Definition der EnsureDispatcherQueue-Methode hinzu.
    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. Registriere das Fenster deiner App als Kompositionsziel.

    • Deklariere in „CompositionHost.h“ eine private Methode namens CreateDesktopWindowTarget, die ein HWND als Argument verwendet.
    void CreateDesktopWindowTarget(HWND window);
    
    • Füge in „CompositionHost.cpp“ die Definition der CreateDesktopWindowTarget-Methode hinzu.
    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. Erstelle einen visuellen Stammcontainer für visuelle Objekte.

    • Deklariere in „CompositionHost.h“ eine private Methode mit dem Namen CreateCompositionRoot.
    void CreateCompositionRoot();
    
    • Füge in „CompositionHost.cpp“ die Definition der CreateCompositionRoot-Methode hinzu.
    void CompositionHost::CreateCompositionRoot()
    {
        auto root = m_compositor.CreateContainerVisual();
        root.RelativeSizeAdjustment({ 1.0f, 1.0f });
        root.Offset({ 124, 12, 0 });
        m_target.Root(root);
    }
    

Erstelle jetzt das Projekt, um sicherzustellen, dass keine Fehler vorliegen.

Diese Methoden richten die Komponenten ein, die für die Interop zwischen der visuellen UWP-Schicht und den Win32-APIs benötigt werden. Nun kannst du deiner App Inhalte hinzufügen.

Hinzufügen von Composition-Elementen

Nachdem du die Infrastruktur eingerichtet hast, kannst du nun den Composition-Inhalt generieren, den du anzeigen möchtest.

Füge für dieses Beispiel Code hinzu, der ein zufällig koloriertes quadratisches SpriteVisual mit einer Animation erzeugt, die bewirkt, dass es nach einer kurzen Verzögerung fällt.

  1. Füge ein Composition-Element hinzu.

    • Deklariere in „CompositionHost.h“ eine öffentliche Methode namens AddElement, die drei float-Werte als Argumente verwendet.
    void AddElement(float size, float x, float y);
    
    • Füge in „CompositionHost.cpp“ die Definition der AddElement-Methode hinzu.
    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);
        }
    }
    

Erstellen und Anzeigen des Fensters

Jetzt kannst du eine Schaltfläche und den Inhalt der UWP-Komposition deiner Win32-Benutzeroberfläche hinzufügen.

  1. Füge in „HelloComposition.cpp“ am Anfang der Datei CompositionHost.h ein. Definiere BTN_ADD, und rufe eine Instanz von CompositionHost ab.

    #include "CompositionHost.h"
    
    // #define MAX_LOADSTRING 100 // This is already in the file.
    #define BTN_ADD 1000
    
    CompositionHost* compHost = CompositionHost::GetInstance();
    
  2. Ändere in der InitInstance-Methode die Größe des Fensters, das erstellt wird. (Ändere in dieser Zeile CW_USEDEFAULT, 0 in 900, 672.)

    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, 900, 672, nullptr, nullptr, hInstance, nullptr);
    
  3. Füge in der WndProc-Funktion case WM_CREATE dem Schalterblock message hinzu. In diesem Fall initialisierst du CompositionHost und erstellst die Schaltfläche.

    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. Behandle außerdem in der WndProc-Funktion den Schaltflächenklick so, dass ein Composition-Element zur Benutzeroberfläche hinzugefügt wird.

    Füge innerhalb des WM_COMMAND-Blocks case BTN_ADD dem Schalterblock wmId hinzu.

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

Jetzt kannst du deine App kompilieren und ausführen. Überprüfe ggf. den vollständigen Code am Ende des Tutorials, um sicherzustellen, dass sich der gesamte Code an den richtigen Stellen befindet.

Wenn du die App ausführst und auf die Schaltfläche klickst, sollten die animierten Quadrate der Benutzeroberfläche hinzugefügt werden.

Zusätzliche Ressourcen

Vollständiger Code

Hier ist der vollständige Code der CompositionHost-Klasse und der InitInstance-Methode.

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 (partiell)

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