Udostępnij przez


Hostowanie standardowej kontrolki XAML WinRT w aplikacji klasycznej C++ (Win32)

Ważne

W tym temacie wykorzystywane lub wspomniane są typy z repozytorium GitHub CommunityToolkit/Microsoft.Toolkit.Win32. Aby uzyskać ważne informacje o obsłudze wysp XAML, zobacz XAML Islands Notice w tym repozytorium.

W tym artykule pokazano, jak używać interfejsu API hostingu XAML WinRT do hostowania standardowej kontrolki XAML WinRT (czyli kontrolki dostarczonej przez zestaw Windows SDK) w nowej aplikacji klasycznej języka C++. Kod jest oparty na prostym przykładzie XAML Island , a w tej sekcji omówiono niektóre z najważniejszych części kodu. Jeśli masz istniejący projekt aplikacji klasycznej w języku C++, możesz dostosować te kroki i przykłady kodu dla projektu.

Uwaga / Notatka

Scenariusz przedstawiony w tym artykule nie obsługuje bezpośredniego edytowania znaczników XAML dla kontrolek XAML WinRT hostowanych w aplikacji. Ten scenariusz ogranicza modyfikowanie wyglądu i zachowania hostowanych kontrolek za pomocą kodu. Aby uzyskać instrukcje umożliwiające bezpośrednią edycję znaczników XAML podczas hostowania kontrolek XAML WinRT, zobacz Hostowanie niestandardowej kontrolki XAML WinRT w aplikacji klasycznej języka C++.

Utwórz projekt aplikacji desktopowej

  1. W programie Visual Studio 2019 z zainstalowanym systemem Windows 10 oraz wersją 1903 SDK (kompilacja 10.0.18362) lub nowszą, utwórz nowy projekt Aplikacji Windows i nadaj mu nazwę MyDesktopWin32App. Ten typ projektu jest dostępny w filtrach projektu C++, Windowsi Desktop.

  2. W Eksploratorze rozwiązań kliknij prawym przyciskiem myszy węzeł rozwiązania, kliknij Retarget solution, wybierz 10.0.18362.0 lub nowszą wersję zestawu SDK, a następnie kliknij przycisk OK.

  3. Zainstaluj pakiet Microsoft.Windows.CppWinRT NuGet, aby umożliwić obsługę C++/WinRT w projekcie:

    1. Kliknij prawym przyciskiem myszy projekt w eksploratorze rozwiązań i wybierz pozycję Zarządzaj pakietami NuGet.
    2. Wybierz kartę Przeglądaj, wyszukaj pakiet Microsoft.Windows.CppWinRT i zainstaluj najnowszą wersję tego pakietu.

    Uwaga / Notatka

    W przypadku nowych projektów można również zainstalować C++/WinRT Visual Studio Extension (VSIX) i użyć jednego z szablonów projektów C++/WinRT zawartych w tym rozszerzeniu. Aby uzyskać więcej informacji, zobacz obsługa programu Visual Studio dla języka C++/WinRT orazVSIX .

  4. Na karcie Przeglądaj w oknie menedżera pakietów NuGet wyszukaj pakiet Microsoft.Toolkit.Win32.UI.SDK i zainstaluj najnowszą stabilną wersję tego pakietu. Pakiet ten zawiera kilka zasobów związanych z kompilacją i czasem wykonywania, które pozwalają wyspom XAML działać w Twojej aplikacji.

  5. Ustaw wartość maxversiontested w manifeście aplikacji , aby określić, że aplikacja jest zgodna z systemem Windows 10 w wersji 1903.

    1. Jeśli nie masz jeszcze manifestu aplikacji w projekcie, dodaj nowy plik XML do projektu i nadaj mu nazwę app.manifest.

    2. W manifeście aplikacji dołącz element zgodności i elementy podrzędne pokazane w poniższym przykładzie. Zastąp atrybut Id elementu maxversiontested numerem wersji systemu Windows, który jest celem (musi to być 10.0.18362.0 lub nowsza). Należy pamiętać, że ustawienie wyższej wartości oznacza, że starsze wersje systemu Windows nie będą prawidłowo uruchamiać aplikacji, ponieważ każda wersja systemu Windows zna tylko wersje przed nim. Jeśli chcesz, aby aplikacja działała w systemie Windows 10 w wersji 1903 (kompilacja 10.0.18362), pozostaw wartość 10.0.18362.0 bez zmian lub dodaj wiele elementów maxversiontested dla różnych wartości, które obsługuje aplikacja.

      <?xml version="1.0" encoding="UTF-8"?>
      <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
          <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
              <application>
                  <!-- Windows 10 -->
                  <maxversiontested Id="10.0.18362.0"/>
                  <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
              </application>
          </compatibility>
      </assembly>
      
  6. Dodaj odwołanie do metadanych środowiska uruchomieniowego systemu Windows:

    1. W Eksploratorze rozwiązań kliknij prawym przyciskiem myszy węzeł 'Odwołania' projektu i wybierz pozycję Dodaj odwołanie.
    2. Kliknij przycisk Przeglądaj w dolnej części strony i przejdź do folderu UnionMetadata w ścieżce instalacji zestawu SDK. Domyślnie SDK będzie instalowane do C:\Program Files (x86)\Windows Kits\10\UnionMetadata.
    3. Następnie wybierz folder o nazwie po docelowej wersji systemu Windows (np. 10.0.18362.0) i wewnątrz tego folderu wybierz Windows.winmd plik.
    4. Kliknij przycisk OK, aby zamknąć okno dialogowe Dodaj odwołanie.

Aby hostować kontrolkę XAML WinRT, użyj interfejsu API hostingu XAML

Podstawowy proces używania interfejsu API hostingu XAML do hostowania kontrolki XAML WinRT tyczy się następujących ogólnych kroków:

  1. Zainicjuj strukturę XAML winRT dla bieżącego wątku, zanim aplikacja utworzy dowolną z Windows.UI.Xaml.UIElement obiektów, które będzie hostować. Istnieje kilka sposobów, aby to zrobić, w zależności od tego, kiedy planujesz utworzyć obiekt DesktopWindowXamlSource, który będzie hostować kontrolki.

    • Jeśli aplikacja utworzy obiekt DesktopWindowXamlSource przed utworzeniem jakiegokolwiek obiektu Windows.UI.Xaml.UIElement, które będą hostowane, ta struktura zostanie zainicjowana, kiedy instancjonujesz obiekt DesktopWindowXamlSource. W tym scenariuszu nie musisz dodawać własnego kodu, aby zainicjować framework.

    • Jeśli jednak aplikacja tworzy obiekty Windows.UI.Xaml.UIElement przed utworzeniem obiektu DesktopWindowXamlSource, który będzie je hostować, aplikacja musi wywołać metodę statyczną WindowsXamlManager.InitializeForCurrentThread, aby jawnie zainicjować strukturę XAML WinRT przed utworzeniem wystąpienia obiektów Windows.UI.Xaml.UIElement. Aplikacja zazwyczaj powinna wywoływać tę metodę, gdy jest tworzony nadrzędny element interfejsu użytkownika, który hostuje wystąpienie DesktopWindowXamlSource.

    Uwaga / Notatka

    Ta metoda zwraca obiekt WindowsXamlManager zawierający odwołanie do struktury XAML WinRT. Można utworzyć dowolną liczbę obiektów WindowsXamlManager w danym wątku. Jednak ponieważ każdy obiekt zawiera odwołanie do struktury XAML winRT, należy usunąć obiekty, aby upewnić się, że zasoby XAML zostaną ostatecznie zwolnione.

  2. Utwórz obiekt DesktopWindowXamlSource i dołącz go do nadrzędnego elementu interfejsu użytkownika w aplikacji skojarzonej z uchwytem okna.

    W tym celu należy wykonać następujące kroki:

    1. Utwórz obiekt DesktopWindowXamlSource i rzutuj go do interfejsu IDesktopWindowXamlSourceNative lub IDesktopWindowXamlSourceNative2 COM.

    2. Wywołaj metodę AttachToWindow interfejsu IDesktopWindowXamlSourceNative lub IDesktopWindowXamlSourceNative2 i przekaż uchwyt okna nadrzędnego elementu interfejsu użytkownika w swojej aplikacji.

      Ważne

      Upewnij się, że twój kod wywołuje metodę AttachToWindow tylko raz na obiekt DesktopWindowXamlSource. Wywołanie tej metody więcej niż raz dla obiektu DesktopWindowXamlSource może spowodować wyciek pamięci.

    3. Ustaw początkowy rozmiar wewnętrznego okna podrzędnego zawartego w DesktopWindowXamlSource. Domyślnie to wewnętrzne okno podrzędne jest ustawione na szerokość i wysokość 0. Jeśli nie ustawisz rozmiaru okna, żadne kontrolki XAML winRT dodawane do DesktopWindowXamlSource nie będą widoczne. Aby uzyskać dostęp do wewnętrznego okna podrzędnego w DesktopWindowXamlSource, użyj właściwości WindowHandle interfejsu IDesktopWindowXamlSourceNative lub IDesktopWindowXamlSourceNative2.

  3. Na koniec przypisz właściwość Windows.UI.Xaml.UIElement, którą chcesz hostować, do właściwości Content obiektu DesktopWindowXamlSource.

W poniższych krokach i przykładach kodu pokazano, jak zaimplementować powyższy proces:

  1. W folderze plików źródłowych projektu otwórz domyślny plik MyDesktopWin32App.cpp. Usuń całą zawartość pliku i dodaj następujące instrukcje include i using. Oprócz standardowych nagłówków standardu C++ i przestrzeni nazw UWP, te instrukcje zawierają kilka elementów specyficznych dla XAML Islands.

    #include <windows.h>
    #include <stdlib.h>
    #include <string.h>
    
    #include <winrt/Windows.Foundation.Collections.h>
    #include <winrt/Windows.system.h>
    #include <winrt/windows.ui.xaml.hosting.h>
    #include <windows.ui.xaml.hosting.desktopwindowxamlsource.h>
    #include <winrt/windows.ui.xaml.controls.h>
    #include <winrt/Windows.ui.xaml.media.h>
    
    using namespace winrt;
    using namespace Windows::UI;
    using namespace Windows::UI::Composition;
    using namespace Windows::UI::Xaml::Hosting;
    using namespace Windows::Foundation::Numerics;
    
  2. Skopiuj następujący kod po poprzedniej sekcji. Ten kod definiuje funkcję WinMain dla aplikacji. Ta funkcja inicjuje okno podstawowe i używa interfejsu API hostingu XAML do hostowania prostej kontrolki platformy UWP TextBlock w oknie.

    LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
    
    HWND _hWnd;
    HWND _childhWnd;
    HINSTANCE _hInstance;
    
    int CALLBACK WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
    {
        _hInstance = hInstance;
    
        // The main window class name.
        const wchar_t szWindowClass[] = L"Win32DesktopApp";
        WNDCLASSEX windowClass = { };
    
        windowClass.cbSize = sizeof(WNDCLASSEX);
        windowClass.lpfnWndProc = WindowProc;
        windowClass.hInstance = hInstance;
        windowClass.lpszClassName = szWindowClass;
        windowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    
        windowClass.hIconSm = LoadIcon(windowClass.hInstance, IDI_APPLICATION);
    
        if (RegisterClassEx(&windowClass) == NULL)
        {
            MessageBox(NULL, L"Windows registration failed!", L"Error", NULL);
            return 0;
        }
    
        _hWnd = CreateWindow(
            szWindowClass,
            L"Windows c++ Win32 Desktop App",
            WS_OVERLAPPEDWINDOW | WS_VISIBLE,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            NULL,
            NULL,
            hInstance,
            NULL
        );
        if (_hWnd == NULL)
        {
            MessageBox(NULL, L"Call to CreateWindow failed!", L"Error", NULL);
            return 0;
        }
    
    
        // Begin XAML Island section.
    
        // The call to winrt::init_apartment initializes COM; by default, in a multithreaded apartment.
        winrt::init_apartment(apartment_type::single_threaded);
    
        // Initialize the XAML framework's core window for the current thread.
        WindowsXamlManager winxamlmanager = WindowsXamlManager::InitializeForCurrentThread();
    
        // This DesktopWindowXamlSource is the object that enables a non-UWP desktop application 
        // to host WinRT XAML controls in any UI element that is associated with a window handle (HWND).
        DesktopWindowXamlSource desktopSource;
    
        // Get handle to the core window.
        auto interop = desktopSource.as<IDesktopWindowXamlSourceNative>();
    
        // Parent the DesktopWindowXamlSource object to the current window.
        check_hresult(interop->AttachToWindow(_hWnd));
    
        // This HWND will be the window handler for the XAML Island: A child window that contains XAML.  
        HWND hWndXamlIsland = nullptr;
    
        // Get the new child window's HWND. 
        interop->get_WindowHandle(&hWndXamlIsland);
    
        // Update the XAML Island window size because initially it is 0,0.
        SetWindowPos(hWndXamlIsland, 0, 200, 100, 800, 200, SWP_SHOWWINDOW);
    
        // Create the XAML content.
        Windows::UI::Xaml::Controls::StackPanel xamlContainer;
        xamlContainer.Background(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::LightGray() });
    
        Windows::UI::Xaml::Controls::TextBlock tb;
        tb.Text(L"Hello World from Xaml Islands!");
        tb.VerticalAlignment(Windows::UI::Xaml::VerticalAlignment::Center);
        tb.HorizontalAlignment(Windows::UI::Xaml::HorizontalAlignment::Center);
        tb.FontSize(48);
    
        xamlContainer.Children().Append(tb);
        xamlContainer.UpdateLayout();
        desktopSource.Content(xamlContainer);
    
        // End XAML Island section.
    
        ShowWindow(_hWnd, nCmdShow);
        UpdateWindow(_hWnd);
    
        //Message loop:
        MSG msg = { };
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    
        return 0;
    }
    
  3. Skopiuj następujący kod po poprzedniej sekcji. Ten kod definiuje procedurę okna dla okna.

    LRESULT CALLBACK WindowProc(HWND hWnd, UINT messageCode, WPARAM wParam, LPARAM lParam)
    {
        PAINTSTRUCT ps;
        HDC hdc;
        wchar_t greeting[] = L"Hello World in Win32!";
        RECT rcClient;
    
        switch (messageCode)
        {
            case WM_PAINT:
                if (hWnd == _hWnd)
                {
                    hdc = BeginPaint(hWnd, &ps);
                    TextOut(hdc, 300, 5, greeting, wcslen(greeting));
                    EndPaint(hWnd, &ps);
                }
                break;
            case WM_DESTROY:
                PostQuitMessage(0);
                break;
    
            // Create main window
            case WM_CREATE:
                _childhWnd = CreateWindowEx(0, L"ChildWClass", NULL, WS_CHILD | WS_BORDER, 0, 0, 0, 0, hWnd, NULL, _hInstance, NULL);
                return 0;
    
            // Main window changed size
            case WM_SIZE:
                // Get the dimensions of the main window's client
                // area, and enumerate the child windows. Pass the
                // dimensions to the child windows during enumeration.
                GetClientRect(hWnd, &rcClient);
                MoveWindow(_childhWnd, 200, 200, 400, 500, TRUE);
                ShowWindow(_childhWnd, SW_SHOW);
    
                return 0;
    
                // Process other messages.
    
            default:
                return DefWindowProc(hWnd, messageCode, wParam, lParam);
                break;
        }
    
        return 0;
    }
    
  4. Zapisz plik kodu i skompiluj i uruchom aplikację. Upewnij się, że w oknie aplikacji jest widoczna kontrolka TextBlock platformy UWP.

    Uwaga / Notatka

    Może zostać wyświetlonych kilka ostrzeżeń dotyczących kompilacji, w tym warning C4002: too many arguments for function-like macro invocation 'GetCurrentTime' i manifest authoring warning 81010002: Unrecognized Element "maxversiontested" in namespace "urn:schemas-microsoft-com:compatibility.v1". Te ostrzeżenia są znanymi problemami z bieżącymi narzędziami i pakietami NuGet i można je zignorować.

Aby uzyskać kompletne przykłady demonstrujące używanie interfejsu API hostingu XAML do hostowania kontrolki XAML winRT, zobacz następujące pliki kodu:

Spakuj aplikację

Opcjonalnie możesz spakować aplikację w pakiecie MSIX do wdrożenia. MSIX to nowoczesna technologia tworzenia pakietów aplikacji dla systemu Windows, która jest oparta na połączeniu technologii instalacyjnych MSI, .appx, App-V i ClickOnce.

Poniższe instrukcje pokazują, jak przygotować wszystkie składniki rozwiązania w formie pakietu MSIX, korzystając z Projektu tworzenia pakietów aplikacji systemu Windows w programie Visual Studio 2019. Te kroki są niezbędne tylko wtedy, gdy chcesz spakować aplikację w pakiecie MSIX.

Uwaga / Notatka

Jeśli zdecydujesz się nie spakować aplikacji w pakiet MSIX do wdrożenia, komputery, na których działa twoja aplikacja, muszą mieć zainstalowane środowisko uruchomieniowe Visual C++.

  1. Dodaj nowy projekt pakietowania aplikacji Windows do swojego rozwiązania. Podczas tworzenia projektu wybierz pozycję Windows 10 w wersji 1903 (10.0; Kompilacja 18362) dla wersji docelowej i Minimalna wersja.

  2. W projekcie pakowania kliknij prawym przyciskiem myszy na węzeł Aplikacje i wybierz opcję Dodaj referencję. Na liście projektów wybierz projekt aplikacji desktopowej C++ w rozwiązaniu i kliknij przycisk OK.

  3. Skompiluj i uruchom projekt pakowania. Upewnij się, że aplikacja działa i wyświetla kontrolki XAML winRT zgodnie z oczekiwaniami.

  4. Aby uzyskać informacje o dystrybucji/wdrażaniu pakietu, zobacz Zarządzanie wdrożeniem MSIX.

Dalsze kroki

Przykłady kodu w tym artykule ułatwiają rozpoczęcie pracy z podstawowym scenariuszem hostowania standardowej kontrolki XAML WinRT w aplikacji klasycznej języka C++. W poniższych sekcjach przedstawiono dodatkowe scenariusze, które aplikacja może potrzebować obsłużyć.

Hostowanie niestandardowej kontrolki WinRT XAML

W wielu scenariuszach może być konieczne hostowanie niestandardowej kontrolki XAML WinRT zawierającej kilka pojedynczych kontrolek, które współpracują ze sobą. Proces hostowania kontrolki niestandardowej (kontrolki definiowanej samodzielnie lub kontrolki dostarczonej przez inną firmę) w aplikacji klasycznej języka C++ jest bardziej złożony niż hostowanie standardowej kontrolki i wymaga dodatkowego kodu.

Aby uzyskać pełny przewodnik, zobacz Hostowanie niestandardowej kontrolki XAML WinRT w aplikacji desktopowej C++ przy użyciu API hostingu XAML.

Scenariusze zaawansowane

Wiele aplikacji komputerowych hostujących wyspy XAML będzie musiało obsługiwać dodatkowe scenariusze, aby zapewnić bezproblemowe doświadczenie użytkownika. Na przykład aplikacje desktopowe mogą wymagać obsługi danych wejściowych z klawiatury na wyspach XAML, nawigacji między wyspami XAML a innymi elementami interfejsu użytkownika oraz zmian układu.

Aby uzyskać więcej informacji na temat obsługi tych scenariuszy oraz referencje do powiązanych przykładów kodu, zobacz Zaawansowane scenariusze dla XAML Islands w aplikacjach desktopowych w C++.