Controles personalizados XAML (modelos) com C++/WinRTXAML custom (templated) controls with C++/WinRT

Importante

Para ver conceitos e termos essenciais que ajudam a entender como utilizar e criar classes de runtime com C++/WinRT, confira Utilizar APIs com C++/WinRT e Criar APIs com C++/WinRT.For essential concepts and terms that support your understanding of how to consume and author runtime classes with C++/WinRT, see Consume APIs with C++/WinRT and Author APIs with C++/WinRT.

Um dos recursos mais poderosos da Plataforma Universal do Windows (UWP) é a flexibilidade que a pilha da interface do usuário (IU) fornece para criar controles personalizados com base no tipo Control de XAML.One of the most powerful features of the Universal Windows Platform (UWP) is the flexibility that the user-interface (UI) stack provides to create custom controls based on the XAML Control type. A estrutura de interface do usuário do XAML fornece recursos como propriedades de dependência personalizadas, propriedades anexadas e modelos de controle, que facilitam a criação de controles com recursos sofisticados e personalizáveis.The XAML UI framework provides features such as custom dependency properties and attached properties, and control templates, which make it easy to create feature-rich and customizable controls. Este tópico o orienta pelas etapas de criação de um controle personalizado (modelo) com C++/WinRT.This topic walks you through the steps of creating a custom (templated) control with C++/WinRT.

Criar um aplicativo em branco (BgLabelControlApp)Create a Blank App (BgLabelControlApp)

Comece criando um novo projeto no Microsoft Visual Studio.Begin by creating a new project in Microsoft Visual Studio. Crie um projeto Aplicativo em Branco (C++/WinRT) e defina o nome dele como BgLabelControlApp e (para que sua estrutura de pasta corresponda ao passo a passo) verifique se a opção Colocar a solução e o projeto no mesmo diretório está desmarcada.Create a Blank App (C++/WinRT) project, set its name to BgLabelControlApp, and (so that your folder structure will match the walkthrough) make sure that Place solution and project in the same directory is unchecked. Direcione a versão mais recente em disponibilidade geral (ou seja, que não esteja em versão prévia) do SDK do Windows.Target the latest generally-available (that is, not preview) version of the Windows SDK.

Em uma seção posterior deste tópico, você será direcionado para criar seu projeto (mas não o crie até lá).In a later section of this topic, you'll be directed to build your project (but don't build until then).

Observação

Para saber mais sobre como configurar o Visual Studio para desenvolvimento em C++/WinRT, incluindo instalação e uso da VSIX (Extensão do Visual Studio) para C++/WinRT e o pacote NuGet (que juntos fornecem um modelo de projeto e suporte ao build), confira Suporte ao Visual Studio para C++/WinRT.For info about setting up Visual Studio for C++/WinRT development—including installing and using the C++/WinRT Visual Studio Extension (VSIX) and the NuGet package (which together provide project template and build support)—see Visual Studio support for C++/WinRT.

Vamos criar uma nova classe para representar um controle personalizado (modelo).We're going to author a new class to represent a custom (templated) control. Criaremos e utilizaremos a classe dentro da mesma unidade de compilação.We're authoring and consuming the class within the same compilation unit. Entretanto, queremos poder instanciar essa classe pela marcação do XAML e, por isso, ela será uma classe de runtime.But we want to be able to instantiate this class from XAML markup, and for that reason it's going to be a runtime class. E usaremos o C++/WinRT para criar e usar.And we're going to use C++/WinRT to both author and consume it.

A primeira etapa na criação de uma nova classe de runtime é adicionar um novo item Midl File (.idl) ao projeto.The first step in authoring a new runtime class is to add a new Midl File (.idl) item to the project. Nomeie-o como BgLabelControl.idl.Name it BgLabelControl.idl. Exclua o conteúdo padrão do BgLabelControl.idl e cole esta declaração de classe de runtime.Delete the default contents of BgLabelControl.idl, and paste in this runtime class declaration.

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

A listagem acima mostra o padrão a seguir ao declarar uma propriedade de dependência (DP).The listing above shows the pattern that you follow when declaring a dependency property (DP). Há duas partes para cada DP.There are two pieces to each DP. Primeiro, declare uma propriedade estática somente leitura do tipo DependencyProperty.First, you declare a read-only static property of type DependencyProperty. Ela tem o nome da sua DP e Property.It has the name of your DP plus Property. Você usará essa propriedade estática em sua implementação.You'll use this static property in your implementation. Em seguida, declare uma propriedade de instância de leitura/gravação com o tipo e o nome da sua DP.Second, you declare a read-write instance property with the type and name of your DP. Se você quiser criar uma propriedade anexada (ao invés de uma DP), confira os exemplos de código Propriedades anexadas personalizadas.If you wish to author an attached property (rather than a DP), then see the code examples in Custom attached properties.

Observação

Se quiser uma DP com um tipo de ponto flutuante, crie-a como double (Double no MIDL 3.0).If you want a DP with a floating-point type, then make it double (Double in MIDL 3.0). Declarar e implementar uma DP do tipo float (Single em MIDL) e, em seguida, definir um valor para essa DP na marcação XAML resulta no erro Falha ao criar “Windows.Foundation.Single” do texto “ .Declaring and implementing a DP of type float (Single in MIDL), and then setting a value for that DP in XAML markup, results in the error Failed to create a 'Windows.Foundation.Single' from the text ''.

Salve o arquivo.Save the file. O projeto não será criado até a conclusão, mas é útil criá-lo agora porque ele gera os arquivos de código-fonte nos quais você implementará a classe de runtime BgLabelControl.The project won't build to completion at the moment, but building now is a useful thing to do because it generates the source code files in which you'll implement the BgLabelControl runtime class. Portanto, vá em frente e compile agora (os erros de build que você espera ver neste estágio têm a ver com um "símbolo externo não resolvido").So go ahead and build now (the build errors you can expect to see at this stage have to do with an "unresolved external symbol").

Durante o processo de compilação, a ferramenta midl.exe é executada para criar um arquivo de metadados do Windows Runtime (\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd), descrevendo a classe de tempo de execução.During the build process, the midl.exe tool is run to create a Windows Runtime metadata file (\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd) describing the runtime class. Em seguida, a ferramenta cppwinrt.exe é executada para gerar arquivos de código fonte para dar suporte à criação e utilização da classe de runtime.Then, the cppwinrt.exe tool is run to generate source code files to support you in authoring and consuming your runtime class. Esses arquivos incluem stubs para ajudá-lo a começar a implementar a classe de runtime BgLabelControl, que foi declarada em seu IDL.These files include stubs to get you started implementing the BgLabelControl runtime class that you declared in your IDL. Esses stubs são \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.h e BgLabelControl.cpp.Those stubs are \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.h and BgLabelControl.cpp.

Copie os arquivos stub BgLabelControl.h e BgLabelControl.cpp de \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ para a pasta do projeto, que é \BgLabelControlApp\BgLabelControlApp\.Copy the stub files BgLabelControl.h and BgLabelControl.cpp from \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ into the project folder, which is \BgLabelControlApp\BgLabelControlApp\. No Gerenciador de Soluções, verifique se Mostrar Todos os Arquivos está ativado.In Solution Explorer, make sure Show All Files is toggled on. Clique com o botão direito do mouse nos arquivos de stub copiados e clique em Incluir no Projeto.Right-click the stub files that you copied, and click Include In Project.

Você verá um static_assert na parte superior de BgLabelControl.h e BgLabelControl.cpp, que você precisará remover.You'll see a static_assert at the top of BgLabelControl.h and BgLabelControl.cpp, which you'll need to remove. Agora o projeto será criado.Now the project will build.

Implementar a classe personalizada BgLabelControlImplement the BgLabelControl custom control class

Agora vamos abrir \BgLabelControlApp\BgLabelControlApp\BgLabelControl.h e BgLabelControl.cpp, e implementar nossa classe de runtime.Now, let's open \BgLabelControlApp\BgLabelControlApp\BgLabelControl.h and BgLabelControl.cpp and implement our runtime class. Em BgLabelControl.h, altere o construtor para definir a chave de estilo padrão, implemente Label e LabelProperty, adicione um manipulador de evento estático chamado OnLabelChanged para processar as alterações no valor da propriedade de dependência e adicione um membro privado para armazenar o campo de suporte para LabelProperty.In BgLabelControl.h, change the constructor to set the default style key, implement Label and LabelProperty, add a static event handler named OnLabelChanged to process changes to the value of the dependency property, and add a private member to store the backing field for LabelProperty.

Após a inclusão desses controles, BgLabelControl.h terá a seguinte aparência.After adding those, your BgLabelControl.h looks like this. Você pode copiar e colar esta listagem de código para substituir o conteúdo de BgLabelControl.h.You can copy and paste this code listing to replace the contents of BgLabelControl.h.

// 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 Windows::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }

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

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

Em BgLabelControl.cpp, defina os membros estáticos da seguinte maneira.In BgLabelControl.cpp, define the static members like this. Você pode copiar e colar esta listagem de código para substituir o conteúdo de BgLabelControl.cpp.You can copy and paste this code listing to replace the contents of BgLabelControl.cpp.

// BgLabelControl.cpp
#include "pch.h"
#include "BgLabelControl.h"
#include "BgLabelControl.g.cpp"

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

    void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::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.
        }
    }
}

Neste passo a passo, nós não utilizaremos OnLabelChanged.In this walkthrough, we won't be using OnLabelChanged. Entretanto, ele continua lá para que você possa ver como registrar uma propriedade de dependência com um retorno de chamada de propriedade alterada.But it's there so that you can see how to register a dependency property with a property-changed callback. A implementação de OnLabelChanged também mostra como obter um tipo projetado derivado de um tipo projetado básico (que é o tipo DependencyObject nesse caso).The implementation of OnLabelChanged also shows how to obtain a derived projected type from a base projected type (the base projected type is DependencyObject, in this case). E mostra como obter um ponteiro para o tipo que implementa o tipo projetado.And it shows how to then obtain a pointer to the type that implements the projected type. 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).That second operation will naturally only be possible in the project that implements the projected type (that is, the project that implements the runtime class).

Observação

Se você ainda não instalou o SDK do Windows, versão 10.0.17763.0 (Windows 10, versão 1809) ou posterior, precisará chamar winrt::from_abi no manipulador de evento da propriedade alterada de dependência ao invés de winrt::get_self.If you haven't installed the Windows SDK version 10.0.17763.0 (Windows 10, version 1809), or later, then you need to call winrt::from_abi in the dependency property changed event handler above, instead of winrt::get_self.

Definir o estilo padrão de design para BgLabelControlDesign the default style for BgLabelControl

Em seu construtor, BgLabelControl define uma chave de estilo padrão para si mesmo.In its constructor, BgLabelControl sets a default style key for itself. O que é um estilo padrão?But what is a default style? Um controle personalizado (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.A custom (templated) control needs to have a default style—containing a default control template—which it can use to render itself with in case the consumer of the control doesn't set a style and/or template. Nesta seção, adicionaremos um arquivo de marcação ao projeto que contém nosso estilo padrão.In this section we'll add a markup file to the project containing our default style.

Verifique se Mostrar Todos os Arquivos ainda está ativado (no Gerenciador de Soluções).Make sure that Show All Files is still toggled on (in Solution Explorer). No nó do projeto, crie uma pasta (não um filtro, mas uma pasta) e dê a ela o nome "Temas".Under your project node, create a new folder (not a filter, but a folder) and name it "Themes". Em Themes, adicione um novo item do tipo Visual C++ > XAML > Exibição XAML e nomeie-o como "Generic.xaml".Under Themes, add a new item of type Visual C++ > XAML > XAML View, and name it "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 personalizado.The folder and file names have to be like this in order for the XAML framework to find the default style for a custom control. Exclua o conteúdo padrão do Generic.xaml e cole na marcação abaixo.Delete the default contents of Generic.xaml, and paste in the markup below.

<!-- \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.In this case, the only property that the default style sets is the control template. 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).The template consists of a square (whose background is bound to the Background property that all instances of the XAML Control type have), and a text element (whose text is bound to the BgLabelControl::Label dependency property).

Adicionar uma instância de BgLabelControl à página da interface do usuário principalAdd an instance of BgLabelControl to the main UI page

Abra MainPage.xaml, que contém a marcação XAML para a página principal da interface do usuário.Open MainPage.xaml, which contains the XAML markup for our main UI page. Imediatamente após o elemento Button (dentro de StackPanel), adicione a seguinte marcação.Immediately after the Button element (inside the StackPanel), add the following markup.

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

Além disso, adicione a seguinte diretiva de inclusão à MainPage.h para que o tipo MainPage (uma combinação de marcação de compilação de XAML e código imperativo) esteja ciente do tipo de controle personalizado BgLabelControl.Also, add the following include directive to MainPage.h so that the MainPage type (a combination of compiling XAML markup and imperative code) is aware of the BgLabelControl custom control type. 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.If you want to use BgLabelControl from another XAML page, then add this same include directive to the header file for that page, too. Ou, você pode apenas colocar uma única diretiva de inclusão em seu arquivo de cabeçalho pré-compilado.Or, alternatively, just put a single include directive in your precompiled header file.

// MainPage.h
...
#include "BgLabelControl.h"
...

Agora compile e execute o projeto.Now build and run the project. 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.You'll see that the default control template is binding to the background brush, and to the label, of the BgLabelControl instance in the markup.

Este passo a passo mostrou um exemplo simples de um controle personalizado (modelo) no C++/WinRT.This walkthrough showed a simple example of a custom (templated) control in C++/WinRT. Você pode criar seus próprios controles personalizados avançados e completos.You can make your own custom controls arbitrarily rich and full-featured. Por exemplo, um controle personalizado pode assumir a forma de algo mais complicado, como uma grade de dados editável, um player de vídeo ou um visualizador de geometria 3D.For example, a custom control can take the form of something as complicated as an editable data grid, a video player, or a visualizer of 3D geometry.

Implementando funções substituíveis, como MeasureOverride e OnApplyTemplateImplementing overridable functions, such as MeasureOverride and OnApplyTemplate

É possível derivar um controle personalizado da classe de runtime Control, que por si só deriva de classes básicas de runtime.You derive a custom control from the Control runtime class, which itself further derives from base runtime classes. E há métodos substituíveis de Control, FrameworkElement, e UIElement que você pode substituir em sua classe derivada.And there are overridable methods of Control, FrameworkElement, and UIElement that you can override in your derived class. Aqui está um exemplo de código mostrando como você pode fazer isso.Here's a code example showing you how to do that.

struct BgLabelControl : BgLabelControlT<BgLabelControl>
{
...
    // Control overrides.
    void OnPointerPressed(Windows::UI::Xaml::Input::PointerRoutedEventArgs const& /* e */) const { ... };

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

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

Funções substituíveis apresentam-se diferentemente em projeções de linguagem diferentes.Overridable functions present themselves differently in different language projections. Em C#, por exemplo, as funções substituíveis normalmente são exibidas como funções virtuais protegidas.In C#, for example, overridable functions typically appear as protected virtual functions. 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.In C++/WinRT, they're neither virtual nor protected, but you can still override them and provide your own implementation, as shown above.

APIs importantesImportant APIs