Partilhar via


Criar controles XAML com C++/WinRT

Este artigo orienta você pela criação de um controle XAML com modelo para WinUI 3 com C++/WinRT. Controles com modelo herdam de Microsoft.UI.XAML.Controls.Control e têm estrutura visual e comportamento visual que podem ser personalizados usando modelos de controle XAML. Este artigo descreve o mesmo cenário que o artigo Controles personalizados XAML (modelos) com C++/WinRT, mas foram adaptados para usar o WinUI 3.

Pré-requisitos

  1. Introdução ao WinUI
  2. Baixe e instale a última versão do VSIX (Extensão do Visual Studio) para C++/WinRT

Criar um aplicativo em branco (BgLabelControlApp)

Comece criando um novo projeto no Microsoft Visual Studio. Na caixa de diálogo Create a new project, selecione o modelo de projeto Aplicativo em Branco (WinUI no UWP) , verificando se selecionou a versão de linguagem do C++. Defina o nome do projeto como "BgLabelControlApp" para que os nomes de arquivo sejam alinhados com o código nos exemplos abaixo. Defina a Versão de destino como o Windows 10, versão 1903 (build 18362) e a Versão mínima como o Windows 10, versão 1803 (build 17134). Este passo a passo também funcionará para aplicativos da área de trabalho criados com o modelo de projeto Aplicativo em branco, empacotado (WinUI na Área de Trabalho) , apenas requerendo que você execute todas as etapas no projeto BgLabelControlApp (Área de Trabalho) .

Modelo de Projeto de Aplicativo em Branco

Adicionar um controle com modelo ao aplicativo

Para adicionar um controle com modelo, clique no menu Projeto na barra de ferramentas ou clique com o botão direito do mouse em seu projeto no Gerenciador de Soluções e selecione Adicionar novo item. Em Visual C++ >WinUI, selecione o modelo Controle Personalizado (WinUI). Chame o novo controle de "BgLabelControl" e clique em Adicionar. Isso adicionará três novos arquivos ao projeto. BgLabelControl.h é o cabeçalho que contém as declarações de controle e BgLabelControl.cpp contém a implementação C++/WinRT do controle. BgLabelControl.idl é o arquivo de Definição da Interface que permite ao controle ser instanciado como uma classe de runtime.

Implementar a classe de controle personalizado BgLabelControl

Nas etapas a seguir, você atualizará o código nos arquivos BgLabelControl.idl, BgLabelControl.h e BgLabelControl.cpp do diretório do projeto para implementar a classe de runtime.

A classe de controle com modelo será instanciada pela marcação do XAML e, por esse motivo, ela será uma classe de runtime. Quando você compila o projeto concluído, o compilador MIDL (midl.exe) usará o arquivo BgLabelControl.idl para gerar o arquivo de metadados do Windows Runtime (.winmd) para controle, que será referenciado pelos consumidores do componente. Para obter mais informações sobre como criar classes de runtime, confira as APIs do Author com C++ /WinRT.

O controle com modelo que estamos criando exporá apenas uma propriedade que é uma cadeia de caracteres que será usada como rótulo do controle. Substitua o conteúdo de BgLabelControl.idl pelo código a seguir.

// BgLabelControl.idl
namespace BgLabelControlApp
{
    runtimeclass BgLabelControl : Microsoft.UI.Xaml.Controls.Control
    {
        BgLabelControl();
        static Microsoft.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

A listagem acima mostra o padrão a seguir ao declarar uma propriedade de dependência (DP). Há duas partes para cada DP. Primeiro, declare uma propriedade estática somente leitura do tipo DependencyProperty. Ela tem o nome da sua DP e Property. Você usará essa propriedade estática em sua implementação. Em seguida, declare uma propriedade de instância de leitura/gravação com o tipo e o nome da sua DP. Se você quiser criar uma propriedade anexada (ao invés de uma DP), confira os exemplos de código em Propriedades anexadas personalizadas.

Observe que as classes XAML referenciadas no código acima estão em namespaces Microsoft.UI.XAML. Isso é o que os distingue como controles WinUI, ao contrário dos controles XAML UWP, que são definidos em namespaces Windows.UI.XAML.

Substitua o conteúdo de BgLabelControl.h pelo código a seguir.

// BgLabelControl.h
#pragma once
#include "BgLabelControl.g.h"

namespace winrt::BgLabelControlApp::implementation
{
    struct BgLabelControl : BgLabelControlT<BgLabelControl>
    {
        BgLabelControl() { DefaultStyleKey(winrt::box_value(L"BgLabelControlApp.BgLabelControl")); }

        winrt::hstring Label()
        {
            return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
        }

        void Label(winrt::hstring const& value)
        {
            SetValue(m_labelProperty, winrt::box_value(value));
        }

        static Microsoft::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }

        static void OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const&, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs const&);

    private:
        static Microsoft::UI::Xaml::DependencyProperty m_labelProperty;
    };
}
namespace winrt::BgLabelControlApp::factory_implementation
{
    struct BgLabelControl : BgLabelControlT<BgLabelControl, implementation::BgLabelControl>
    {
    };
}

O código mostrado acima implementa as propriedades Label e LabelProperty, adiciona um manipulador de evento estático chamado OnLabelChanged para processar as alterações no valor da propriedade de dependência e adiciona um membro privado para armazenar o campo de suporte para LabelProperty. Observe novamente que as classes XAML referenciadas no arquivo de cabeçalho estão nos namespaces Microsoft.UI.XAML que pertencem à estrutura WinUI 3 em vez dos namespaces Windows.UI.XAML usados pela estrutura de interface do usuário da UWP.

Em seguida, substitua o conteúdo de BgLabelControl.cpp pelo código a seguir.

// BgLabelControl.cpp
#include "pch.h"
#include "BgLabelControl.h"
#if __has_include("BgLabelControl.g.cpp")
#include "BgLabelControl.g.cpp"
#endif

namespace winrt::BgLabelControlApp::implementation
{
    Microsoft::UI::Xaml::DependencyProperty BgLabelControl::m_labelProperty =
        Microsoft::UI::Xaml::DependencyProperty::Register(
            L"Label",
            winrt::xaml_typename<winrt::hstring>(),
            winrt::xaml_typename<BgLabelControlApp::BgLabelControl>(),
            Microsoft::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Microsoft::UI::Xaml::PropertyChangedCallback{ &BgLabelControl::OnLabelChanged } }
    );

    void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const& d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs const& /* e */)
    {
        if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
        {
            // Call members of the projected type via theControl.

            BgLabelControlApp::implementation::BgLabelControl* ptr{ winrt::get_self<BgLabelControlApp::implementation::BgLabelControl>(theControl) };
            // Call members of the implementation type via ptr.
        }
    }
}

Este passo a passo não usa o retorno de chamada OnLabelChanged, mas ele é fornecido para que você possa ver como registrar uma propriedade de dependência com um retorno de chamada de propriedade alterada. A implementação de OnLabelChanged também mostra como obter um tipo projetado derivado de um tipo projetado básico (que nesse caso é o tipo DependencyObject). E mostra como obter um ponteiro para o tipo que implementa o tipo projetado. Naturalmente, essa segunda operação só é possível no projeto que implementa o tipo projetado (ou seja, o projeto que implementa a classe de runtime).

A função xaml_typename é fornecida pelo namespace Windows.UI.XAML.Interop que não está incluído por padrão no modelo de projeto WinUI 3. Adicione uma linha ao arquivo de cabeçalho pré-compilado para seu projeto, pch.h, para incluir o arquivo de cabeçalho associado a esse namespace.

// pch.h
...
#include <winrt/Windows.UI.Xaml.Interop.h>
...

Definir o estilo padrão para BgLabelControl

Em seu construtor, BgLabelControl define uma chave de estilo padrão para si mesmo. Um controle com modelo precisa ter um estilo padrão (com um modelo de controle padrão), que pode ser usado para renderizar a si mesmo no caso do consumidor do controle não definir um estilo e/ou modelo. Nesta seção, adicionaremos um arquivo de marcação ao projeto que contém nosso estilo padrão.

Verifique se Mostrar Todos os Arquivos ainda está ativado (no Gerenciador de Soluções). No nó do projeto, crie uma pasta (não um filtro, mas uma pasta) e dê a ela o nome "Temas". Em Themes, adicione um novo item do tipo Visual C++ > WinUI > Dicionário de recursos (WinUI) e dê a ele o nome de "Generic.xaml". Os nomes de pasta e arquivo precisam ser dessa maneira para que a estrutura XAML localize o estilo padrão para um controle com modelo. Exclua o conteúdo padrão do Generic.xaml e cole a marcação abaixo.

<!-- \Themes\Generic.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BgLabelControlApp">

    <Style TargetType="local:BgLabelControl" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:BgLabelControl">
                    <Grid Width="100" Height="100" Background="{TemplateBinding Background}">
                        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{TemplateBinding Label}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Nesse caso, a única propriedade que o estilo padrão define é o modelo de controle. O modelo consiste em um quadrado (cujo plano de fundo está associado à propriedade Background que todas as instâncias do tipo Control de XAML têm) e um elemento de texto (cujo texto está associado à propriedade de dependência BgLabelControl::Label).

Adicionar uma instância de BgLabelControl à página da interface do usuário principal

Abra MainWindow.xaml, que contém a marcação XAML para a página principal da interface do usuário. Imediatamente após o elemento Button (dentro de StackPanel), adicione a seguinte marcação.

<local:BgLabelControl Background="Red" Label="Hello, World!"/>

Além disso, adicione a diretiva de inclusão a seguir a MainWindow.h para que o tipo MainWindow (uma combinação de marcação XAML de compilação e código imperativo) esteja ciente do tipo de controle com modelo BgLabelControl. Se você quiser usar BgLabelControl de outra página XAML, adicione essa mesma diretiva de inclusão ao arquivo de cabeçalho para essa página também. Ou, você pode apenas colocar uma única diretiva de inclusão em seu arquivo de cabeçalho pré-compilado.

//MainWindow.h
...
#include "BgLabelControl.h"
...

Agora compile e execute o projeto. Você verá que o modelo de controle padrão está associado ao pincel do plano de fundo e ao rótulo da instância BgLabelControl na marcação.

Resultado do controle com modelo

Como implementar funções substituíveis, como MeasureOverride e OnApplyTemplate

É possível derivar um controle personalizado da classe de runtime Control, que por si só deriva de classes básicas de runtime. E há métodos substituíveis de Control, FrameworkElement e UIElement, que você pode substituir em sua classe derivada. Aqui está um exemplo de código mostrando como você pode fazer isso.

// Control overrides.
void OnPointerPressed(Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& /* e */) const { ... };

// FrameworkElement overrides.
Windows::Foundation::Size MeasureOverride(Windows::Foundation::Size const& /* availableSize */) const { ... };
void OnApplyTemplate() const { ... };

// UIElement overrides.
Microsoft::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer() const { ... };

Funções substituíveis apresentam-se diferentemente em projeções de linguagem diferentes. Em C#, por exemplo, as funções substituíveis normalmente são exibidas como funções virtuais protegidas. Em C++/WinRT, elas não são virtuais nem protegidas, mas é possível substituí-las e fornecer sua própria implementação, conforme mostrado acima.

Geração dos arquivos de origem do controle sem usar um modelo.

Esta seção mostra como você pode gerar os arquivos de origem necessários para criar seu controle personalizado sem usar o modelo de item do Controle Personalizado.

Primeiro, adicione um novo item de Arquivo Midl (.idl) ao projeto. No menu Projeto, selecione Adicionar Novo Item... e digite "MIDL" na caixa de pesquisa para localizar o item de arquivo .idl. Nomeie o novo arquivo BgLabelControl.idl para que o nome seja consistente com as etapas neste artigo. Exclua o conteúdo padrão de BgLabelControl.idl e cole nele a declaração da classe runtime mostrada nas etapas acima.

Depois de salvar o novo arquivo .idl, a próxima etapa é gerar o arquivo de metadados do Windows Runtime ( .winmd) e stubs para os arquivos de implementação .cpp e .h que serão usados para implementar o controle com modelo. Gere esses arquivos criando a solução, o que fará com que o compilador MIDL (midl.exe) compile o arquivo .idl que você criou. Observe que a solução não será compilada com êxito e o Visual Studio mostrará erros de build na janela de saída, mas os arquivos necessários serão gerados.

Copie os arquivos de stub BgLabelControl.h e BgLabelControl.cpp de \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ para a pasta do projeto. No Gerenciador de Soluções, verifique se Mostrar Todos os Arquivos está ativado. Clique com o botão direito do mouse nos arquivos de stub copiados e clique em Incluir no Projeto.

O compilador coloca uma linha static_assert na parte superior de BgLabelControl.h e BgLabelControl.cpp para impedir que os arquivos gerados sejam compilados. Ao implementar o controle, você deve remover essas linhas dos arquivos que você colocou no diretório do projeto. Para esta explicação, você pode simplesmente substituir todo o conteúdo dos arquivos pelo código fornecido acima.

Confira também