Размещение стандартного элемента управления XAML UWP в классическом приложении C++ (Win32)

Это важно

В этом разделе используются или упоминаются типы из репозитория CommunityToolkit/Microsoft.Toolkit.Win32 GitHub. Сведения о поддержке островов XAML UWP см. в уведомлении XAML Islands в этом репозитории.

В этой статье показано, как использовать API размещения XAML UWP XAML для размещения стандартного элемента управления XAML UWP (т. е. элемента управления, предоставленного пакетом SDK Windows) в новом классическом приложении C++. Код основан на примере простого XAML Island, и этот раздел рассматривает самые важные части кода. Если у вас есть существующий настольный проект приложения на C++, можно адаптировать эти шаги и примеры кода для вашего проекта.

Замечание

Сценарий, показанный в этой статье, не поддерживает непосредственное редактирование разметки XAML для элементов управления UWP XAML, размещенных в приложении. Изменить внешний вид и поведение размещенных элементов управления в этом сценарии возможно только с помощью кода. Инструкции, позволяющие напрямую изменять разметку XAML при размещении элементов управления XAML UWP, см. в разделе "Размещение пользовательского элемента управления XAML UWP" в классическом приложении C++.

Создание проекта настольного приложения

  1. В Visual Studio 2019 с установленным пакетом SDK Windows 10 версии 1903 (сборка 10.0.18362) или более поздней версии создайте новое приложение Windows Desktop Application project и назовите его MyDesktopWin32App. Этот тип проекта доступен в фильтрах проектов C++, Windows и Desktop.

  2. В Обозреватель решений щелкните правой кнопкой мыши узел решения, щелкните Retarget solution, выберите 10.0.18362.0 или более позднюю версию SDK и затем щелкните OK.

  3. Установите пакет NuGet Microsoft.Windows.CppWinRT чтобы включить поддержку C++/WinRT в project:

    1. Щелкните правой кнопкой мыши проект в Обозреватель решений и выберите Управление пакетами NuGet.
    2. Перейдите на вкладку Обзор, найдите пакет Microsoft.Windows.CppWinRT и установите последнюю версию этого пакета.

    Замечание

    Для новых проектов можно также установить C++/WinRT Visual Studio Extension (VSIX) и использовать один из шаблонов project C++/WinRT, включенных в это расширение. Дополнительные сведения см. об поддержке C++/WinRT и VSIX в Visual Studio.

  4. На вкладке Browse окна NuGet диспетчер пакетов выполните поиск NuGet пакета Microsoft.Toolkit.Win32.UI.SDK и установите последнюю стабильную версию этого пакета. Этот пакет предоставляет несколько ресурсов времени сборки и времени выполнения, которые позволяют UWP XAML Islands работать в приложении.

  5. Задайте значение maxversiontested в манифесте application, чтобы указать, что приложение совместимо с Windows 10 версии 1903.

    1. Если у вас еще нет манифеста приложения в project, добавьте новый XML-файл в project и назовите его app.manifest.

    2. Включите в манифест приложения элемент compatibility и дочерние элементы, показанные в следующем примере. Замените атрибут Id в элементе maxversiontested номером версии Windows, для которой предназначено приложение (это должен быть выпуск 10.0.18362.0 или выше). Обратите внимание, что установка более высокого значения означает, что более старые версии Windows не будут запускать приложение должным образом, так как каждый выпуск Windows учитывает только версии, предшествующие этой. Если вы хотите, чтобы приложение работало в Windows 10 версии 1903 (сборка 10.0.18362), следует оставить значение 10.0.18362.0 как есть, или добавить несколько maxversiontested для различных значений, поддерживаемых приложением.

      <?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. Добавьте ссылку на метаданные среда выполнения Windows:

    1. В Обозреватель решений щелкните правой кнопкой мыши узел project References и выберите Add Reference.
    2. Нажмите кнопку Обзор в нижней части страницы и перейдите к папке UnionMetadata по пути установки пакета SDK. По умолчанию пакет SDK будет установлен в C:\Program Files (x86)\Windows Kits\10\UnionMetadata.
    3. Затем выберите папку с именем версии Windows( например, 10.0.18362.0) и в этой папке выберите Windows.winmd файл.
    4. Нажмите кнопку ОК, чтобы закрыть диалоговое окно Добавление ссылки.

Используйте API хостинга XAML для размещения элемента управления UWP XAML

Базовый процесс использования API размещения XAML для размещения элемента управления XAML UWP выполняет следующие общие действия.

  1. Инициализируйте платформу XAML UWP для текущего потока до того, как ваше приложение создаст какие-либо объекты Windows.UI.Xaml.UIElement, которые будут размещаться. Существует несколько способов выполнить эту задачу в зависимости от того, когда вы планируете создать объект DesktopWindowXamlSource, который будет содержать элементы управления.

    • Если ваше приложение создаёт объект DesktopWindowXamlSource перед созданием любого из размещаемых объектов Windows.UI.Xaml.UIElement, то инициализация этой платформы будет выполнена автоматически при создании экземпляра объекта DesktopWindowXamlSource. В этом случае вам не потребуется собственный код для инициализации платформы.

    • Однако если ваше приложение создает объекты Windows.UI.Xaml.UIElement до создания объекта DesktopWindowXamlSource, который будет их размещать, приложение должно вызвать статический метод WindowsXamlManager.InitializeForCurrentThread, чтобы явно инициализировать платформу XAML UWP перед созданием объектов Windows.UI.Xaml.UIElement. Обычно приложение должно вызывать этот метод при создании экземпляра родительского элемента пользовательского интерфейса, который управляет DesktopWindowXamlSource.

    Замечание

    Этот метод возвращает объект WindowsXamlManager , содержащий ссылку на платформу XAML UWP. На данном потоке можно создать любое количество объектов WindowsXamlManager. Тем не менее, так как каждый объект содержит ссылку на платформу XAML UWP, необходимо удалить объекты, чтобы гарантировать, что ресурсы XAML в конечном итоге освобождены.

  2. Создайте объект DesktopWindowXamlSource и присоедините его к родительскому элементу пользовательского интерфейса в приложении, которое связано с обработчиком окна.

    Для этого вам нужно следовать этим шагам:

    1. Создайте объект DesktopWindowXamlSource и преобразуйте его в интерфейс COM IDesktopWindowXamlSourceNative или IDesktopWindowXamlSourceNative2.

    2. Вызовите метод AttachToWindow интерфейса IDesktopWindowXamlSourceNative или IDesktopWindowXamlSourceNative2, передав дескриптор окна родительского элемента пользовательского интерфейса в приложении.

      Это важно

      Убедитесь, что код вызывает метод AttachToWindow только один раз для каждого объекта DesktopWindowXamlSource. Вызов этого метода более одного раза для объекта DesktopWindowXamlSource может привести к утечке памяти.

    3. Задайте начальный размер для внутреннего дочернего окна, размещенного в DesktopWindowXamlSource. По умолчанию этому внутреннему дочернему окну присваиваются нулевые значения высоты и ширины. Если размер окна не задан, все элементы управления XAML UWP, добавленные в DesktopWindowXamlSource , не будут отображаться. Чтобы получить доступ к внутреннему дочернему окну в DesktopWindowXamlSource, используйте свойство WindowHandle интерфейса IDesktopWindowXamlSourceNative или IDesktopWindowXamlSourceNative2.

  3. Наконец, назначьте элемент Windows.UI.Xaml.UIElement, который вы хотите разместить, свойству Content объекта DesktopWindowXamlSource.

В следующих шагах и примерах кода показано, как реализовать вышеописанный процесс.

  1. В папке Source Files project откройте файл по умолчанию MyDesktopWin32App.cpp. Удалите все содержимое файла и добавьте вместо него следующие инструкции include и using. Помимо стандартных заголовков и пространств имен UWP, эти инструкции включают несколько элементов, относящихся к островам XAML UWP.

    #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. Скопируйте следующий код после предыдущего раздела. Этот код определяет в приложении функцию WinMain. Эта функция инициализирует простое окно и применяет API размещения XAML для размещения простого элемента управления UWP TextBlock в окне.

    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 UWP 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. Скопируйте следующий код после предыдущего раздела. Этот код определяет процедуру окна для окна.

    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. Сохраните этот файл с кодом и выполните приложение. Убедитесь, что в окне приложения отображается элемент управления UWP TextBlock.

    Замечание

    Возможно, вы увидите несколько предупреждений сборки, таких как warning C4002: too many arguments for function-like macro invocation 'GetCurrentTime' и manifest authoring warning 81010002: Unrecognized Element "maxversiontested" in namespace "urn:schemas-microsoft-com:compatibility.v1". Это известная проблема в текущих версиях средств и пакетов NuGet. Такие предупреждения можно игнорировать.

Полные примеры, демонстрирующие использование API размещения XAML для размещения элемента управления XAML UWP, см. в следующих файлах кода:

Создание пакета приложения

При желании вы можете упаковать приложение в пакет MSIX для развертывания. MSIX — это современная технология упаковки приложений для Windows. В ее основе лежат технологии установки MSI, APPX, App-V и ClickOnce.

В следующих инструкциях показано, как упаковать все компоненты решения в пакет MSIX с помощью пакета Windows Application Package Project в Visual Studio 2019. Эти действия требуются, только если нужно упаковать приложение в пакет MSIX.

Замечание

Если вы решили не упаковывать приложение в пакет MSIX для развертывания, на компьютерах с запущенными приложениями должна быть установлена среда выполнения Visual C++.

  1. Добавьте в решение новый проект Windows Application Packaging Project. При создании проекта выберите Windows 10 версии 1903 (10.0; Сборка 18362) для целевой версии и минимальной версии.

  2. В проекте упаковки щелкните правой кнопкой мыши узел Applications и выберите Add reference. В списке проектов выберите настольное приложение C++ в вашем решении и щелкните OK.

  3. Создайте и запустите проект пакетирования. Убедитесь, что приложение выполняется и отображает элементы управления XAML UWP должным образом.

  4. Сведения о распространении и развертывании пакета см. в статье Управление развертыванием MSIX.

Дальнейшие шаги

Примеры кода в этой статье знакомят с базовым сценарием хостинга стандартного элемента управления XAML UWP в настольном приложении C++. В следующих разделах представляются дополнительные сценарии, которые вашему приложению, возможно, потребуется поддерживать.

Размещение пользовательского элемента управления XAML UWP

Для многих сценариев может потребоваться разместить пользовательский элемент управления XAML UWP, содержащий несколько отдельных элементов управления, которые работают вместе. Процесс размещения настраиваемого элемента управления (который вы определите самостоятельно или получите от третьей стороны) в классическом приложении C++ будет более сложным, чем для стандартного элемента управления. Для этого требуется дополнительный код.

Полное пошаговое руководство по размещению пользовательского элемента управления XAML UWP в классическом приложении C++ с помощью API размещения XAML.

Расширенные сценарии

Многие настольные приложения, на которых размещаются острова XAML UWP, должны обрабатывать дополнительные сценарии, чтобы обеспечить плавное взаимодействие с пользователем. Например, настольным приложениям может потребоваться обрабатывать ввод с клавиатуры на островах XAML в UWP, навигацию фокуса между островами XAML в UWP и другими элементами пользовательского интерфейса, а также изменения макета.

Дополнительные сведения об обработке этих сценариев и указателях на связанные примеры кода см. в дополнительных сценариях для островов XAML UWP.