Hospedar um controle WinRT XAML padrão em um aplicativo de área de trabalho em C++ (Win32)

Importante

Este tópico usa ou menciona tipos do repositório GitHub CommunityToolkit/Microsoft.Toolkit.Win32. Para saber mais sobre o suporte a ilhas XAML, confira o Aviso de ilhas XAML nesse repositório.

Este artigo mostra como usar a API de hospedagem XAML do WinRT para hospedar um controle XAML do WinRT padrão (ou seja, um controle fornecido pelo SDK do Windows) em um novo aplicativo da área de trabalho C++. O código se baseia na amostra de Ilha XAML simples, e esta seção aborda algumas das partes mais importantes do código. Se você já tiver um projeto de aplicativo da área de trabalho C++, poderá adaptar estas etapas e estes exemplos de código para ele.

Observação

O cenário demonstrado neste artigo não dá suporte à edição direta de marcação XAML em controles WinRT XAML hospedados no seu aplicativo. Esse cenário impede você de modificar a aparência e o comportamento de controles hospedados por meio de código. Para obter instruções que permitam editar diretamente a marcação XAML durante a hospedagem de controles WinRT XAML, confira Hospedar um controle WinRT XAML personalizado em um aplicativo da área de trabalho C++ .

Criar um projeto de aplicativo da área de trabalho

  1. No Visual Studio 2019 com o SDK do Windows 10 versão 1903 (build 10.0.18362) ou em uma versão posterior instalada, crie um projeto do Aplicativo da Área de Trabalho do Windows. Além disso, nomeie esse projeto como MyDesktopWin32App. Esse tipo de projeto está disponível nos filtros de projeto C++ , Windows e Área de Trabalho.

  2. No Gerenciador de Soluções, clique com o botão direito do mouse no nó da solução, clique em Redirecionar solução, selecione 10.0.18362.0 ou uma versão posterior do SDK e, em seguida, clique em OK.

  3. Instale o pacote NuGet Microsoft.Windows.CppWinRT para habilitar o suporte para o C++/WinRT no projeto:

    1. Clique com o botão direito do mouse em seu projeto no Gerenciador de Soluções e escolha Gerenciar Pacotes NuGet.
    2. Selecione a guia Procurar, procure o pacote Microsoft.Windows.CppWinRT e instale a última versão desse pacote.

    Observação

    Para novos projetos, como alternativa, você pode instalar o VSIX (Extensão do Visual Studio C++/WinRT) e usar um dos modelos de projeto C++/WinRT incluídos nessa extensão. Para obter mais informações, confira Suporte do Visual Studio para C++/WinRT e o VSIX.

  4. Na guia Procurar da janela Gerenciador de Pacotes NuGet, procure o pacote NuGet Microsoft.Toolkit.Win32.UI.SDK e instale a última versão estável dele. Esse pacote fornece vários ativos de build e runtime que permitem que as Ilhas XAML funcionem no seu aplicativo.

  5. Defina o valor maxversiontested no manifesto do aplicativo para especificar que o aplicativo é compatível com o Windows 10 versão 1903 ou posterior.

    1. Caso você ainda não tenha um manifesto do aplicativo no projeto, adicione um novo arquivo XML ao projeto e dê a ele o nome app.manifest.

    2. No manifesto do aplicativo, inclua o elemento compatibility e os elementos filho mostrados no exemplo a seguir. Substitua o atributo ID do elemento maxversiontested pelo número de versão do Windows que você está direcionando (a versão deverá ser 10.0.18362.0 ou posterior). Observe que definir um valor mais alto significa que as versões anteriores do Windows não executarão o aplicativo de modo correto porque cada versão do Windows reconhece somente as versões anteriores. Caso queira que o aplicativo seja executado no Windows 10 versão 1903 (build 10.0.18362), será preciso manter o valor 10.0.18362.0 como está ou adicionar vários elementos maxversiontested em diferentes valores em que o aplicativo é compatível.

      <?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. Adicione uma referência aos metadados do Windows Runtime:

    1. No Gerenciador de Soluções, clique com o botão direito do mouse no nó Referências do seu projeto e selecione Adicionar Referência.
    2. Clique no botão Procurar na parte inferior da página e navegue até a pasta UnionMetadata no caminho de instalação do SDK. Por padrão, o SDK será instalado em C:\Program Files (x86)\Windows Kits\10\UnionMetadata.
    3. Em seguida, selecione a pasta nomeada com a versão do Windows que você está direcionando (por exemplo, 10.0.18362.0) e, dentro dessa pasta, escolha o arquivo de Windows.winmd.
    4. Clique em OK para fechar a caixa de diálogo Adicionar Referência.

Usar a API de hospedagem XAML para hospedar um controle WinRT XAML

O processo básico de usar a API de hospedagem XAML para hospedar um controle WinRT XAML segue estas etapas gerais:

  1. Inicialize a estrutura XAML do WinRT para o thread atual antes que o aplicativo crie um dos objetos Windows.UI.Xaml.UIElement que ele hospedará. Há várias maneiras de fazer isso, dependendo de quando você planeja criar o objeto DesktopWindowXamlSource que hospedará os controles.

    • Se o aplicativo criar o objeto DesktopWindowXamlSource antes de criar um dos objetos Windows.UI.Xaml.UIElement que ele hospedará, essa estrutura será inicializada para você ao criar uma instância do objeto DesktopWindowXamlSource. Nesse cenário, você não precisa adicionar nenhum código próprio para inicializar a estrutura.

    • No entanto, se o aplicativo criar os objetos Windows.UI.Xaml.UIElement antes de criar o objeto DesktopWindowXamlSource que os hospedará, o aplicativo precisará chamar o método estático WindowsXamlManager.InitializeForCurrentThread para inicializar explicitamente a estrutura XAML do WinRT antes da instanciação dos objetos Windows.UI.Xaml.UIElement. Seu aplicativo normalmente deverá chamar esse método quando for criada uma instância do elemento pai da interface do usuário que hospeda o DesktopWindowXamlSource.

    Observação

    Esse método retorna um objeto WindowsXamlManager que contém uma referência à estrutura XAML do WinRT. Você pode criar quantos objetos WindowsXamlManager desejar em determinado thread. No entanto, como cada objeto contém uma referência à estrutura XAML do WinRT, você deverá descartar os objetos para garantir que os recursos XAML acabem sendo liberados.

  2. Crie um objeto DesktopWindowXamlSource e anexe-o a um elemento pai da interface do usuário no aplicativo associado a um identificador de janela.

    Para fazer isso, você precisará seguir estas etapas:

    1. Crie um objeto DesktopWindowXamlSource e converta-o na interface COM IDesktopWindowXamlSourceNative ou IDesktopWindowXamlSourceNative2.

    2. Chame o método AttachToWindow da interface IDesktopWindowXamlSourceNative ou IDesktopWindowXamlSourceNative2 e transmita o identificador de janela do elemento pai da interface do usuário no aplicativo.

      Importante

      Verifique se seu código chama o método AttachToWindow apenas uma vez por objeto DesktopWindowXamlSource. Chamar esse método mais de uma vez para um objeto DesktopWindowXamlSource pode resultar em perda de memória.

    3. Defina o tamanho inicial da janela filho interna contida no DesktopWindowXamlSource. Por padrão, essa janela filho interna é definida com uma largura e uma altura igual a 0. Se você não definir o tamanho da janela, todos os controles WinRT XAML adicionados ao DesktopWindowXamlSource não estarão visíveis. Para acessar a janela filho interna no DesktopWindowXamlSource, use a propriedade WindowHandle da interface IDesktopWindowXamlSourceNative ou IDesktopWindowXamlSourceNative2.

  3. Por fim, atribua o Windows.UI.Xaml.UIElement que deseja hospedar à propriedade Conteúdo do objeto DesktopWindowXamlSource.

As seguintes etapas e os exemplos de código demonstram como implementar o processo acima:

  1. Na pasta Arquivos de Origem do projeto, abra o arquivo padrão MyDesktopWin32App.cpp. Exclua todo o conteúdo do arquivo e adicione as instruções include e using a seguir. Além dos cabeçalhos e dos namespaces padrão C++ e UWP, essas instruções incluem vários itens específicos das Ilhas XAML.

    #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. Copie o código a seguir após a seção anterior. Esse código define a função WinMain para o aplicativo. Essa função inicializa uma janela básica e usa a API de Hospedagem XAML para hospedar um controle UWP simples TextBlock na janela.

    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. Copie o código a seguir após a seção anterior. Esse código define o procedimento de janela da janela.

    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. Salve o arquivo de código e compile e execute o aplicativo. Confirme se você vê o controle TextBlock UWP na janela do aplicativo.

    Observação

    Você poderá ver os vários avisos de build, incluindo warning C4002: too many arguments for function-like macro invocation 'GetCurrentTime' e manifest authoring warning 81010002: Unrecognized Element "maxversiontested" in namespace "urn:schemas-microsoft-com:compatibility.v1". Esses avisos são problemas conhecidos com as ferramentas e os pacotes NuGet atuais e eles podem ser ignorados.

Para obter exemplos completos que demonstram como usar a API de hospedagem de XAML para hospedar um controle WinRT XAML, confira os seguintes arquivos de código:

Empacotar o aplicativo

Opcionalmente, você pode empacotar o aplicativo em um pacote MSIX para implantação. O MSIX é a tecnologia moderna de empacotamento de aplicativo para o Windows e se baseia em uma combinação das tecnologias de instalação MSI, .appx, App-V e ClickOnce.

As instruções a seguir mostram como empacotar todos os componentes da solução em um pacote MSIX usando o Projeto de Empacotamento de Aplicativo do Windows no Visual Studio 2019. Essas etapas serão necessárias apenas se você quiser empacotar o aplicativo em um pacote MSIX.

Observação

Se você optar por não empacotar seu aplicativo em um pacote MSIX para implantação, os computadores que executam o aplicativo precisarão ter o Runtime do Visual C++ instalado.

  1. Adicione um novo Projeto de Empacotamento de Aplicativo do Windows à solução. Ao criar o projeto, selecione Windows 10, versão 1903 (10.0; Build 18362) para a Versão de destino e a Versão mínima.

  2. No projeto de empacotamento, clique com o botão direito do mouse no nó Aplicativos e escolha Adicionar referência. Na lista de projetos, selecione o projeto de aplicativo da área de trabalho C++ na solução e clique em OK.

  3. Compile e execute o projeto de empacotamento. Confirme se o aplicativo é executado e exibe os controles WinRT XAML conforme esperado.

  4. Para obter informações sobre como distribuir/implantar o pacote, consulte Gerenciar sua implantação MSIX.

Próximas etapas

Os exemplos de código deste artigo ajudarão você a obter uma introdução ao cenário básico de hospedagem de um controle WinRT XAML padrão em um aplicativo da área de trabalho C++. As seções a seguir apresentam outros cenários aos quais o seu aplicativo poderá precisar dar suporte.

Hospedar um controle XAML do WinRT personalizado

Para muitos cenários, talvez você precise hospedar um controle XAML do WinRT personalizado que contenha vários controles individuais funcionando juntos. O processo para hospedar um controle personalizado (ou seja, que você define por conta própria ou fornecido por terceiros) em um aplicativo da área de trabalho em C++ é mais complexo do que hospedar um controle padrão e exige código adicional.

Para obter um passo a passo completo, confira Hospedar um controle WinRT XAML personalizado em um aplicativo da área de trabalho C++ usando a API de hospedagem XAML.

Cenários avançados

Muitos aplicativos da área de trabalho que hospedam as Ilhas XAML precisarão lidar com cenários adicionais para proporcionar uma experiência do usuário estável. Por exemplo, os aplicativos da área de trabalho podem precisar processar a entrada do teclado nas Ilhas XAML, focar a navegação entre as Ilhas XAML e outros elementos de interface do usuário, bem como alterações de layout.

Para obter mais informações sobre como lidar com esses cenários e ponteiros para exemplos de código relacionados, confira Cenários avançados das Ilhas XAML em aplicativos da área de trabalho em C++.