Controles (basados en modelo) personalizados de XAML con C++/WinRT

Importante

Para conocer los conceptos y términos esenciales que te ayuden a entender cómo consumir y crear clases en tiempo de ejecución con C++/WinRT, consulta Consumir API con C++/WinRT y Crear API con C++/WinRT .

Una de las características más eficaces de la plataforma Universal de Windows (UWP) es la flexibilidad que ofrece la pila de la interfaz de usuario (UI) para crear controles personalizados basados en el tipo Control de XAML. El marco de interfaz de usuario de XAML ofrece características tales como propiedades de dependencia personalizadas y propiedades adjuntas, así como plantillas de control, que facilitan la creación de controles repletos de características y personalizables. Este tema te guía por los pasos de creación de un control (basado en modelo) personalizado con C++/WinRT.

Crear una aplicación en blanco (BgLabelControlApp)

Comienza creando un proyecto en Microsoft Visual Studio Crea un proyecto de Aplicación en blanco (C++/WinRT) , establece el nombre en BgLabelControlApp y, para que la estructura de carpetas coincida con el tutorial, asegúrate de que la opción Colocar la solución y el proyecto en el mismo directorio esté desactivada. Elija como destino la versión más reciente disponible de manera general (es decir, no en versión preliminar) de Windows SDK.

En una sección posterior de este tema, se te indicará que compiles el proyecto (pero no lo compiles hasta entonces).

Nota

Para obtener más información sobre cómo instalar Visual Studio para la implementación de C++/WinRT incluida la instalación y el uso de la Extensión de Visual Studio (VSIX) para C++/WinRT y el paquete NuGet (que juntos proporcionan la plantilla de proyecto y compatibilidad de la compilación), consulte Compatibilidad de Visual Studio para C++/WinRT.

Vamos a crear una nueva clase que represente un control (basado en modelo) personalizado. Vamos a crear y consumir la clase dentro de la misma unidad de compilación. Pero queremos poder crear instancias de esta clase desde XAML y por ello tiene que ser una clase en tiempo de ejecución. Y vamos a usar C++/WinRT tanto para crearla como para consumirla.

El primer paso para crear una clase en tiempo de ejecución es agregar un nuevo elemento Archivo MIDL (.idl) al proyecto. Asígnale el nombre BgLabelControl.idl. Elimina el contenido predeterminado de BgLabelControl.idl y pégalo en esta declaración de clase en tiempo de ejecución.

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

La lista anterior muestra el patrón que se sigue al declarar una propiedad de dependencia (DP). Hay dos partes en cada DP. En primer lugar, se declara una propiedad estática de solo lectura de tipo DependencyProperty. Tiene el nombre de la DP más Property. Usarás esta propiedad estática en la implementación. En segundo lugar, se declara una propiedad de instancia de lectura y escritura con el tipo y el nombre del DP. Si quieres crear una propiedad adjunta (en lugar de una DP), consulta los ejemplos de código en Propiedades adjuntas personalizadas.

Nota

Si quieres una DP con un tipo de punto flotante, conviértela en double (Double en MIDL 3.0). Si se declara e implementa una DP de tipo float (Single en MIDL) y luego se establece un valor para esa DP en marcado XAML, se producirá el error No se pudo crear un elemento "Windows.Foundation.Single" a partir del texto "<NUMBER>".

Guarde el archivo. El proyecto no se compilará totalmente en este momento, pero compilar ahora resulta útil porque genera los archivos de código fuente en el que implementará la clase en tiempo de ejecución BgLabelControl. Así que puede compilar ahora. Los errores de compilación que puede esperar en esta fase estarán relacionados con un "símbolo externo no resuelto".

Durante el proceso de compilación, la herramienta midl.exe se ejecuta para crear un archivo de metadatos de Windows Runtime (\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd) que describe la clase en tiempo de ejecución. Después se ejecutará la herramienta cppwinrt.exe para generar archivos de código fuente y ayudarte a crear y consumir tu clase en tiempo de ejecución. Estos archivos incluyen códigos auxiliares para que puedas empezar a implementar la clase en tiempo de ejecución BgLabelControl que declaraste en el archivo IDL. Estos archivos de código auxiliar son \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.h y BgLabelControl.cpp

Copia los archivos de código auxiliar BgLabelControl.h y BgLabelControl.cpp de \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ a la carpeta del proyecto, que es \BgLabelControlApp\BgLabelControlApp\. En el Explorador de soluciones, asegúrate de que Mostrar todos los archivos esté activado. Haz clic con el botón derecho en los archivos de código auxiliar que has copiado y luego haz clic en Incluir en el proyecto.

Verá static_assert en la parte superior de BgLabelControl.h y BgLabelControl.cpp, que deberás quitar. Ahora se compilará el proyecto.

Implementar la clase de control personalizado BgLabelControl

Ahora, vamos a abrir \BgLabelControlApp\BgLabelControlApp\BgLabelControl.h y BgLabelControl.cpp e implementar nuestra clase en tiempo de ejecución. En BgLabelControl.h, cambia el constructor para establecer la clave de estilo predeterminado, implementa Label y LabelProperty, agrega un controlador de eventos estáticos de nombre OnLabelChanged que procese los cambios de valor de la propiedad de dependencia y agrega un miembro privado que almacene el campo de respaldo para LabelProperty.

Una vez agregados, tu BgLabelControl.h tendrá este aspecto. Puedes copiar y pegar esta lista de código para reemplazar el contenido de 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>
    {
    };
}

En BgLabelControl.cpp, define los miembros estáticos de la manera siguiente. Puedes copiar y pegar esta lista de código para reemplazar el contenido de 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.
        }
    }
}

En este tutorial, no se usará OnLabelChanged. Pero está ahí para que puedas ver cómo se registra una propiedad de dependencia con una devolución de llamada modificada por propiedades. La implementación de OnLabelChanged también muestra cómo obtener un tipo proyectado derivado a partir de un tipo proyectado base (en este caso, el tipo proyectado base es DependencyObject). Además, muestra cómo obtener un puntero al tipo que implementa el tipo proyectado. Esa segunda operación solo será de por sí posible en el proyecto que implemente el tipo proyectado (es decir, el proyecto que implemente la clase de tiempo de ejecución).

Nota

Si no has instalado Windows SDK versión 10.0.17763.0 (Windows 10, versión 1809) o posterior, tendrás que llamar a winrt::from_abi en el controlador de eventos de cambio de la propiedad de la dependencia anterior, en lugar de a winrt::get_self.

Diseñar el estilo predeterminado para BgLabelControl

En su constructor, BgLabelControl establece una clave de estilo predeterminado para sí mismo. Pero, ¿qué es un estilo predeterminado? Un control personalizado con plantilla ha de tener un estilo predeterminado (que contenga una plantilla de control predeterminado) que pueda usar para representarse a sí mismo en caso de que el usuario del control no establezca un estilo o una plantilla. En esta sección vamos a agregar un archivo de marcado al proyecto que contiene el estilo predeterminado.

Asegúrate de que Mostrar todos los archivos esté activado (en el Explorador de soluciones). En el nodo del proyecto, crea una carpeta (no un filtro, sino una carpeta) y asígnale el nombre "Themes". En Themes, agrega un nuevo elemento de tipo Visual C++>XAML>Vista XAML y asígnale el nombre "Generic.xaml". Los nombres de carpeta y archivo deben ser similares a este para que el marco XAML encuentre el estilo predeterminado para un control personalizado. Elimina el contenido predeterminado de Generic.xaml y pégalo en el marcado siguiente.

<!-- \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>

En este caso, la única propiedad que el estilo predeterminado establece es la plantilla de control. La plantilla consta de un cuadrado (cuyo fondo está enlazado a la propiedad Background que todas las instancias del tipo Control de XAML tienen) y un elemento de texto (cuyo texto está enlazado a la propiedad de dependencia BgLabelControl::Label).

Agregar una instancia de BgLabelControl a la página principal de la interfaz de usuario

Abre MainPage.xaml, que contiene el marcado XAML de nuestra página principal de la interfaz de usuario. Inmediatamente después del elemento Button (dentro de la clase StackPanel), agrega el marcado siguiente.

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

Además, agrega la siguiente directiva Include a MainPage.h para que el tipo MainPage (una combinación de compilación de marcado XAML y código imperativo) tenga constancia del tipo de control personalizado BgLabelControl. Si quieres usar BgLabelControl desde otra página XAML, agrega también esta misma directiva Include al archivo de encabezado de esa página. O bien, puedes poner una sola directiva Include en el archivo de encabezado precompilado.

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

Ahora compila y ejecuta el proyecto. Verás que la plantilla de control predeterminado está enlazada al pincel de fondo y a la etiqueta de la instancia de BgLabelControl en el marcado.

Este tutorial ha mostrado un ejemplo sencillo de un control (basado en modelo) personalizado en C++/WinRT. Puedes crear tus propios controles personalizados enriquecidos y con todas las funciones de forma arbitraria. Por ejemplo, un control personalizado puede adoptar la forma de algo tan complicado como una cuadrícula de datos editable, un reproductor de vídeo o un visualizador de geometría 3D.

Implementación de métodos reemplazables, como MeasureOverride y OnApplyTemplate

Consulte la sección Llamada y invalidación al escribir la base con C++/WinRT.

API importantes