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

Важно!

В этом разделе используются или упоминание типы из сообщества набор средств/Microsoft.набор средств. Репозиторий Win32 GitHub. Дополнительные сведения о поддержке XAML Islands см . в уведомлении о xaml Islands в этом репозитории.

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

Примечание.

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

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

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

  2. В обозревателе решений щелкните правой кнопкой мыши узел решения, выберите команду Изменить целевую платформу решения, выберите 10.0.18362.0 или более позднюю версию пакета SDK, а затем нажмите кнопку ОК.

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

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

    Примечание.

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

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

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

    1. Если в проекте еще нет манифеста приложения, добавьте в проект новый XML-файл и присвойте ему имя 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. В Обозревателе решений щелкните правой кнопкой мыши узел Ссылки проекта и выберите команду Добавить ссылку.
    2. Нажмите кнопку Обзор в нижней части страницы и перейдите к папке UnionMetadata по пути установки пакета SDK. По умолчанию пакет SDK будет установлен в C:\Program Files (x86)\Windows Kits\10\UnionMetadata.
    3. Затем выберите папку с именем, которое соответствует целевой версии Windows (например, 10.0.18362.0). В этой папке выберите файл Windows.winmd.
    4. Нажмите кнопку ОК, чтобы закрыть диалоговое окно Добавление ссылки.

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

Базовый процесс использования API размещения XAML для размещения элемента управления WinRT XAML включает следующие шаги:

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

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

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

    Примечание.

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

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

    Для этого выполните следующие действия:

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

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

      Важно!

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

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

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

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

  1. В папке проекта Source Files откройте стандартный файл MyDesktopWin32App.cpp. Удалите все содержимое файла и добавьте вместо него следующие инструкции include и using. В дополнение к стандартным заголовкам и пространствам имен C++ и UWP эти инструкции включают несколько элементов специально для 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. Скопируйте следующий код после предыдущего раздела. Этот код определяет в приложении функцию 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 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. Скопируйте следующий код после предыдущего раздела. Этот код определяет в приложении рабочую процедуру.

    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 для размещения элемента управления WinRT XAML, см. в следующих файлах кода:

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

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

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

Примечание.

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

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

  2. В проекте упаковки щелкните правой кнопкой мыши узел Приложения и выберите команду Добавить ссылку. В списке проектов выберите проект классического приложения C++ и нажмите кнопку ОК.

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

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

Следующие шаги

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

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

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

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

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

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

Дополнительные сведения о работе с такими сценариями и связанные примеры кода см. в статье Расширенные сценарии использования XAML Islands в классических приложениях C++.