Controles de XAML; enlazar a una propiedad de C++/WinRT
Artículo
Una propiedad que se puede enlazar de forma eficaz a un control de XAML se conoce como una propiedad observable. Esta idea se basa en el patrón de diseño de software conocido como patrón observador. En este tema se muestra cómo implementar propiedades observables en C++/WinRT y cómo enlazar controles de elementos XAML a dichas propiedades (para obtener información general, consulta Enlace de datos).
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 .
¿Qué significa observable con respecto a una propiedad?
Supongamos que una clase en tiempo de ejecución denominada BookSku tiene una propiedad denominada Title. Si BookSku genera el evento INotifyPropertyChanged::PropertyChanged siempre que el valor de Title cambia, significa que la propiedad Title será de tipo observable. Es el comportamiento de BookSku (generar o no generar el evento) que determina cuáles de sus propiedades, si las hay, son observables.
Un control o elemento de texto XAML puede enlazarse a estos eventos y controlarlos. Dicho elemento o control administra el evento. Para ello, recupera los valores actualizados y, luego, se actualiza automáticamente para mostrar el nuevo valor.
Nota
Para más información sobre cómo instalar y usar la Extensión de Visual Studio (VSIX) para C++/WinRT y el paquete de NuGet (que juntos proporcionan la plantilla de proyecto y compatibilidad de la compilación), consulta Compatibilidad de Visual Studio para C++/WinRT.
Crear una aplicación en blanco (Bookstore)
Para empezar, crea un proyecto en Microsoft Visual Studio. Crea un proyecto Aplicación en blanco (C++/WinRT) y asígnale el nombre Bookstore. Asegúrese 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.
Vamos a crear una nueva clase para representar un libro que tenga una propiedad de título observable. Vamos a crear y consumir la clase dentro de la misma unidad de compilación. Pero queremos poder enlazar a esta clase desde XAML y para 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. Asigne el nombre BookSku.idl al nuevo elemento. Elimina el contenido predeterminado de BookSku.idl y pégalo en esta declaración de clase en tiempo de ejecución.
Las clases del modelo de vista, en realidad cualquier clase en tiempo de ejecución que declares en la aplicación, no deben derivar de una clase base. La clase BookSku declarada anteriormente es un ejemplo de ello. Implementa una interfaz, pero no deriva de ninguna clase base.
Cualquier clase en tiempo de ejecución que declares en la aplicación y que derive de una clase base se conoce como una clase que admite composición. Las clases que admiten composición tienen restricciones. Para que una aplicación pase las pruebas del kit de certificación de aplicaciones en Windows que Visual Studio y Microsoft Store utilizan para validar los envíos (y para que la aplicación se incorpore correctamente a Microsoft Store), una clase que admite composición debe derivar en última instancia de una clase base de Windows. Eso significa que, en la raíz misma de la jerarquía de herencia, la clase debe ser un tipo que se origina en un espacio de nombres Windows.*. Si necesita derivar una clase en tiempo de ejecución de una clase base, por ejemplo para implementar una clase BindableBase para todos los modelos de vista de los que puede derivar, puede derivar de Windows.UI.Xaml.DependencyObject.
Un modelo de vista es una abstracción de una vista y, por lo tanto, está enlazado directamente a la vista (el marcado XAML). Un modelo de datos es una abstracción de datos, solo lo consumen los modelos de vista y no se enlazan directamente a XAML. Por lo tanto, en lugar de declarar los modelos de datos como clases en tiempo de ejecución, puede hacerlo como estructuras o clases de C++. No es necesario declararlos en MIDL y tienes la libertad de usar cualquier jerarquía de herencia que prefieras.
Guarde el archivo y compile el proyecto. La compilación aún no se completará correctamente (del todo), pero realizará algunas acciones que necesitamos. En concreto, durante el proceso de compilación, la herramienta midl.exe se ejecuta para crear un archivo de metadatos de Windows Runtime que describe la clase en tiempo de ejecución (el archivo se sitúa en el disco en \Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd). 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 BookSku que declaraste en tu archivo IDL. Los encontraremos en el disco en un momento, pero esos códigos auxiliares son \Bookstore\Bookstore\Generated Files\sources\BookSku.h y BookSku.cpp.
Por lo tanto, haga clic con el botón derecho en el nodo del proyecto en Visual Studio y haga clic en Abrir carpeta en Explorador de archivos. Se abrirá la carpeta del proyecto en el Explorador de archivos. Ahora debería examinar el contenido de la carpeta \Bookstore\Bookstore\. Desde allí, vaya a la carpeta \Generated Files\sources\ y copie los archivos de código auxiliar BookSku.h y BookSku.cpp en el Portapapeles. Vuelva a la carpeta del proyecto (\Bookstore\Bookstore\) y pegue los dos archivos que acaba de copiar. Por último, en el Explorador de soluciones con el nodo de proyecto seleccionado, 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.
Implementar BookSku
Ahora, vamos a abrir \Bookstore\Bookstore\BookSku.h y BookSku.cpp e implementar nuestra clase en tiempo de ejecución. Primero, verá static_assert en la parte superior de BookSku.h y BookSku.cpp y deberá quitarlo.
A continuación, en BookSku.h, haga estos cambios.
En el constructor predeterminado, cambie = default por = delete. El motivo de ello es que no queremos un constructor predeterminado.
Agregue un miembro privado para almacenar la cadena de título. Tenga en cuenta que tenemos un constructor que toma el valor winrt::hstring. Ese valor es la cadena de título.
Agregue otro miembro privado para el evento que se generará cuando el título cambie.
Después de hacer estos cambios, tu BookSku.h tendrá este aspecto.
En la función de mutación Title, comprobamos si se ha establecido un valor diferente del valor actual. Y, si es el caso, actualizamos el título y también generamos el evento INotifyPropertyChanged::PropertyChanged con un argumento igual que el nombre de la propiedad que cambió. Esto sirve para que la interfaz de usuario sepa qué valor de propiedad tiene que volver a consultar.
El proyecto se volverá a compilar ahora, en caso de que quiera comprobarlo.
Declarar e implementar BookstoreViewModel
Nuestra página principal de XAML se enlazará a un modelo de vista principal. Y ese modelo de vista tendrá varias propiedades, incluida una de tipo BookSku . En este paso, declararemos e implementaremos la clase en tiempo de ejecución del modelo de vista principal.
Guárdelo y compílelo (la compilación no se completará correctamente, pero la realizamos para volver a generar los archivos de código auxiliar).
Copia BookstoreViewModel.h y BookstoreViewModel.cpp desde la carpeta Generated Files\sources en la carpeta del proyecto e inclúyelos en el proyecto. Abra estos archivos (para ello, vuelva a eliminar static_assert) e implemente la clase en tiempo de ejecución como se indica a continuación. Observa cómo, en BookstoreViewModel.h, hemos incluido BookSku.h, que declara el tipo de implementación para BookSku (que es winrt::Bookstore::implementation::BookSku). Y hemos quitado = default del constructor predeterminado.
Nota
En los siguientes listados para BookstoreViewModel.h y BookstoreViewModel.cpp, el código muestra la forma predeterminada de construir el miembro de datos m_bookSku. Este es el método que se creó en la primera versión de C++/WinRT, y se recomienda al menos estar familiarizado con el patrón. Con la versión 2.0 de C++ o WinRT y las posteriores, dispone de una forma de construcción optimizada que se conoce como construcción uniforme (consulte Novedades y cambios de C++/WinRT 2.0). Más adelante en este tema, mostraremos un ejemplo de construcción uniforme.
El tipo de m_bookSku es el tipo proyectado (winrt::Bookstore::BookSku) y el parámetro de plantilla que usas con winrt::make es el tipo de implementación (winrt::Bookstore::implementation::BookSku). Aun así, make devuelve una instancia del tipo proyectado.
A continuación, se volverá a compilar el proyecto.
Agrega una propiedad de tipo BookstoreViewModel a MainPage
Abre MainPage.idl, que declara la clase en tiempo de ejecución que representa nuestra página de interfaz de usuario principal.
Agregue una directiva import para importar BookstoreViewModel.idl.
Agregue una propiedad de solo lectura denominada MainViewModel de tipo BookstoreViewModel.
Guarde el archivo. El proyecto no se compilará correctamente por completo en este momento, pero realizar la compilación ahora resulta útil, ya que se generan los archivos de código fuente en los que se implementará la clase en tiempo de ejecución MainPage (\Bookstore\Bookstore\Generated Files\sources\MainPage.h y MainPage.cpp). Así que compila ahora. En esta fase, el error de compilación que verás es "MainViewModel": no es miembro de "winrt::Bookstore::implementation::MainPage" .
Si omites la inclusión de BookstoreViewModel.idl (consulta la lista anterior de MainPage.idl), verás el error se espera < cerca de "MainViewModel" . Otra sugerencia es asegurarse de que deja todos los tipos en el mismo espacio de nombres, que se muestra en los listados de código.
Para resolver el error que esperamos ver, tendrás que copiar el código auxiliar del descriptor de acceso de la propiedad MainViewModel fuera de los archivos generados (\Bookstore\Bookstore\Generated Files\sources\MainPage.h y MainPage.cpp) y en \Bookstore\Bookstore\MainPage.h y MainPage.cpp. Estos son los pasos para hacerlo.
En \Bookstore\Bookstore\MainPage.h, siga estos pasos.
Incluya BookstoreViewModel.h, que declara el tipo de implementación para BookstoreViewModel (que es winrt::Bookstore::implementation::BookstoreViewModel).
Agrega un miembro privado para almacenar el modelo de vista. Tenga en cuenta que la función de descriptor de acceso de propiedad (y el miembro m_mainViewModel) se implementan en términos del tipo proyectado para BookstoreViewModel (que es Bookstore::BookstoreViewModel).
El tipo de implementación está en el mismo proyecto (unidad de compilación) que la aplicación, por lo que construimos m_mainViewModel a través de la sobrecarga del constructor que toma std::nullptr_t.
Quite la propiedad MyProperty.
Nota
En el par de listados siguiente para MainPage.h y MainPage.cpp, el código muestra la forma predeterminada de construir el miembro de datos m_mainViewModel. En la siguiente sección, mostraremos una versión que utiliza la construcción uniforme en su lugar.
En \Bookstore\Bookstore\MainPage.cpp, como se muestra en la lista siguiente, realice los cambios siguientes.
Llame a winrt::make (con el tipo de implementación BookstoreViewModel) para asignar una nueva instancia del tipo BookstoreViewModel proyectado a m_mainViewModel. Como hemos visto anteriormente, el constructor BookstoreViewModel crea un nuevo objeto BookSku como miembro de datos privado, estableciendo su título inicialmente en L"Atticus".
En el controlador de eventos del botón (ClickHandler), actualice el título del libro a su título publicado.
Implemente el descriptor de acceso para la propiedad MainViewModel.
Para usar la construcción uniforme en lugar de winrt::make, en MainPage.h declare e inicialice m_mainViewModel en un solo paso, como se muestra a continuación.
A continuación, en el constructor MainPage de MainPage.cpp, no se requiere el código m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.
Abre MainPage.xaml, que contiene el marcado XAML de nuestra página principal de la interfaz de usuario. Tal y como muestra el listado siguiente, quita el nombre del el botón y cambia el valor de la propiedad Content de un literal a una expresión de enlace. Ten en cuenta la propiedad Mode=OneWay en la expresión de enlace (unidireccional desde el modelo de vista a la interfaz de usuario). Sin dicha propiedad, la interfaz de usuario no responderá a los eventos cambiados de propiedad.
Ahora compila y ejecuta el proyecto. Haz clic en el botón para ejecutar el controlador de eventos Clic. Este controlador llama la función de mutación de título del libro; la mutación genera un evento para que la interfaz de usuario sepa que la propiedad Title ha cambiado; y el botón vuelve a consultar el valor de dicha propiedad para actualizar su propio valor Content.
Uso de la extensión de marcado {Binding} con C++/WinRT
En la versión actualmente publicada de C++/WinRT, para poder usar la extensión de marcado {Binding} se deben implementar las interfaces ICustomPropertyProvider y ICustomProperty.
Enlace de elemento a elemento
Se puede enlazar la propiedad de un elemento XAML a la propiedad de otro elemento XAML. En este ejemplo se muestra cómo queda en marcado.
Este es el motivo de tal necesidad. Todos los tipos que el compilador XAML debe validar (incluidas aquellas utilizadas en {x: Bind}) se leen desde Metadatos de Windows (WinMD). Lo único que necesita hacer es agregar la propiedad de solo lectura al archivo Midl. No la implementes, porque el código XAML subyacente generado automáticamente proporciona la implementación por ti.
Consumir objetos a partir del marcado XAML
Todas las entidades consumidas por la extensión de marcado {x:Bind} de XAML deben exponerse públicamente en IDL. Además, si el marcado XAML contiene una referencia a otro elemento que también está en el marcado, el captador de dicho marcado debe estar presente en IDL.
El elemento ChangeColorButton hace referencia al elemento UseCustomColorCheckBox a través del enlace. Por lo tanto, el IDL de esta página debe declarar una propiedad de solo lectura denominada UseCustomColorCheckBox para que sea accesible para el enlace.
El delegado del controlador de eventos click para UseCustomColorCheckBox usa la sintaxis de delegado de XAML clásica, de modo que no necesita una entrada en el IDL; tan solo tiene que ser público en la clase de la implementación. Por otro lado, ChangeColorButton también tiene un controlador de eventos click {x:Bind}, que también debe entrar en el IDL.
idl
runtimeclass MyPage : Windows.UI.Xaml.Controls.Page
{
MyPage();
// These members are consumed by binding.
void ChangeColorButton_OnClick();
Windows.UI.Xaml.Controls.CheckBox UseCustomColorCheckBox{ get; };
}
No es necesario que proporciones una implementación para la propiedad UseCustomColorCheckBox. El generador de código XAML lo hace automáticamente.
Enlazar a un valor booleano
Puede hacerlo en modo de diagnóstico:
XAML
<TextBlockText="{Binding CanPair}"/>
Se muestra true o false en C++/CX, pero se muestra Windows.Foundation.IReference`1<Boolean> en C++/WinRT.
En su lugar, use x:Bind al enlazar a un valor booleano.
XAML
<TextBlockText="{x:Bind CanPair}"/>
Mediante las bibliotecas de implementación de Windows (WIL).
Cree una interfaz de usuario con enlace de datos. La interfaz de usuario se actualiza automáticamente en función de los datos más recientes, mientras que los datos se actualizan en respuesta a los cambios en la interfaz de usuario.
Este tema le ayudará durante el proceso de adición de compatibilidad básica para la [biblioteca de la interfaz de usuario de Windows (WinUI)]https://github.com/Microsoft/microsoft-ui-xaml) a su proyecto UWP en C++/WinRT. En concreto, este tema trata sobre WinUI 2, que es para aplicaciones para UWP.
Este tema te guía por los pasos de creación de un control personalizado sencillo con C++/WinRT. Puedes usar esta información para crear tus propios controles de interfaz de usuario repletos de características y personalizables.
En este tema se muestra cómo usar las API de C++/WinRT, tanto si se han implementado mediante Windows, un proveedor de componentes de terceros o por tu cuenta.
Una colección que se puede enlazar de forma eficaz a un control de elementos de XAML se conoce como una colección *observable*. En este tema se muestra cómo implementar y consumir una colección observable y cómo enlazar un control de elementos de XAML a dicha colección.
Un valor escalar o matricial debe estar encapsulado dentro de un objeto de clase de referencia antes de pasarlo a una función que espera **IInspectable**. Dicho proceso de encapsulación se conoce como la *conversión boxing* del valor.