Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Una propiedad que se puede enlazar eficazmente a un control XAML se conoce como una propiedad observable . Esta idea se basa en el patrón de diseño de software conocido como patrón de observador. En este tema se muestra cómo implementar propiedades observables en C++/WinRT y cómo enlazar controles XAML a ellos (para obtener información en segundo plano, consulta Enlace de datos).
Important
Para conocer los conceptos y términos esenciales que admiten su comprensión de cómo consumir y crear clases en tiempo de ejecución con C++/WinRT, consulte Consumo de API con C++/WinRT y Creación de API con C++/WinRT.
¿Qué significa observable para una propiedad?
Supongamos que una clase en tiempo de ejecución denominada BookSku tiene una propiedad denominada Title. Si BookSku genera el evento INotifyPropertyChanged::P ropertyChanged cada vez que cambia el valor de Title , significa que Title es una propiedad observable. Es el comportamiento de BookSku (si dispara o no el evento) lo que determina cuáles de sus propiedades, si alguna, son observables.
Un elemento de texto XAML, o control, puede enlazarse a estos eventos y controlarlos. Este elemento o control controla el evento recuperando los valores actualizados y, a continuación, actualizándose para mostrar el nuevo valor.
Note
Para obtener información sobre cómo instalar y usar la extensión de Visual Studio de C++/WinRT (VSIX) y el paquete NuGet (que juntos proporcionan compatibilidad con la compilación y la plantilla de proyecto), consulta Visual Studio compatibilidad con C++/WinRT.
Crear una aplicación en blanco (Bookstore)
Empiece por crear un nuevo proyecto en Microsoft Visual Studio. Cree una aplicación en blanco, empaquetada (WinUI 3 en el escritorio) para el proyecto de C++ y asígnela el nombre Bookstore. Asegúrese de que la opción Colocar solución y proyecto en el mismo directorio está desactivada. Tenga como destino la versión más reciente disponible con carácter general (es decir, no en versión preliminar) del SDK de Windows.
Vamos a crear una nueva clase para representar un libro que tenga una propiedad de título observable. Estamos creando y consumiendo la clase dentro de la misma unidad de compilación. Pero queremos poder enlazar a esta clase desde XAML y, por ese motivo, va a ser una clase en tiempo de ejecución. Y vamos a usar C++/WinRT para crearlo y consumirlo.
El primer paso para crear una nueva clase en tiempo de ejecución es agregar un nuevo elemento Midl File (.idl) al proyecto. Asigne el nombre BookSku.idl al nuevo elemento. Elimine el contenido predeterminado de BookSku.idl, y pegue en él esta declaración de clase de tiempo de ejecución.
// BookSku.idl
namespace Bookstore
{
runtimeclass BookSku : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
{
BookSku(String title);
String Title;
}
}
Note
Las clases del modelo de vista (de hecho, cualquier clase en tiempo de ejecución que declare en la aplicación) no deben derivarse de una clase base. La clase BookSku declarada anteriormente es un ejemplo de eso. Implementa una interfaz, pero no se deriva de ninguna clase base.
Cualquier clase en tiempo de ejecución que declare en la aplicación que deriva de una clase base se conoce como una clase composable . Y hay restricciones respecto a las clases que se pueden componer. Para que una aplicación pase las pruebas del Kit de certificación de aplicaciones de Windows usadas por Visual Studio y microsoft Store para validar los envíos (y, por tanto, para que la aplicación se ingera correctamente en Microsoft Store), una clase que se pueda componer debe derivar en última instancia de una clase base de Windows. Lo que significa que la clase en la raíz de la jerarquía de herencia debe ser un tipo que se origine en un espacio de nombres Windows.* o Microsoft.*. 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 derivar), puede derivar de Microsoft. Interfaz de usuario. Xaml.DependencyObject.
Un modelo de vista es una abstracción de una vista y, por tanto, está vinculado directamente a la vista (el lenguaje de marcado XAML). Un modelo de datos es una abstracción de los datos y solo se utiliza desde los modelos de vista, y no se enlaza directamente a XAML. Por lo tanto, puede declarar los modelos de datos no como clases en tiempo de ejecución, sino como estructuras o clases de C++. No es necesario declararlos en MIDL y puede usar la jerarquía de herencia que quiera.
Guarde el archivo y compile el proyecto. La compilación aún no tendrá éxito del todo, pero nos servirá para realizar algunas tareas necesarias. En concreto, durante el proceso de compilación se ejecuta la midl.exe herramienta para crear un archivo de metadatos de Windows Runtime que describe la clase en tiempo de ejecución (el archivo se coloca en el disco en \Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd). A continuación, se ejecuta la herramienta cppwinrt.exe para generar archivos de código fuente que le ayuden a crear y consumir su clase de tiempo de ejecución. Estos archivos incluyen códigos auxiliares para empezar a implementar la clase en tiempo de ejecución BookSku que declaró en su IDL. Dentro de un momento los encontraremos en disco, pero esos stubs 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 el Explorador de archivos. Que abre la carpeta del proyecto en el Explorador de archivos. Ahora debería examinar el contenido de la \Bookstore\Bookstore\ carpeta. Desde allí, vaya a la carpeta \Generated Files\sources\ y copie los archivos auxiliares 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 Explorador de Soluciones con el nodo del proyecto seleccionado, asegúrese de que Mostrar todos los archivos esté activado. Haga clic con el botón derecho en los archivos auxiliares que copió y haga clic en Incluir en Project.
Implementación de BookSku
Ahora vamos a abrir \Bookstore\Bookstore\BookSku.h y BookSku.cpp e implementar nuestra clase de tiempo de ejecución. En primer lugar, verá un static_assert en la parte superior de BookSku.h y BookSku.cpp, que deberá quitar.
A continuación, en BookSku.h, realice estos cambios.
- En el constructor predeterminado, cambie
= defaulta= delete. Esto se debe a 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 un valor winrt::hstring . Ese valor es la cadena de título.
- Agregue otro miembro privado para el evento que se generará cuando cambie el título.
Después de realizar estos cambios, BookSku.h tendrá este aspecto.
// BookSku.h
#pragma once
#include "BookSku.g.h"
namespace winrt::Bookstore::implementation
{
struct BookSku : BookSkuT<BookSku>
{
BookSku() = delete;
BookSku(winrt::hstring const& title);
winrt::hstring Title();
void Title(winrt::hstring const& value);
winrt::event_token PropertyChanged(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& value);
void PropertyChanged(winrt::event_token const& token);
private:
winrt::hstring m_title;
winrt::event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
};
}
namespace winrt::Bookstore::factory_implementation
{
struct BookSku : BookSkuT<BookSku, implementation::BookSku>
{
};
}
En BookSku.cpp, implemente las funciones como esta.
// BookSku.cpp
#include "pch.h"
#include "BookSku.h"
#include "BookSku.g.cpp"
namespace winrt::Bookstore::implementation
{
BookSku::BookSku(winrt::hstring const& title) : m_title{ title }
{
}
winrt::hstring BookSku::Title()
{
return m_title;
}
void BookSku::Title(winrt::hstring const& value)
{
if (m_title != value)
{
m_title = value;
m_propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"Title" });
}
}
winrt::event_token BookSku::PropertyChanged(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
{
return m_propertyChanged.add(handler);
}
void BookSku::PropertyChanged(winrt::event_token const& token)
{
m_propertyChanged.remove(token);
}
}
En la función mutador Title , comprobamos si se establece un valor distinto del valor actual. Y, si es así, actualizamos el título y también generamos el evento INotifyPropertyChanged::P ropertyChanged con un argumento igual al nombre de la propiedad que ha cambiado. Esto es para que la interfaz de usuario (UI) sepa qué valor de propiedad se va a volver a consultar.
El proyecto se compilará de nuevo ahora, si desea comprobarlo.
Declarar e implementar BookstoreViewModel
Nuestra página XAML principal se enlazará a un modelo de vista principal. Y ese modelo de vista va a tener varias propiedades, incluido uno de tipo BookSku. En este paso, declararemos e implementaremos nuestra clase de tiempo de ejecución del modelo de vista principal.
Agregue un nuevo elemento midl File (.idl) denominado BookstoreViewModel.idl. Consulte también Incorporación de clases en tiempo de ejecución en archivos Midl (.idl).
// BookstoreViewModel.idl
import "BookSku.idl";
namespace Bookstore
{
runtimeclass BookstoreViewModel
{
BookstoreViewModel();
BookSku BookSku{ get; };
}
}
Guarde y compile (la compilación aún no se realizará correctamente, pero el motivo por el que estamos compilando es volver a generar archivos de código auxiliar).
Copie BookstoreViewModel.h y BookstoreViewModel.cpp de la Generated Files\sources carpeta en la carpeta del proyecto e inclúyelas en el proyecto. Abre esos archivos (quitando de nuevo static_assert) e implementa la clase de tiempo de ejecución como se muestra a continuación. Observe cómo, en BookstoreViewModel.h, se incluye BookSku.h, que declara el tipo de implementación para BookSku (que es winrt::Bookstore::implementation::BookSku). Y estamos quitando = default del constructor predeterminado.
Note
En las listas siguientes para BookstoreViewModel.h y BookstoreViewModel.cpp, el código muestra la forma predeterminada de construir el miembro de datos m_bookSku . Ese es el método que se remonta a la primera versión de C++/WinRT y es una buena idea estar al menos familiarizado con el patrón. Con C++/WinRT versión 2.0 y versiones posteriores, hay una forma optimizada de construcción disponible para usted conocido como construcción uniforme (consulte Noticias y cambios, en C++/WinRT 2.0). Más adelante en este tema, mostraremos un ejemplo de construcción uniforme.
// BookstoreViewModel.h
#pragma once
#include "BookstoreViewModel.g.h"
#include "BookSku.h"
namespace winrt::Bookstore::implementation
{
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
BookstoreViewModel();
Bookstore::BookSku BookSku();
private:
Bookstore::BookSku m_bookSku{ nullptr };
};
}
namespace winrt::Bookstore::factory_implementation
{
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel, implementation::BookstoreViewModel>
{
};
}
// BookstoreViewModel.cpp
#include "pch.h"
#include "BookstoreViewModel.h"
#include "BookstoreViewModel.g.cpp"
namespace winrt::Bookstore::implementation
{
BookstoreViewModel::BookstoreViewModel()
{
m_bookSku = winrt::make<Bookstore::implementation::BookSku>(L"Atticus");
}
Bookstore::BookSku BookstoreViewModel::BookSku()
{
return m_bookSku;
}
}
Note
El tipo de m_bookSku es el tipo proyectado (winrt::Bookstore::BookSku), y el parámetro de plantilla que se utiliza con winrt::make es el tipo de implementación (winrt::Bookstore::implementation::BookSku). Incluso así, make devuelve una instancia del tipo proyectado.
Ahora el proyecto volverá a compilarse.
Agregar una propiedad de tipo BookstoreViewModel a MainPage
Abra MainPage.idl, que declara la clase en tiempo de ejecución que representa la página principal de la interfaz de usuario.
- Agregue una
importdirectiva para importarBookstoreViewModel.idl. - Agregue una propiedad de solo lectura denominada MainViewModel, de tipo BookstoreViewModel.
- Quite la propiedad MyProperty.
// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
MainPage();
BookstoreViewModel MainViewModel{ get; };
}
}
Guarde el archivo. El proyecto todavía no se compilará completamente, pero compilarlo ahora es útil porque regenera los archivos de código fuente en los que se implementa la clase de tiempo de ejecución MainPage (\Bookstore\Bookstore\Generated Files\sources\MainPage.h y MainPage.cpp). Así que continúe y compile ahora. El error de compilación que puede ver en esta fase es "MainViewModel": no es miembro de "winrt::Bookstore::implementation::MainPage".
Si omite la inclusión de BookstoreViewModel.idl (vea la lista anteriorMainPage.idl), verá el error que espera < cerca de "MainViewModel". Otra sugerencia es asegurarse de que deja todos los tipos en el mismo espacio de nombres: el espacio de nombres que se muestra en las listas de código.
Para resolver el error que esperamos ver, ahora tendrás que copiar los stubs de acceso de la propiedad MainViewModel de los archivos generados (\Bookstore\Bookstore\Generated Files\sources\MainPage.h y MainPage.cpp) y pegarlos en \Bookstore\Bookstore\MainPage.h y MainPage.cpp. Los pasos que se deben seguir se describen a continuación.
En \Bookstore\Bookstore\MainPage.h, realice estos pasos.
- Incluya
BookstoreViewModel.h, que declara el tipo de implementación para BookstoreViewModel (que es winrt::Bookstore::implementation::BookstoreViewModel). - Agregue un miembro privado para almacenar el modelo de vista. Tenga en cuenta que la función de acceso a la propiedad (y el miembro m_mainViewModel) están implementados en función 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 creamos m_mainViewModel a través de la sobrecarga del constructor que toma std::nullptr_t.
- Quite la propiedad MyProperty.
Note
En el par de listados siguientes para MainPage.h y MainPage.cpp, el código muestra la forma predeterminada de construir el miembro de datos m_mainViewModel . En la sección siguiente, mostraremos una versión que usa la construcción uniforme en su lugar.
// MainPage.h
...
#include "BookstoreViewModel.h"
...
namespace winrt::Bookstore::implementation
{
struct MainPage : MainPageT<MainPage>
{
MainPage();
Bookstore::BookstoreViewModel MainViewModel();
void ClickHandler(Windows::Foundation::IInspectable const&, Microsoft::UI::Xaml::RoutedEventArgs const&);
private:
Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
};
}
...
En \Bookstore\Bookstore\MainPage.cpp, como se muestra en la lista siguiente, realice los siguientes cambios.
- Llame a winrt::make (con el tipo de implementación BookstoreViewModel ) para asignar una nueva instancia del tipo BookstoreViewModel proyectado a m_mainViewModel. Como vimos 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.
- Implementar el accesor para la propiedad MainViewModel.
- Quite la propiedad MyProperty.
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"
using namespace winrt;
using namespace Microsoft::UI::Xaml;
namespace winrt::Bookstore::implementation
{
MainPage::MainPage()
{
m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
InitializeComponent();
}
void MainPage::ClickHandler(Windows::Foundation::IInspectable const& /* sender */, Microsoft::UI::Xaml::RoutedEventArgs const& /* args */)
{
MainViewModel().BookSku().Title(L"To Kill a Mockingbird");
}
Bookstore::BookstoreViewModel MainPage::MainViewModel()
{
return m_mainViewModel;
}
}
Construcción uniforme
Para usar la construcción uniforme en lugar de winrt::make, en MainPage.h declarar e inicializar m_mainViewModel en un solo paso, como se muestra a continuación.
// MainPage.h
...
#include "BookstoreViewModel.h"
...
struct MainPage : MainPageT<MainPage>
{
...
private:
Bookstore::BookstoreViewModel m_mainViewModel;
};
...
Y, a continuación, en el constructor MainPage de MainPage.cpp, no es necesario el código m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.
Para obtener más información sobre la construcción uniforme y ejemplos de código, vea Opt in to uniform construction (Participar en la construcción uniforme) y acceso directo a la implementación.
Enlace del botón a la propiedad Title
Abra MainPage.xaml, que contiene el marcado XAML para nuestra página principal de la interfaz de usuario. Como se muestra en la lista siguiente, quite el nombre del botón y cambie su valor de propiedad Content de un literal a una expresión de enlace. Observe la propiedad Mode=OneWay en la expresión de enlace (unidireccional desde el modelo de vista hasta la interfaz de usuario). Sin esa propiedad, la interfaz de usuario no responderá a los eventos de cambio de propiedad.
<Button Click="ClickHandler" Content="{x:Bind MainViewModel.BookSku.Title, Mode=OneWay}"/>
Ahora compile y ejecute el proyecto. Haga clic en el botón para ejecutar el controlador de eventos Click . Ese controlador llama a la función mutador de título del libro; ese mutador 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 esa propiedad para actualizar su propio valor content .
Uso de la extensión de marcado {Binding} con C++/WinRT
Para la versión publicada actualmente de C++/WinRT, para poder usar la extensión de marcado {Binding} deberá implementar las interfaces ICustomPropertyProvider e ICustomProperty .
Enlace de elemento a elemento
Puedes enlazar la propiedad de un elemento XAML a la propiedad de otro elemento XAML. Aquí tienes un ejemplo de cómo se ve en código de marcado.
<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />
Tendrás que declarar la entidad XAML denominada myTextBox como una propiedad de solo lectura en tu archivo Midl (.idl).
// MainPage.idl
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
MainPage();
Microsoft.UI.Xaml.Controls.TextBox myTextBox{ get; };
}
Esta es la razón de esta necesidad. Todos los tipos que el compilador XAML necesita validar (incluidos los usados en {x:Bind}) se leen de Windows Metadatos (WinMD). Todo lo que debe hacer es agregar la propiedad de solo lectura al archivo Midl. No lo implementes, porque el código subyacente XAML autogenerado te proporciona la implementación.
Consumo de objetos del marcado XAML
Todas las entidades consumidas mediante la extensión de marcado XAML {x:Bind} deben exponerse públicamente en IDL. Además, si el marcado XAML contiene una referencia a otro elemento que también está en marcado, el captador de ese marcado debe estar presente en IDL.
<Page x:Name="MyPage">
<StackPanel>
<CheckBox x:Name="UseCustomColorCheckBox" Content="Use custom color"
Click="UseCustomColorCheckBox_Click" />
<Button x:Name="ChangeColorButton" Content="Change color"
Click="{x:Bind ChangeColorButton_OnClick}"
IsEnabled="{x:Bind UseCustomColorCheckBox.IsChecked.Value, Mode=OneWay}"/>
</StackPanel>
</Page>
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 del evento click para UseCustomColorCheckBox usa la sintaxis clásica de delegados de XAML, así que no hace falta una entrada en el IDL; solo tiene que ser público en su clase de implementación. Por otro lado, ChangeColorButton también tiene un {x:Bind} controlador del evento de clic, que también debe incluirse en la IDL.
runtimeclass MyPage : Microsoft.UI.Xaml.Controls.Page
{
MyPage();
// These members are consumed by binding.
void ChangeColorButton_OnClick();
Microsoft.UI.Xaml.Controls.CheckBox UseCustomColorCheckBox{ get; };
}
No es necesario proporcionar una implementación para la propiedad UseCustomColorCheckBox . El generador de código XAML lo hace automáticamente.
Vinculación a booleano
Puede hacerlo en modo de diagnóstico:
<TextBlock Text="{Binding CanPair}"/>
Esto 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 con un valor booleano.
<TextBlock Text="{x:Bind CanPair}"/>
Uso de las bibliotecas de implementación de Windows (WIL)
Las bibliotecas de implementación de Windows (WIL) proporcionan asistentes para facilitar la escritura de propiedades enlazables. Consulte Notifying Properties en la documentación de WIL.