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.
En este tema se presenta un caso práctico de migración de uno de los ejemplos de aplicaciones de Plataforma universal de Windows (UWP) de C# a C++/WinRT. Puede adquirir práctica y experiencia en la adaptación siguiendo la guía e implementando usted mismo el ejemplo a medida que avanza.
Note
El código fuente que se va a migrar es una aplicación de C# para UWP. El código de C++/WinRT de destino de este artículo se escribe para WinUI 3 (SDK de Aplicaciones para Windows). Siempre que el código fuente de UWP use API que difieran en WinUI 3 (por ejemplo, Windows.UI.Core.CoreDispatcher frente a Microsoft.UI.Dispatching.DispatcherQueue), este artículo muestra explícitamente el equivalente correcto en WinUI 3 en el código generado de C++/WinRT. Puedes usar los patrones de código de la columna C++/WinRT directamente en la aplicación WinUI 3.
Para obtener un catálogo completo de los detalles técnicos implicados en la portabilidad a C++/WinRT desde C#, consulte el tema complementario Mover a C++/WinRT desde C#.
Un breve prefacio sobre los archivos de código fuente de C# y C++
En un proyecto de C#, los archivos de código fuente son principalmente .cs archivos. Al pasar a C++, observará que hay más tipos de archivos de código fuente para usarlos. La razón es hacer con la diferencia entre los compiladores, la forma en que se reutiliza el código fuente de C++ y las nociones de declarar y definir un tipo y sus funciones (sus métodos).
Una declaración de función describe solo la firma de la función (su tipo de valor devuelto, su nombre y sus tipos de parámetros y nombres). Una definición de función incluye el cuerpo de la función (su implementación).
Es un poco diferente cuando se trata de tipos. Un tipo se define proporcionando su nombre y, como mínimo, declarando todas sus funciones miembro (y otros miembros). Así es, puedes definir un tipo aunque no definas sus funciones miembro.
- Los archivos de código fuente comunes de C++ son
.h(dot aitch) y.cpparchivos. Un.harchivo es un archivo de encabezado y define uno o varios tipos. Aunque puedes definir funciones miembro en un archivo de cabecera, para eso suele estar un archivo.cpp. Por lo tanto, para un tipo hipotético de C++ MyClass, definiría MyClass enMyClass.hy definiría sus funciones miembro enMyClass.cpp. Para que otros desarrolladores vuelvan a usar las clases, compartiría solo los archivos y el.hcódigo de objeto. Mantendría el secreto de los.cpparchivos, ya que la implementación constituye su propiedad intelectual. - Encabezado precompilado (
pch.h). Normalmente, hay un conjunto de archivos de encabezado que se incluyen en la aplicación y no se cambian esos archivos con mucha frecuencia. Por lo tanto, en lugar de procesar el contenido de ese conjunto de encabezados cada vez que se compila, puede agregar esos encabezados en un archivo, compilarlo una vez y, a continuación, usar la salida de ese paso de precompilación cada vez que se compila. Esto se hace mediante un archivo de cabecera precompilada (normalmente llamadopch.h). - Archivos
.idl. Estos archivos contienen el lenguaje de definición de interfaz (IDL). Puede considerar IDL como archivos de encabezado para tipos de Windows Runtime. Hablaremos más sobre IDL en la sección IDL para el tipo MainPage.
Descarga y prueba del ejemplo del Portapapeles
Visite la página web del ejemplo Clipboard sample y haga clic en Descargar ZIP. Descomprima el archivo descargado y eche un vistazo a la estructura de carpetas.
- La versión de C# del código fuente de ejemplo se encuentra en la carpeta denominada
cs. - La versión de C++/WinRT del código fuente de ejemplo se encuentra en la carpeta denominada
cppwinrt. - Otros archivos( usados por la versión de C# y la versión de C++/WinRT) se pueden encontrar en las
sharedcarpetas ySharedContent.
En este tutorial se muestra cómo puede recrear la versión en C++/WinRT del ejemplo Clipboard portándolo desde el código fuente en C#. De este modo, puede ver cómo puede migrar sus propios proyectos de C# a C++/WinRT.
Para obtener una idea de lo que hace el ejemplo, abra la solución de C# (\Clipboard_sample\cs\Clipboard.sln), cambie la configuración según corresponda (quizás a x64), compile y ejecute. La propia interfaz de usuario (UI) del ejemplo le guía a través de sus diversas características, paso a paso.
Tip
La carpeta raíz del ejemplo que descargó podría denominarse Clipboard en lugar de Clipboard_sample. Pero continuaremos haciendo referencia a esa carpeta como Clipboard_sample para distinguirla de la versión de C++/WinRT que va a crear en un paso posterior.
Creación de una aplicación en blanco, denominada Portapapeles
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.
Comience el proceso de portabilidad mediante la creación de un nuevo proyecto de C++/WinRT en Microsoft Visual Studio. Cree un nuevo proyecto con la plantilla de proyecto Aplicación en blanco, empaquetada (WinUI 3 en escritorio) para C++. Asígnele el nombre de Portapapeles y, para que la estructura de carpetas coincida con la guía, asegúrese de que Colocar la solución y el proyecto en el mismo directorio no esté marcada.
Como punto de partida, asegúrese de que este proyecto nuevo y vacío se compila y se ejecuta.
Package.appxmanifest y archivos de recursos
Si las versiones en C# y C++/WinRT del ejemplo no necesitan instalarse de forma simultánea en la misma máquina, entonces los archivos de origen del manifiesto del paquete de la aplicación de ambos proyectos (Package.appxmanifest) pueden ser idénticos. En ese caso, solo puede copiar Package.appxmanifest desde el proyecto de C# al proyecto de C++/WinRT y ha terminado.
Para que coexistan las dos versiones del ejemplo, necesitan identificadores diferentes. En ese caso, en el proyecto de C++/WinRT, abra el Package.appxmanifest archivo en un editor XML y anote estos tres valores.
- Dentro del elemento /Package/Identity , anote el valor del atributo Name . Este es el nombre del paquete. Para un proyecto recién creado, el proyecto le proporcionará un valor inicial de un GUID único.
- Dentro del elemento /Package/Applications/Application , tenga en cuenta el valor del atributo Id . Este es el identificador de la aplicación.
- Dentro del elemento /Package/mp:PhoneIdentity , tenga en cuenta el valor del atributo PhoneProductId . Una vez más, para un proyecto recién creado, este se establecerá con el mismo GUID que se haya asignado al nombre del paquete.
A continuación, copie Package.appxmanifest desde el proyecto de C# al proyecto de C++/WinRT. Por último, puede restaurar los tres valores que anotó. O bien, puede editar los valores copiados para que sean únicos o adecuados para la aplicación y para la organización (como normalmente lo haría para un nuevo proyecto). Por ejemplo, en este caso, en lugar de restaurar el valor del nombre del paquete, solo podemos cambiar el valor copiado de Microsoft. SDKSamples.Clipboard.CS a Microsoft. SDKSamples.Clipboard.CppWinRT. Y podemos dejar el identificador de aplicación establecido en Aplicación. Siempre que el nombre del paquete o el identificador de aplicación sean diferentes, las dos aplicaciones tendrán identificadores de modelo de usuario de aplicación diferentes (AUMID). Y eso es lo necesario para que dos aplicaciones se instalen en paralelo en la misma máquina.
Para los fines de este tutorial, tiene sentido realizar algunos otros cambios en Package.appxmanifest. Hay tres apariciones de la cadena Clipboard C# Sample. Cámbiela a Ejemplo de C++/WinRT del Portapapeles.
En el proyecto de C++/WinRT, el Package.appxmanifest archivo y el proyecto ya no están sincronizados con respecto a los archivos de recursos a los que hacen referencia. Para solucionarlo, primero quite los recursos del proyecto de C++/WinRT seleccionando todos los archivos de la Assets carpeta (en Explorador de soluciones en Visual Studio) y quitándolos (elija Eliminar en el cuadro de diálogo).
El proyecto de C# hace referencia a archivos de recursos desde una carpeta compartida. Puedes hacer lo mismo en el proyecto de C++/WinRT, o puedes copiar los archivos como lo haremos en este tutorial.
Vaya a la carpeta \Clipboard_sample\SharedContent\media. Seleccione los siete archivos que incluye el proyecto de C# (microsoft-sdk.png, smalltile-sdk.png, splash-sdk.pngsquaretile-sdk.pngstorelogo-sdk.png, tile-sdk.pngy windows-sdk.png), cópielos y péguelos en la \Clipboard\Clipboard\Assets carpeta del nuevo proyecto.
Haga clic con el botón derecho en la Assets carpeta (en Explorador de soluciones en el proyecto de C++/WinRT) >Agregar>elemento existente... y vaya a \Clipboard\Clipboard\Assets. En el selector de archivos, seleccione los siete archivos y haga clic en Agregar.
Package.appxmanifest ahora vuelve a estar sincronizado con los archivos de recursos del proyecto.
MainPage, incluida la funcionalidad que configura el ejemplo
El ejemplo del Portapapeles, como todos los ejemplos de aplicaciones de Plataforma universal de Windows (UWP), consta de una colección de escenarios que el usuario puede recorrer de una en una. La colección de escenarios de un ejemplo determinado se configura en el código fuente del ejemplo. Cada escenario de la colección es un elemento de datos que almacena un título, así como el tipo de la clase en el proyecto que implementa el escenario.
En la versión de C# del ejemplo, si busca en el SampleConfiguration.cs archivo de código fuente, verá dos clases. La mayoría de la lógica de configuración se encuentra en la clase MainPage , que es una clase parcial (forma una clase completa cuando se combina con el marcado en MainPage.xaml y el código imperativo en MainPage.xaml.cs). La otra clase de este archivo de código fuente es Scenario, con sus propiedades Title y ClassType .
En las siguientes subsecciones, veremos cómo portar MainPage y Scenario.
IDL para el tipo MainPage
Comencemos esta sección hablando brevemente sobre el lenguaje de definición de interfaz (IDL) y cómo nos ayuda a programar con C++/WinRT. IDL es un tipo de código fuente que describe la superficie invocable de un tipo de Windows Runtime. La interfaz invocable (o pública) de un tipo se expone al exterior, para que el tipo pueda utilizarse. Esa parte proyectada del tipo contrasta con la implementación interna real del tipo, que, por supuesto, no es invocable ni pública. Solo definimos en IDL la porción proyectada.
Después de haber creado el código fuente IDL (dentro de un .idl archivo), puede compilar el IDL en archivos de metadatos legibles por la máquina (también conocidos como metadatos de Windows). Esos archivos de metadatos tienen la extensión .winmdy estos son algunos de sus usos.
- Un
.winmdpuede describir los tipos de Windows Runtime en un componente. Cuando un proyecto de aplicación hace referencia a un componente de Windows Runtime (WRC), el proyecto de aplicación lee los metadatos de Windows correspondientes al WRC (estos metadatos pueden estar en un archivo independiente o empaquetados en el mismo archivo que el propio WRC), para que la aplicación pueda usar los tipos del WRC en la propia aplicación. - Un
.winmdpuede describir los tipos de Windows Runtime en una parte de la aplicación para que puedan ser consumidos por una parte diferente de la misma aplicación. Por ejemplo, un tipo de Windows Runtime consumido por una página XAML en la misma aplicación. - Para facilitar el uso de tipos de Windows Runtime (integrados o de terceros), el sistema de compilación de C++/WinRT usa archivos
.winmdpara generar tipos contenedores que representan las porciones proyectadas de esos tipos de Windows Runtime. - Para facilitar la implementación de sus propios tipos de Windows Runtime, el sistema de compilación de C++/WinRT convierte su IDL en un
.winmdarchivo y, a continuación, lo usa para generar contenedores para la proyección, así como códigos auxiliares en los que basar la implementación (hablaremos más sobre estos códigos auxiliares más adelante en este tema).
La versión específica de IDL que usamos con C++/WinRT es Microsoft Interface Definition Language 3.0. En el resto de esta sección del tema, examinaremos el tipo MainPage de C# con algunos detalles. Decidiremos qué partes deben estar en la proyección del tipo MainPage de C++/WinRT (es decir, en su superficie invocable o pública) y que pueden formar parte de su implementación. Esa distinción es importante porque cuando lleguemos a crear nuestro IDL (lo que haremos en la sección después de este), definiremos solo las partes invocables allí.
Los archivos de código fuente de C# que juntos implementan el tipo MainPage son: MainPage.xaml (que se migrarán pronto, copiandolo), MainPage.xaml.csy SampleConfiguration.cs.
En la versión de C++/WinRT, dividiremos nuestro tipo MainPage en varios archivos de código fuente de manera similar. Tomaremos la lógica en MainPage.xaml.cs y la traduciremos para la mayor parte a MainPage.h y MainPage.cpp. Y para la lógica de SampleConfiguration.cs, lo traduciremos a SampleConfiguration.h y SampleConfiguration.cpp.
Las clases de una aplicación para la Plataforma universal de Windows (UWP) en C# son, por supuesto, tipos de Windows Runtime. Pero al crear un tipo en una aplicación de C++/WinRT, puede elegir si ese tipo es un tipo de Windows Runtime o una clase/estructura/enumeración de C++ normal.
Cualquier página XAML de nuestro proyecto debe ser un tipo de Windows Runtime, por lo que MainPage debe ser un tipo Windows Runtime. En el proyecto de C++/WinRT, MainPage ya es un tipo de Windows Runtime, por lo que no es necesario cambiar ese aspecto. En concreto, es una clase en tiempo de ejecución.
- Para obtener más información sobre si debe crear o no una clase en tiempo de ejecución para un tipo determinado, consulte el tema Author API with C++/WinRT (Creación de API con C++/WinRT).
- En C++/WinRT, la implementación interna de una clase en tiempo de ejecución y las partes proyectadas (públicas) de ella existen en forma de dos clases diferentes. Estos se conocen como el tipo de implementación y el tipo proyectado. Puede obtener más información al respecto en el tema mencionado en la viñeta anterior, y también en Consumir API con C++/WinRT.
- Para obtener más información sobre la conexión entre las clases en tiempo de ejecución y los archivos IDL (
.idl), puedes leer y seguir el tema Controles XAML; enlazar con una propiedad de C++/WinRT. Este tema le guía por el proceso de creación de una nueva clase en tiempo de ejecución, el primer paso del cual se va a agregar un nuevo elemento midl File (.idl) al proyecto.
Para MainPage, en realidad tenemos el archivo necesario MainPage.idl en el proyecto de C++/WinRT. Esto se debe a que la plantilla de proyecto la creó para nosotros. Pero más adelante en este tutorial agregaremos más .idl archivos al proyecto.
En breve, veremos una lista de exactamente qué IDL necesitamos agregar al archivo existente MainPage.idl . Antes de eso, tenemos que razonar un poco sobre qué debe ir en el IDL y qué no.
Para determinar qué miembros de MainPage necesitamos declarar en (para que se conviertan en MainPage.idl parte de la clase en tiempo de ejecución MainPage ) y que simplemente pueden ser miembros del tipo de implementación MainPage , vamos a crear una lista de los miembros de la clase MainPage de C#. Encontramos a esos miembros buscando en MainPage.xaml.cs y en SampleConfiguration.cs.
Encontramos un total de doce protected campos y private métodos. Y encontramos a los siguientes public miembros.
- Constructor predeterminado
MainPage(). - Los campos estáticos Current y FEATURE_NAME.
- Las propiedades IsClipboardContentChangedEnabled y Scenarios.
- Los métodos BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotifications, y NotifyUser.
Son esos public miembros los que son susceptibles de declararse en MainPage.idl. Por lo tanto, vamos a examinar cada uno de ellos y ver si deben formar parte de la clase en tiempo de ejecución MainPage o si solo necesitan formar parte de su implementación.
- Constructor predeterminado
MainPage(). Para una página XAML, es normal declarar un constructor predeterminado en su IDL. De este modo, el marco de interfaz de usuario XAML puede activar el tipo. - El campo estático Current se usa desde las páginas XAML del escenario individual para acceder a la instancia de MainPage de la aplicación. Puesto que Current no se usa para interoperar con el marco XAML (ni se usa en unidades de compilación), podríamos reservarlo para que sea únicamente miembro del tipo de implementación. En sus propios proyectos, en casos como este, puede optar por hacerlo. Pero dado que el campo es una instancia del tipo proyectado, se siente lógico declararlo en la IDL. Así que eso es lo que haremos aquí (y hacerlo también hace que el código sea ligeramente más limpio).
- Es un caso similar para el campo estático FEATURE_NAME, al que se accede desde el tipo MainPage. De nuevo, elegir declararlo en el IDL hace que el código sea ligeramente más limpio.
- La propiedad IsClipboardContentChangedEnabled solo se usa en la clase OtherScenarios . Así que, durante la adaptación, simplificaremos un poco las cosas y la convertiremos en un campo privado de la clase de tiempo de ejecución OtherScenarios. Para que no vaya a la IDL.
- La propiedad Scenarios es una colección de objetos de tipo Scenario (un tipo que mencionamos anteriormente). Hablaremos de Scenario en la siguiente subsección, así que vamos a dejar la propiedad Scenarios hasta entonces.
- Los métodos BuildClipboardFormatsOutputString, DisplayToast y EnableClipboardContentChangedNotifications son funciones de utilidad que se sienten más relacionadas con el estado general del ejemplo que sobre la página principal. Por lo tanto, durante el puerto, refactorizaremos estos tres métodos en un nuevo tipo de utilidad denominado SampleState (que no necesita ser un tipo Windows Runtime). Por esa razón, estos tres métodos no se incluirán en la IDL.
- Se llama al método NotifyUser desde las páginas XAML de cada escenario, en la instancia de MainPage devuelta por el campo estático Current. Como (como ya se indicó) Current es una instancia del tipo proyectado, es necesario declarar NotifyUser en el IDL. NotifyUser toma un parámetro de tipo NotifyType. Hablaremos de eso en la siguiente subsección.
Cualquier miembro al que quiera enlazar datos también debe declararse en IDL (ya sea que use {x:Bind} o {Binding}). Para obtener más información, consulta Enlace de datos.
Estamos progresando: estamos desarrollando una lista de los miembros que se van a agregar, y que no se van a agregar, al MainPage.idl archivo. Pero todavía tenemos que analizar la propiedad Scenarios y el tipo NotifyType . Así que hagamos eso a continuación.
IDL para los tipos Scenario y NotifyType
La clase Scenario se define en SampleConfiguration.cs. Tenemos una decisión de tomar sobre cómo migrar esa clase a C++/WinRT. De forma predeterminada, es probable que lo conviertamos en un C++ structordinario. Pero si scenario se usa en archivos binarios o para interoperar con el marco XAML, debe declararse en IDL como un tipo de Windows Runtime.
Al estudiar el código fuente de C#, encontramos que el escenario se usa en este contexto.
<ListBox x:Name="ScenarioControl" ... >
var itemCollection = new List<Scenario>();
int i = 1;
foreach (Scenario s in scenarios)
{
itemCollection.Add(new Scenario { Title = $"{i++}) {s.Title}", ClassType = s.ClassType });
}
ScenarioControl.ItemsSource = itemCollection;
Se asigna una colección de objetos Scenario a la propiedad ItemsSource de un ListBox (que es un control de elementos). Dado que El escenarionecesita interoperar con XAML, debe ser un tipo de Windows Runtime. Por lo tanto, debe definirse en IDL. Definir el tipo Scenario en IDL hace que el sistema de compilación de C++/WinRT genere por usted una definición del código fuente de Scenario en un archivo de encabezado interno (cuyo nombre y ubicación no son importantes para este tutorial).
Y recordará que MainPage.Scenarios es una colección de objetos Scenario , que acabamos de decir que deben estar en IDL. Por ese motivo, mainPage.Scenarios también debe declararse en la IDL.
NotifyType es un enum declarado en el MainPage.xaml.cs de C#. Dado que pasamos NotifyType a un método que pertenece a la clase en tiempo de ejecución MainPage, NotifyType también debe ser un tipo de Windows Runtime; y debe definirse en MainPage.idl.
Ahora vamos a agregar al MainPage.idl archivo los nuevos tipos y el nuevo miembro de Mainpage que hemos decidido declarar en IDL. Al mismo tiempo, eliminaremos del IDL los miembros provisionales de Mainpage que nos dio la plantilla de proyecto de Visual Studio.
Por lo tanto, en el proyecto de C++/WinRT, abra MainPage.idly edítelo para que tenga el aspecto de la lista siguiente. Tenga en cuenta que una de las modificaciones consiste en cambiar el nombre del espacio de nombres de Clipboard a SDKTemplate. Si lo desea, puede reemplazar todo el contenido de MainPage.idl por el código siguiente. Otro ajuste que se debe tener en cuenta es que estamos cambiando el nombre de Scenario::ClassType a Scenario::ClassName.
// MainPage.idl
namespace SDKTemplate
{
struct Scenario
{
String Title;
Microsoft.UI.Xaml.Interop.TypeName ClassName;
};
enum NotifyType
{
StatusMessage,
ErrorMessage
};
[default_interface]
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
MainPage();
static MainPage Current{ get; };
static String FEATURE_NAME{ get; };
static Windows.Foundation.Collections.IVector<Scenario> scenarios{ get; };
void NotifyUser(String strMessage, NotifyType type);
};
}
Note
Para obtener más información sobre el contenido de un .idl archivo en un proyecto de C++/WinRT, consulta Microsoft Interface Definition Language 3.0.
Con su propio trabajo de portabilidad, es posible que no desee o necesite cambiar el nombre del espacio de nombres como lo hicimos anteriormente. Lo hacemos aquí solo porque el espacio de nombres predeterminado del proyecto de C# que vamos a portar es SDKTemplate; mientras que el nombre del proyecto y del ensamblado es Clipboard.
Pero, a medida que avancemos con esta adaptación en este tutorial guiado, cambiaremos cada aparición del nombre del espacio de nombres Clipboard por SDKTemplate en el código fuente. También hay un lugar en las propiedades del proyecto de C++/WinRT donde aparece el nombre del espacio de nombres Clipboard, así que aprovecharemos para cambiarlo ahora.
En Visual Studio, para el proyecto de C++/WinRT, establezca la propiedad del proyecto Common Properties>C++/WinRT>Root Namespace en el valor SDKTemplate.
Guarde el IDL y vuelva a generar los archivos stub.
El tema controles XAML; enlazar a una propiedad de C++/WinRT presenta la noción de archivos auxiliares y muestra un tutorial de ellos en acción. También hemos mencionado códigos auxiliares anteriormente en este tema cuando mencionamos que el sistema de compilación de C++/WinRT convierte el contenido de los .idl archivos en Windows Metadatos y, a continuación, a partir de esos metadatos, una herramienta denominada cppwinrt.exe genera códigos auxiliares en los que puede basar la implementación.
Cada vez que agregue, quite o cambie algo en su IDL y compile, el sistema de compilación actualiza las implementaciones de código auxiliar en esos archivos de código auxiliar. Por lo tanto, cada vez que cambie su IDL y compile, se recomienda ver esos archivos auxiliares, copiar las firmas modificadas y pegarlas en el proyecto. Daremos ejemplos y detalles más específicos de cómo hacerlo exactamente en un momento. Pero la ventaja de hacerlo es proporcionar una forma libre de errores de saber en todo momento cuál debe ser la forma del tipo de implementación y cuál debe ser la firma de sus métodos.
En este momento del tutorial, hemos terminado de editar el MainPage.idl archivo por el momento, por lo que debe guardarlo ahora. El proyecto no se compilará por completo de momento, pero aun así compilar ahora resulta útil porque regenera los archivos stub de MainPage. Por lo tanto, compile el proyecto ahora y ignore los errores de compilación.
Para este proyecto de C++/WinRT, los archivos auxiliares se generan en la carpeta \Clipboard\Clipboard\Generated Files\sources. Los encontrarás allí cuando la compilación parcial haya terminado (de nuevo, como era de esperar, la compilación no se completará del todo correctamente. Pero el paso que nos interesa —generar stubs— sí se habrá completado correctamente). Los archivos que nos interesan son MainPage.h y MainPage.cpp.
En esos dos archivos stub, verás nuevas implementaciones stub de los miembros de MainPage que agregamos al IDL (Current y FEATURE_NAME, por ejemplo). Le conviene copiar esas implementaciones provisionales en los archivos MainPage.h y MainPage.cpp que ya están en el proyecto. Al mismo tiempo, al igual que hicimos con el IDL, quitaremos de esos archivos existentes los miembros de marcador de posición de Mainpage que la plantilla de proyecto de Visual Studio nos dio (la propiedad ficticia denominada MyProperty y el controlador de eventos denominado ClickHandler).
De hecho, el único miembro de la versión actual de MainPage que queremos mantener es el constructor.
Una vez que hayas copiado los nuevos miembros de los archivos stub, eliminado los miembros que no queremos y actualizado el espacio de nombres, los archivos MainPage.h y MainPage.cpp de tu proyecto deberían tener un aspecto similar al de los fragmentos de código que aparecen a continuación. Observe que hay dos tipos MainPage . Uno en el espacio de nombres implementation, y otro en el espacio de nombres factory_implementation. El único cambio que hemos realizado en el de factory_implementation es añadir SDKTemplate a su espacio de nombres.
// MainPage.h
#pragma once
#include "MainPage.g.h"
namespace winrt::SDKTemplate::implementation
{
struct MainPage : MainPageT<MainPage>
{
MainPage();
static SDKTemplate::MainPage Current();
static hstring FEATURE_NAME();
static Windows::Foundation::Collections::IVector<SDKTemplate::Scenario> scenarios();
void NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type);
};
}
namespace winrt::SDKTemplate::factory_implementation
{
struct MainPage : MainPageT<MainPage, implementation::MainPage>
{
};
}
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"
namespace winrt::SDKTemplate::implementation
{
MainPage::MainPage()
{
InitializeComponent();
}
SDKTemplate::MainPage MainPage::Current()
{
throw hresult_not_implemented();
}
hstring MainPage::FEATURE_NAME()
{
throw hresult_not_implemented();
}
Windows::Foundation::Collections::IVector<SDKTemplate::Scenario> MainPage::scenarios()
{
throw hresult_not_implemented();
}
void MainPage::NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
throw hresult_not_implemented();
}
}
En el caso de las cadenas, C# usa System.String. Consulte el método MainPage.NotifyUser para obtener un ejemplo. En nuestro IDL, declaramos una cadena con String y cuando la cppwinrt.exe herramienta genera código de C++/WinRT para nosotros, usa el tipo winrt::hstring . Cada vez que encontremos una cadena en código C#, la convertiremos a winrt::hstring. Para obtener más información, consulta Control de cadenas en C++/WinRT.
Para obtener una explicación de los parámetros const& de las firmas de método, consulte Paso de parámetros.
Actualizar todas las declaraciones y referencias restantes del espacio de nombres, y compilar
Antes de compilar el proyecto de C++/WinRT, busque cualquier declaración del espacio de nombres Clipboard (y cualquier referencia a este) y cámbielas a SDKTemplate.
-
MainPage.xamlyApp.xaml. El espacio de nombres aparece en los valores de los atributosx:Classyxmlns:local. -
App.idl. -
App.h. -
App.cpp. Hay dosusing namespacedirectivas (busque la subcadenausing namespace Clipboard) y dos calificaciones del tipo MainPage (busqueClipboard::MainPage). Esos necesitan cambiar.
Como hemos quitado el controlador de eventos de MainPage, entra también en MainPage.xaml y elimina el elemento Button del código de marcado.
Guarde todos los archivos. Limpie la solución (Compilar>Limpiar solución) y luego compílela. Si has seguido todos los cambios hasta este punto exactamente como se indica, la compilación debería completarse correctamente.
Implementación de los miembros mainPage declarados en IDL
El constructor, Current y FEATURE_NAME
Este es el código pertinente (del proyecto de C#) que necesitamos portar.
<!-- MainPage.xaml -->
...
<TextBlock x:Name="SampleTitle" ... />
...
// MainPage.xaml.cs
...
public sealed partial class MainPage : Page
{
public static MainPage Current;
public MainPage()
{
InitializeComponent();
Current = this;
SampleTitle.Text = FEATURE_NAME;
}
...
}
...
// SampleConfiguration.cs
...
public partial class MainPage : Page
{
public const string FEATURE_NAME = "Clipboard C# sample";
...
}
...
Pronto, volveremos a usar MainPage.xaml en su totalidad (copiandolo). Por ahora (a continuación), agregaremos temporalmente un elemento TextBlock , con el nombre adecuado, en el MainPage.xaml del proyecto de C++/WinRT.
FEATURE_NAME es un campo estático de MainPage (un campo de C# const es esencialmente estático en su comportamiento), definido en SampleConfiguration.cs. En el caso de C++/WinRT, en lugar de un campo (estático), lo convertiremos en la expresión de C++/WinRT de una propiedad de solo lectura (estática). La manera en que C++/WinRT expresa el getter de una propiedad es como una función que devuelve el valor de la propiedad y no toma ningún parámetro (un accesor). Por lo tanto, el campo estático de C# FEATURE_NAME se convierte en la función de acceso estática de C++/WinRT FEATURE_NAME (en este caso, que devuelve la cadena literal).
Por cierto, haríamos lo mismo si estuviéramos portando una propiedad de solo lectura de C#. Para una propiedad que se puede escribir en C#, la forma de C++/WinRT de expresar un establecedor de propiedades es como una void función que toma el valor de propiedad como un parámetro (un mutador). En cualquier caso, si el campo o la propiedad de C# es estático, el accesor y/o mutador de C++/WinRT también lo serán.
Current es un campo estático (no constante) de MainPage. De nuevo, convertiremos la versión en C++/WinRT de esta en una propiedad de solo lectura y volveremos a hacer que sea estática. Donde FEATURE_NAME es constante, Current no. Por lo tanto, en C++/WinRT necesitaremos un campo de respaldo y nuestro descriptor de acceso lo devolverá. Por lo tanto, en el proyecto de C++/WinRT, declararemos en MainPage.h un campo estático privado denominado current, definiremos o inicializaremos el actual en MainPage.cpp (porque tiene duración de almacenamiento estático) y accederemos a él a través de una función de descriptor de acceso estático pública denominada Current.
El propio constructor realiza un par de asignaciones, que son sencillas de portar.
En el proyecto de C++/WinRT, agregue un nuevo elemento Visual C++>Code>Archivo C++ (.cpp) con el nombre SampleConfiguration.cpp.
Edite MainPage.xaml, MainPage.h, MainPage.cppy SampleConfiguration.cpp para que coincida con las listas siguientes.
<!-- MainPage.xaml -->
...
<StackPanel ...>
<TextBlock x:Name="SampleTitle" />
</StackPanel>
...
// MainPage.h
...
namespace winrt::SDKTemplate::implementation
{
struct MainPage : MainPageT<MainPage>
{
...
static SDKTemplate::MainPage Current() { return current; }
...
private:
static SDKTemplate::MainPage current;
...
};
...
}
// MainPage.cpp
...
namespace winrt::SDKTemplate::implementation
{
SDKTemplate::MainPage MainPage::current{ nullptr };
MainPage::MainPage()
{
InitializeComponent();
MainPage::current = *this;
SampleTitle().Text(FEATURE_NAME());
}
...
}
// SampleConfiguration.cpp
#include "pch.h"
#include "MainPage.h"
using namespace winrt;
using namespace SDKTemplate;
hstring implementation::MainPage::FEATURE_NAME()
{
return L"Clipboard C++/WinRT Sample";
}
Además, asegúrese de eliminar los cuerpos de función existentes de MainPage.cpp para MainPage::Current() y MainPage::FEATURE_NAME(), ya que ahora estamos definiendo esos métodos en otro lugar.
Como puede ver, MainPage::current se declara como de tipo SDKTemplate::MainPage, que es el tipo proyectado. No es del tipo SDKTemplate::implementation::MainPage, que es el tipo de implementación. El tipo proyectado es el diseñado para usarse dentro del propio proyecto para la interoperabilidad con XAML o entre binarios. El tipo de implementación es el que se usa para implementar las funcionalidades que has expuesto en el tipo proyectado. Dado que la declaración de MainPage::current (en MainPage.h) aparece dentro del espacio de nombres de implementación (winrt::SDKTemplate::implementation), un MainPage sin calificar se habría referido al tipo de implementación. Por lo tanto, calificamos con SDKTemplate:: para estar claro que queremos que MainPage::current sea una instancia del tipo proyectado winrt::SDKTemplate::MainPage.
En el constructor, hay algunos puntos relacionados con MainPage::current = *this; que merecen una explicación.
- Cuando se utiliza el puntero
thisdentro de un elemento miembro del tipo de implementación, el punterothises, por supuesto, un puntero al tipo de implementación. - Para convertir el puntero
thisen el tipo proyectado correspondiente, hay que desreferenciarlo. Siempre que genere el tipo de implementación a partir de IDL (como tenemos aquí), el tipo de implementación tiene un operador de conversión que se convierte en su tipo proyectado. Por eso funciona la asignación aquí.
Para obtener más información sobre esos detalles, consulte Creación de instancias y devolución de tipos e interfaces de implementación.
También en el constructor está SampleTitle().Text(FEATURE_NAME());. El SampleTitle() elemento es una llamada a una función de descriptor de acceso simple denominada SampleTitle, que devuelve el TextBlock que agregamos al XAML. Cada vez que x:Name un elemento XAML, el compilador de XAML genera un accesor para usted con el nombre del elemento. La parte .Text(...) llama a la función mutadora Text del objeto TextBlock que devolvió el accesor SampleTitle. Y FEATURE_NAME() llama a nuestra función de acceso estática MainPage::FEATURE_NAME para devolver la cadena literal. Por completo, esa línea de código establece la propiedad Text del TextBlock denominado SampleTitle.
Tenga en cuenta que, dado que las cadenas son de caracteres anchos en Windows Runtime, para portar un literal de cadena lo prefijamos con el prefijo de codificación de caracteres anchos L. Así, cambiamos (por ejemplo) "un literal de cadena" por L"un literal de cadena". Consulte también Literales de cadena ancha.
Escenarios
Este es el código de C# pertinente que necesitamos portar.
// MainPage.xaml.cs
...
public sealed partial class MainPage : Page
{
...
public List<Scenario> Scenarios
{
get { return this.scenarios; }
}
...
}
...
// SampleConfiguration.cs
...
public partial class MainPage : Page
{
...
List<Scenario> scenarios = new List<Scenario>
{
new Scenario() { Title = "Copy and paste text", ClassType = typeof(CopyText) },
new Scenario() { Title = "Copy and paste an image", ClassType = typeof(CopyImage) },
new Scenario() { Title = "Copy and paste files", ClassType = typeof(CopyFiles) },
new Scenario() { Title = "Other Clipboard operations", ClassType = typeof(OtherScenarios) }
};
...
}
...
A partir de nuestra investigación anterior, sabemos que esta colección de objetos Scenario se muestra en un ListBox. En C++/WinRT, hay límites para el tipo de colección que podemos asignar a la propiedad ItemsSource de un control items. La colección debe ser un vector o un vector observable, y sus elementos deben ser uno de los siguientes:
- O bien clases en tiempo de ejecución, o
- IInspectable.
Para el caso de IInspectable, si los elementos no son en sí clases de tiempo de ejecución, entonces esos elementos deben ser de un tipo que pueda encapsularse y desencapsularse en y desde IInspectable. Y eso significa que tienen que ser tipos de Windows Runtime (consulte Encapsulado y desempaquetado de los valores en IInspectable).
En este caso práctico, no hicimos que Scenario sea una clase en tiempo de ejecución. Sin embargo, sigue siendo una opción razonable. Y habrá casos en tu propio trabajo de adaptación en los que una clase de tiempo de ejecución será sin duda la mejor opción. Por ejemplo, si necesitas hacer que el tipo de elemento sea observable (consulta controles XAML; enlazar a una propiedad C++/WinRT), o si el elemento necesita tener métodos por cualquier otro motivo, y es más que un conjunto de miembros de datos.
Puesto que, en este tutorial, no vamos con una clase en tiempo de ejecución para el tipo de escenario , entonces debemos pensar en el boxing. Si hubiéramos hecho Scenario un tipo normal de C++ struct, entonces no podríamos encapsularlo. Pero declaramos Scenario como un struct en IDL, y por eso podemos encapsularlo.
Nos queda la opción de encapsular Scenario con antelación, o esperar hasta justo antes de asignarlos a ItemsSource y encapsularlos justo a tiempo. Estas son algunas consideraciones relacionadas con esas dos opciones.
- Boxing con antelación. Para esta opción, nuestro miembro de datos es una colección de IInspectable lista para asignar a la interfaz de usuario. Durante la inicialización, encapsulamos los objetos Scenario en ese miembro de datos. Solo necesitamos una copia de esa colección, pero tenemos que desenboxar un elemento cada vez que necesitamos leer sus campos.
- Boxing justo a tiempo. Para esta opción, nuestro campo de datos es una colección de Escenario. Cuando llega el momento de asignarlos a la interfaz de usuario, convertimos los objetos Scenario del miembro de datos en una nueva colección de IInspectable. Podemos leer los campos de los elementos del miembro de datos sin desempaquetar, pero necesitamos dos copias de la colección.
Como puedes ver, para una pequeña colección como esta, las ventajas y desventajas se compensan bastante. Por lo tanto, para este caso práctico, iremos con la opción Just-In-Time.
El miembro scenarios es un campo de MainPage, definido e inicializado en SampleConfiguration.cs. Y Scenarios es una propiedad de solo lectura de MainPage, definida en MainPage.xaml.cs (e implementada para devolver simplemente el campo escenarios ). Haremos algo similar en el proyecto de C++/WinRT; pero haremos que los dos miembros sean estáticos (ya que solo necesitamos una instancia en la aplicación; y para que podamos acceder a ellos sin necesidad de una instancia de clase). Y los denominaremos escenariosInner y escenarios, respectivamente. Declararemos scenariosInner en MainPage.h. Y, dado que tiene una duración de almacenamiento estática, definiremos o inicializaremos en un .cpp archivo (SampleConfiguration.cppen este caso).
Edite MainPage.h y SampleConfiguration.cpp para que coincida con las descripciones siguientes.
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
static Windows::Foundation::Collections::IVector<Scenario> scenarios() { return scenariosInner; }
...
private:
static winrt::Windows::Foundation::Collections::IVector<Scenario> scenariosInner;
...
};
// SampleConfiguration.cpp
...
using namespace Windows::Foundation::Collections;
...
IVector<Scenario> implementation::MainPage::scenariosInner = winrt::single_threaded_observable_vector<Scenario>(
{
Scenario{ L"Copy and paste text", xaml_typename<SDKTemplate::CopyText>() },
Scenario{ L"Copy and paste an image", xaml_typename<SDKTemplate::CopyImage>() },
Scenario{ L"Copy and paste files", xaml_typename<SDKTemplate::CopyFiles>() },
Scenario{ L"History and roaming", xaml_typename<SDKTemplate::HistoryAndRoaming>() },
Scenario{ L"Other Clipboard operations", xaml_typename<SDKTemplate::OtherScenarios>() },
});
Además, asegúrese de eliminar el cuerpo de la función existente de MainPage.cpp para MainPage::scenarios(), ya que ahora estamos definiendo ese método en el archivo de encabezado.
Como puede ver, en SampleConfiguration.cpp, inicializamos el miembro de datos estático scenariosInner mediante una llamada a una función auxiliar de C++/WinRT llamada winrt::single_threaded_observable_vector. Esa función crea un nuevo objeto de colección Windows Runtime para nosotros y lo devuelve como una interfaz IObservableVector. Dado que, en este ejemplo, la colección no es observable (no es necesario, porque no agrega ni quita elementos después de la inicialización), podríamos haber optado por llamar a winrt::single_threaded_vector. Esa función devuelve la colección como una interfaz IVector .
Para obtener más información sobre las colecciones y el enlace a ellas, consulta Controles de elementos XAML; enlazar a una colección de C++/WinRT y Colecciones con C++/WinRT.
El código de inicialización que acaba de agregar hace referencia a tipos que todavía no están en el proyecto (por ejemplo, winrt::SDKTemplate::CopyText. Para solucionarlo, vamos a continuar y vamos a agregar cinco nuevas páginas XAML en blanco al proyecto.
Agregar cinco páginas XAML nuevas en blanco
Agregue al proyecto un nuevo elemento Visual C++>Blank Page (C++/WinRT) (asegúrese de que sea la plantilla de elemento Blank Page (C++/WinRT) y no la de Blank Page). Asígnalo CopyText. La nueva página XAML se define dentro del espacio de nombres SDKTemplate , que es lo que queremos.
Repita el proceso anterior otras cuatro veces y asigne a las páginas XAML los nombres CopyImage, CopyFiles, HistoryAndRoaming y OtherScenarios.
Ahora podrá volver a compilar, si lo desea.
NotifyUser
En el proyecto de C#, encontrará la implementación del método MainPage.NotifyUser en MainPage.xaml.cs.
MainPage.NotifyUser tiene una dependencia en MainPage.UpdateStatus y ese método a su vez tiene dependencias en elementos XAML que aún no hemos migrado. Por lo tanto, por ahora solo se eliminará un método UpdateStatus en el proyecto de C++/WinRT y lo migraremos más adelante.
Este es el código de C# pertinente que necesitamos portar.
// MainPage.xaml.cs
...
public void NotifyUser(string strMessage, NotifyType type)
if (Dispatcher.HasThreadAccess)
{
UpdateStatus(strMessage, type);
}
else
{
var task = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => UpdateStatus(strMessage, type));
}
private void UpdateStatus(string strMessage, NotifyType type) { ... }{
...
NotifyUser envía actualizaciones de la interfaz de usuario al subproceso principal. En WinUI 3, se usa Microsoft.UI.Dispatching.DispatcherQueue en lugar de la antigua CoreDispatcher. En C++/WinRT, siempre que quieras usar un tipo de un espacio de nombres de Windows o Microsoft, debes incluir el archivo de encabezado del espacio de nombres C++/WinRT correspondiente (para obtener más información sobre eso, consulta Introducción a C++/WinRT). En este caso, como verá en la lista de código siguiente, el encabezado es winrt/Microsoft.UI.Dispatching.hy lo incluiremos en pch.h.
UpdateStatus es privado. Por lo tanto, haremos que sea un método privado en nuestro tipo de implementación MainPage . UpdateStatus no está pensado para llamarse en la clase en tiempo de ejecución, por lo que no lo declararemos en IDL.
Después de migrar MainPage.NotifyUser y destubar MainPage.UpdateStatus, esto es lo que tenemos en el proyecto de C++/WinRT. Después de esta lista de código, examinaremos algunos de los detalles.
// pch.h
...
#include <winrt/Microsoft.UI.Dispatching.h>
...
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
void NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type);
...
private:
void UpdateStatus(hstring const& strMessage, SDKTemplate::NotifyType const& type);
...
};
// MainPage.cpp
...
void MainPage::NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
if (DispatcherQueue().HasThreadAccess())
{
UpdateStatus(strMessage, type);
}
else
{
DispatcherQueue().TryEnqueue([strMessage, type, this]()
{
UpdateStatus(strMessage, type);
});
}
}
void MainPage::UpdateStatus(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
throw hresult_not_implemented();
}
...
En C#, puedes usar la notación de punto para acceder a propiedades anidadas. Por lo tanto, el tipo MainPage de C# puede tener acceso a su propia propiedad Dispatcher con la sintaxis Dispatcher. Y C# puede acceder aún más a ese valor con sintaxis como Dispatcher.HasThreadAccess. En C++/WinRT, las propiedades se implementan como funciones de acceso, por lo que la sintaxis solo difiere en que hay que añadir paréntesis en cada llamada a función.
| C# | C++/WinRT |
|---|---|
Dispatcher.HasThreadAccess |
DispatcherQueue().HasThreadAccess() |
Cuando la versión de C# de NotifyUser llama a Dispatcher.RunAsync, el equivalente de WinUI 3 usa DispatcherQueue.TryEnqueue. La versión de C++/WinRT implementa el delegado de devolución de llamada como una función lambda. En C++/WinRT, capturamos los dos parámetros que vamos a usar, así como el this puntero (ya que vamos a llamar a una función miembro). Hay más información sobre la implementación de delegados como lambdas y ejemplos de código en el tema Controlar eventos mediante delegados en C++/WinRT.
Implementar los miembros restantes de MainPage
Vamos a hacer una lista completa de los miembros de MainPage (implementados en MainPage.xaml.cs y SampleConfiguration.cs) para que podamos ver cuáles hemos migrado hasta ahora, y cuáles todavía no lo están.
| Miembro | Access | Status |
|---|---|---|
| Constructor de MainPage | public |
Adaptado |
| Propiedad actual | public |
Adaptado |
| FEATURE_NAME propiedad | public |
Adaptado |
| Propiedad IsClipboardContentChangedEnabled | public |
No iniciado |
| Scenarios (propiedad) | public |
Adaptado |
| Método BuildClipboardFormatsOutputString | public |
No iniciado |
| Método DisplayToast | public |
No iniciado |
| Método EnableClipboardContentChangedNotifications | public |
No iniciado |
| Método NotifyUser | public |
Adaptado |
| Método OnNavigatedTo | protected |
No iniciado |
| campo isApplicationWindowActive | private |
No iniciado |
| campo needToPrintClipboardFormat | private |
No iniciado |
| campo escenarios | private |
Adaptado |
| método Button_Click | private |
No iniciado |
| Método DisplayChangedFormats | private |
No iniciado |
| método Footer_Click | private |
No iniciado |
| Método HandleClipboardChanged | private |
No iniciado |
| Método OnClipboardChanged | private |
No iniciado |
| Método OnWindowActivated | private |
No iniciado |
| método ScenarioControl_SelectionChanged | private |
No iniciado |
| Método UpdateStatus | private |
Implementado provisionalmente |
Entonces, hablaremos de los miembros que aún no se han portado en las siguientes subsecciones.
Note
De vez en cuando, vamos a encontrar referencias en el código fuente a los elementos de la interfaz de usuario en el marcado XAML (en MainPage.xaml). A medida que llegamos a estas referencias, vamos a solucionarlos temporalmente agregando elementos de marcador de posición simples al XAML. De este modo, el proyecto seguirá compilando después de cada subsección. La alternativa es resolver las referencias copiando el contenido completo de MainPage.xaml del proyecto de C# al proyecto de C++/WinRT ahora. Pero si hacemos eso, pasará mucho tiempo antes de que podamos hacer una pausa y volver a compilar (lo que podría ocultar cualquier errata u otros errores que cometamos por el camino).
Una vez que hayamos terminado de migrar el código imperativo para la clase MainPage, copiaremos el contenido del archivo XAML y nos aseguraremos de que el proyecto seguirá compilando.
IsClipboardContentChangedEnabled
Se trata de una propiedad get-set de C# que tiene falsecomo valor predeterminado . Es un miembro de MainPage y se define en SampleConfiguration.cs.
Para C++/WinRT, necesitaremos una función de acceso, una función mutadora y un miembro de datos de respaldo que actúe como campo. Dado que IsClipboardContentChangedEnabled representa el estado de uno de los escenarios del ejemplo, en lugar del estado de MainPage en sí, crearemos los nuevos miembros en un nuevo tipo de utilidad denominado SampleState. Y lo implementaremos en nuestro SampleConfiguration.cpp archivo de código fuente y haremos que los miembros static (ya que solo necesitamos una instancia en la aplicación; y para que podamos acceder a ellos sin necesidad de una instancia de clase).
Para acompañar nuestro SampleConfiguration.cpp en el proyecto de C++/WinRT, agregue un nuevo elemento Visual C++>Code>Header File (.h) con el nombre SampleConfiguration.h. Edite SampleConfiguration.h y SampleConfiguration.cpp para que coincida con las descripciones siguientes.
// SampleConfiguration.h
#pragma once
#include "pch.h"
namespace winrt::SDKTemplate
{
struct SampleState
{
static bool IsClipboardContentChangedEnabled();
static void IsClipboardContentChangedEnabled(bool checked);
private:
static bool isClipboardContentChangedEnabled;
};
}
// SampleConfiguration.cpp
...
#include "SampleConfiguration.h"
...
bool SampleState::isClipboardContentChangedEnabled = false;
...
bool SampleState::IsClipboardContentChangedEnabled()
{
return isClipboardContentChangedEnabled;
}
void SampleState::IsClipboardContentChangedEnabled(bool checked)
{
if (isClipboardContentChangedEnabled != checked)
{
isClipboardContentChangedEnabled = checked;
}
}
De nuevo, un campo con static almacenamiento (como SampleState::isClipboardContentChangedEnabled) debe definirse una vez en la aplicación y un .cpp archivo es un buen lugar para eso (SampleConfiguration.cpp en este caso).
BuildClipboardFormatsOutputString
Este método es un miembro público de MainPage y se define en SampleConfiguration.cs.
// SampleConfiguration.cs
...
public string BuildClipboardFormatsOutputString()
{
DataPackageView clipboardContent = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();
StringBuilder output = new StringBuilder();
if (clipboardContent != null && clipboardContent.AvailableFormats.Count > 0)
{
output.Append("Available formats in the clipboard:");
foreach (var format in clipboardContent.AvailableFormats)
{
output.Append(Environment.NewLine + " * " + format);
}
}
else
{
output.Append("The clipboard is empty");
}
return output.ToString();
}
...
En C++/WinRT, crearemos BuildClipboardFormatsOutputString un método estático público de SampleState. Podemos hacerlo static porque no tiene acceso a ningún miembro de instancia.
Para usar los tipos Clipboard y DataPackageView en C++/WinRT, es necesario incluir el archivo winrt/Windows.ApplicationModel.DataTransfer.hde encabezado de espacio de nombres de C++/WinRT Windows .
En C#, la propiedad DataPackageView.AvailableFormats es un IReadOnlyList, por lo que podemos acceder a la propiedad Count de eso. En C++/WinRT, la función de descriptor de acceso DataPackageView::AvailableFormats devuelve un IVectorView, que tiene una función de descriptor de acceso Size a la que se puede llamar.
Para migrar el uso del tipo System.Text.StringBuilder de C#, usaremos el tipo estándar de C++ std::wostringstream. Ese tipo es un flujo de salida para cadenas de caracteres anchos (y, para usarlo, tendremos que incluir el archivo de cabecera sstream). En lugar de usar un método Append como lo hace con un stringBuilder, se usa el operador de inserción (<<) con una secuencia de salida como wostringstream. Para obtener más información, consulta programación de iostream y Formato de cadenas de C++/WinRT.
El código de C# construye un stringBuilder con la new palabra clave . En C#, los objetos son tipos de referencia de forma predeterminada, declarados en el montón con new. En C++, los objetos son tipos de valor de forma predeterminada, declarados en la pila (sin usar new). Por lo tanto, vamos a migrar StringBuilder output = new StringBuilder(); a C++/WinRT como simplemente std::wostringstream output;.
La palabra clave de C# var pide al compilador que infiere un tipo. Se debe migrar var a auto en C++/WinRT. Pero en C++/WinRT, hay casos en los que (para evitar copias) quieres una referencia a un tipo inferido (o deducido) y expresas una referencia lvalue a un tipo deducido con auto&. También hay casos en los que se quiere un tipo especial de referencia que se enlaza correctamente tanto si se inicializa con un lvalue como con un rvalue. Y lo expresas con auto&&. Esa es la forma que se usa en el bucle for del código adaptado que aparece a continuación. Para obtener una introducción a los valores lvalues y rvalues, vea Categorías de valor y referencias a ellos.
Edite pch.h, SampleConfiguration.hy SampleConfiguration.cpp para que coincida con las listas siguientes.
// pch.h
...
#include <sstream>
#include "winrt/Windows.ApplicationModel.DataTransfer.h"
...
// SampleConfiguration.h
...
struct SampleState
{
static hstring BuildClipboardFormatsOutputString();
...
}
...
// SampleConfiguration.cpp
...
using namespace Windows::ApplicationModel::DataTransfer;
...
hstring SampleState::BuildClipboardFormatsOutputString()
{
DataPackageView clipboardContent{ Clipboard::GetContent() };
std::wostringstream output;
if (clipboardContent && clipboardContent.AvailableFormats().Size() > 0)
{
output << L"Available formats in the clipboard:";
for (auto&& format : clipboardContent.AvailableFormats())
{
output << std::endl << L" * " << std::wstring_view(format);
}
}
else
{
output << L"The clipboard is empty";
}
return hstring{ output.str() };
}
Note
La sintaxis de la línea de código DataPackageView clipboardContent{ Clipboard::GetContent() }; usa una característica de C++ estándar moderno denominada inicialización uniforme, con su uso característico de corchetes en lugar de un = signo. Esa sintaxis deja claro que se está llevando a cabo la inicialización, en lugar de la asignación. Si prefiere la forma sintáctica que parece una asignación (aunque en realidad no lo es), puede reemplazar la sintaxis anterior por su equivalente DataPackageView clipboardContent = Clipboard::GetContent();. Sin embargo, es una buena idea familiarizarse con ambas formas de expresar la inicialización, ya que es probable que vea ambos usados con frecuencia en el código que encuentre.
DisplayToast
DisplayToast es un método estático público de la clase MainPage de C# y lo encontrará definido en SampleConfiguration.cs. En C++/WinRT, lo convertiremos en un método estático público de SampleState.
Ya hemos encontrado la mayoría de los detalles y técnicas que son relevantes para migrar este método. Un aspecto nuevo que conviene señalar es que se convierte un literal de cadena textual de C# (@) en un literal de cadena sin formato estándar de C++ (LR).
Además, al hacer referencia a los tipos ToastNotification y XmlDocument en C++/WinRT, puede calificarlos por nombre de espacio de nombres, o bien puede editar SampleConfiguration.cpp y agregar using namespace directivas como el ejemplo siguiente.
using namespace Windows::UI::Notifications;
Tiene la misma opción al hacer referencia al tipo XmlDocument y siempre que haga referencia a cualquier otro tipo de Windows Runtime.
Aparte de esos elementos, siga las mismas instrucciones que ha hecho anteriormente para realizar los pasos siguientes.
- Declare el método en
SampleConfiguration.hy definalo enSampleConfiguration.cpp. - Edite
pch.hpara incluir los archivos de encabezado necesarios del espacio de nombres de Windows para C++/WinRT. - Construya objetos de C++/WinRT en la pila, no en el montón.
- Sustituya las llamadas a los accesores get de propiedades por sintaxis de llamada a función (
()).
Una causa muy común de los errores del compilador o del enlazador es olvidar incluir los archivos de encabezado del espacio de nombres Windows de C++/WinRT necesarios. Para más información sobre un posible error, consulte C3779: ¿Por qué el compilador me muestra el error «consume_Something: una función que devuelve 'auto' no se puede usar antes de que se defina»?
Si quiere seguir la guía paso a paso y adaptar usted mismo DisplayToast, puede comparar sus resultados con el código de la versión en C++/WinRT del archivo ZIP del código fuente del ejemplo Clipboard sample que descargó.
EnableClipboardContentChangedNotifications
EnableClipboardContentChangedNotifications es un método estático público de la clase MainPage de C# y se define en SampleConfiguration.cs.
// SampleConfiguration.cs
...
public bool EnableClipboardContentChangedNotifications(bool enable)
{
if (IsClipboardContentChangedEnabled == enable)
{
return false;
}
IsClipboardContentChangedEnabled = enable;
if (enable)
{
Clipboard.ContentChanged += OnClipboardChanged;
Window.Current.Activated += OnWindowActivated;
}
else
{
Clipboard.ContentChanged -= OnClipboardChanged;
Window.Current.Activated -= OnWindowActivated;
}
return true;
}
...
private void OnClipboardChanged(object sender, object e) { ... }
private void OnWindowActivated(object sender, WindowActivatedEventArgs e) { ... }
...
En C++/WinRT, lo convertiremos en un método estático público de SampleState.
En C#, se utilizan los operadores += y -= para registrar y anular el registro de delegados de controladores de eventos. En C++/WinRT, tiene varias opciones sintácticas para registrar o revocar un delegado, como se describe en Controlar eventos mediante delegados en C++/WinRT. Pero, en general, consiste en registrar y revocar mediante llamadas a un par de funciones que llevan el nombre del evento. Para registrarse, pase el delegado a la función de registro y recupere un token de revocación como resultado (un winrt::event_token). Para revocarlo, pase ese token a la función de revocación. En este caso, el hander es estático y (como puede ver en la lista de código siguiente), la sintaxis de llamada de función es sencilla.
De hecho, se usan tokens similares, entre bastidores, en C#. Pero el idioma hace que ese detalle sea implícito. C++/WinRT hace que sea explícito.
El tipo de objeto aparece en las firmas del controlador de eventos de C#. En el lenguaje C#, el objeto es un alias para el tipo System.Object de .NET. El equivalente en C++/WinRT es winrt::Windows::Foundation::IInspectable. Así, verá IInspectable en los manejadores de eventos de C++/WinRT.
Edite SampleConfiguration.h y SampleConfiguration.cpp para que coincida con las descripciones siguientes.
// SampleConfiguration.h
...
static bool EnableClipboardContentChangedNotifications(bool enable);
...
private:
...
static event_token clipboardContentChangedToken;
static event_token activatedToken;
static void OnClipboardChanged(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
static void OnWindowActivated(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::WindowActivatedEventArgs const& e);
...
// SampleConfiguration.cpp
...
using namespace Windows::Foundation;
using namespace Microsoft::UI;
using namespace Microsoft::UI::Xaml;
...
event_token SampleState::clipboardContentChangedToken;
event_token SampleState::activatedToken;
...
bool SampleState::EnableClipboardContentChangedNotifications(bool enable)
{
if (isClipboardContentChangedEnabled == enable)
{
return false;
}
IsClipboardContentChangedEnabled(enable);
if (enable)
{
clipboardContentChangedToken = Clipboard::ContentChanged(OnClipboardChanged);
activatedToken = Window::Current().Activated(OnWindowActivated);
}
else
{
Clipboard::ContentChanged(clipboardContentChangedToken);
Window::Current().Activated(activatedToken);
}
return true;
}
void SampleState::OnClipboardChanged(IInspectable const&, IInspectable const&){}
void SampleState::OnWindowActivated(IInspectable const&, WindowActivatedEventArgs const& e){}
Deje los delegados de control de eventos (OnClipboardChanged y OnWindowActivated) como códigos auxiliares por ahora. Ya están en nuestra lista de elementos que adaptar, así que los trataremos más adelante, en subsecciones posteriores.
OnNavigatedTo
OnNavigatedTo es un método protegido de la clase MainPage de C# y se define en MainPage.xaml.cs. Aquí está, junto con el ListBox de XAML al que hace referencia.
<!-- MainPage.xaml -->
...
<ListBox x:Name="ScenarioControl" ... />
...
// MainPage.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Populate the scenario list from the SampleConfiguration.cs file
var itemCollection = new List<Scenario>();
int i = 1;
foreach (Scenario s in scenarios)
{
itemCollection.Add(new Scenario { Title = $"{i++}) {s.Title}", ClassType = s.ClassType });
}
ScenarioControl.ItemsSource = itemCollection;
if (Window.Current.Bounds.Width < 640)
{
ScenarioControl.SelectedIndex = -1;
}
else
{
ScenarioControl.SelectedIndex = 0;
}
}
Es un método importante e interesante, ya que aquí es donde se asigna nuestra colección de objetos Scenario a la interfaz de usuario. El código de C# compila un objeto System.Collections.Generic.List de objetos Scenario y lo asigna a la propiedad ItemsSource de un Control ListBox (que es un control items). Y, en C#, usamos la interpolación de cadenas para compilar el título de cada objeto Scenario (tenga en cuenta el uso del $ carácter especial).
En C++/WinRT, convertiremos OnNavigatedTo en un método público de MainPage. Y agregaremos un elemento ListBox provisional al archivo XAML para que la compilación se complete correctamente. Después de la lista de código, examinaremos algunos de los detalles.
<!-- MainPage.xaml -->
...
<StackPanel ...>
...
<ListBox x:Name="ScenarioControl" />
</StackPanel>
...
// MainPage.h
...
void OnNavigatedTo(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
...
// MainPage.cpp
...
using namespace winrt::Microsoft::UI::Xaml;
using namespace winrt::Microsoft::UI::Xaml::Navigation;
...
void MainPage::OnNavigatedTo(NavigationEventArgs const& /* e */)
{
auto itemCollection = winrt::single_threaded_observable_vector<IInspectable>();
int i = 1;
for (auto s : MainPage::scenarios())
{
s.Title = winrt::to_hstring(i++) + L") " + s.Title;
itemCollection.Append(winrt::box_value(s));
}
ScenarioControl().ItemsSource(itemCollection);
if (Window::Current().Bounds().Width < 640)
{
ScenarioControl().SelectedIndex(-1);
}
else
{
ScenarioControl().SelectedIndex(0);
}
}
...
De nuevo, llamamos a la función winrt::single_threaded_observable_vector , pero esta vez para crear una colección de IInspectable. Eso formó parte de la decisión que tomamos de realizar el encapsulado de nuestros objetos Scenario en el último momento.
Además, en lugar del uso de interpolación de cadenas de C#aquí, usamos una combinación de la función to_hstring y el operador de concatenación de winrt::hstring.
isApplicationWindowActive
En C#, isApplicationWindowActive es un campo privado bool simple que pertenece a la clase MainPage y se define en SampleConfiguration.cs. El valor predeterminado es false. En C++/WinRT, lo convertiremos en un campo estático privado de SampleState (por los motivos que ya hemos descrito) en los SampleConfiguration.h archivos y SampleConfiguration.cpp , con el mismo valor predeterminado.
Ya hemos visto cómo declarar, definir e inicializar un campo estático. Para refrescar la memoria, revise lo que hicimos con el campo isClipboardContentChangedEnabled y haga lo mismo con isApplicationWindowActive.
needToPrintClipboardFormat
El mismo patrón que isApplicationWindowActive (vea el encabezado inmediatamente antes de este).
Button_Click
Button_Click es un método privado (control de eventos) de la clase MainPage de C# y se define en MainPage.xaml.cs. Aquí está, junto con el SplitView XAML al que hace referencia, y el ToggleButton que lo registra.
<!-- MainPage.xaml -->
...
<SplitView x:Name="Splitter" ... />
...
<ToggleButton Click="Button_Click" .../>
...
private void Button_Click(object sender, RoutedEventArgs e)
{
Splitter.IsPaneOpen = !Splitter.IsPaneOpen;
}
Y este es el equivalente, migrado a C++/WinRT. Tenga en cuenta que, en la versión de C++/WinRT, el controlador de eventos es public (como puede ver, lo declara antes de las private:declaraciones). Esto se debe a que un controlador de eventos registrado en el marcado XAML, como este es, debe estar public en C++/WinRT para que el marcado XAML tenga acceso a él. Por otro lado, si registra un controlador de eventos en código imperativo (como hicimos en MainPage::EnableClipboardContentChangedNotifications anteriormente), el controlador de eventos no necesita ser public.
<!-- MainPage.xaml -->
...
<StackPanel ...>
...
<SplitView x:Name="Splitter" />
</StackPanel>
...
// MainPage.h
...
void Button_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& e);
private:
...
// MainPage.cpp
void MainPage::Button_Click(Windows::Foundation::IInspectable const& /* sender */, Microsoft::UI::Xaml::RoutedEventArgs const& /* e */)
{
Splitter().IsPaneOpen(!Splitter().IsPaneOpen());
}
DisplayChangedFormats
En C#, DisplayChangedFormats es un método privado que pertenece a la clase MainPage y se define en SampleConfiguration.cs.
private void DisplayChangedFormats()
{
string output = "Clipboard content has changed!" + Environment.NewLine;
output += BuildClipboardFormatsOutputString();
NotifyUser(output, NotifyType.StatusMessage);
}
En C++/WinRT, lo convertiremos en un campo estático privado de SampleState (no tiene acceso a ningún miembro de instancia), en los SampleConfiguration.h archivos y SampleConfiguration.cpp . El código de C# para este método no usa System.Text.StringBuilder; pero hace suficiente formato de cadena que para la versión de C++/WinRT, este es otro buen lugar para usar std::wostringstream.
En lugar de la propiedad estática System.Environment.NewLine , que se usa en el código de C#, insertaremos el C++ std::endl estándar (un carácter de nueva línea) en el flujo de salida.
// SampleConfiguration.h
...
private:
static void DisplayChangedFormats();
...
// SampleConfiguration.cpp
void SampleState::DisplayChangedFormats()
{
std::wostringstream output;
output << L"Clipboard content has changed!" << std::endl;
output << BuildClipboardFormatsOutputString().c_str();
MainPage::Current().NotifyUser(output.str(), NotifyType::StatusMessage);
}
Hay una pequeña ineficiencia en el diseño de la versión de C++/WinRT anterior. En primer lugar, creamos un std::wostringstream. Pero también llamamos al método BuildClipboardFormatsOutputString (que hemos migrado anteriormente). Ese método crea su propio std::wostringstream. Y convierte su secuencia en un winrt::hstring y lo devuelve. Llamamos a la función hstring::c_str para volver a convertir esa hstring devuelta en una cadena de estilo C y, a continuación, lo insertamos en la secuencia. Sería más eficiente crear solo un std::wostringstream y pasarlo (por referencia), de modo que los métodos puedan insertar cadenas directamente en él.
Eso es lo que hacemos en la versión en C++/WinRT del código fuente del ejemplo Clipboard sample (en el archivo ZIP que descargaste). En ese código fuente, hay un nuevo método estático privado denominado SampleState::AddClipboardFormatsOutputString, que toma y funciona en una referencia a una secuencia de salida. Y, a continuación, los métodos SampleState::D isplayChangedFormats y SampleState::BuildClipboardFormatsOutputString se refactorizarán para llamar a ese nuevo método. Es funcionalmente equivalente a las descripciones de código de este tema, pero es más eficaz.
Footer_Click
Footer_Click es un controlador de eventos asincrónico que pertenece a la clase MainPage de C# y se define en MainPage.xaml.cs. La lista de código siguiente es funcionalmente equivalente al método en el código fuente que descargó. Pero aquí lo he desglosado de una sola línea en cuatro, para que sea más fácil ver qué hace y, en consecuencia, cómo debemos adaptarlo.
async void Footer_Click(object sender, RoutedEventArgs e)
{
var hyperlinkButton = (HyperlinkButton)sender;
string tagUrl = hyperlinkButton.Tag.ToString();
Uri uri = new Uri(tagUrl);
await Windows.System.Launcher.LaunchUriAsync(uri);
}
Aunque técnicamente, el método es asincrónico, no hace nada después de await, por lo que no necesita ( await ni la async palabra clave). Probablemente los usa para evitar el mensaje de IntelliSense en Visual Studio.
El método equivalente de C++/WinRT también será asincrónico (porque llama a Launcher.LaunchUriAsync). Pero no es necesario co_await ni devolver un objeto asíncrono. Para obtener información sobre co_await y los objetos asincrónicos, consulta Concurrencia y operaciones asincrónicas con C++/WinRT.
Ahora hablemos de lo que hace el método. Dado que se trata de un controlador de eventos para el evento Click de un HyperlinkButton, el objeto denominado sender es realmente un HyperlinkButton. Por lo tanto, la conversión de tipos es segura (también podríamos haber expresado esta conversión como sender as HyperlinkButton). A continuación, recuperamos el valor de la propiedad Tag (si examinas el marcado XAML en el proyecto de C#, verás que se establece en una cadena que representa una dirección URL web). Aunque la propiedad FrameworkElement.Tag (HyperlinkButton es frameworkElement) es de tipo object, en C# podemos encadenar eso con Object.ToString. A partir de la cadena resultante, creamos un objeto URI . Y por último (con la ayuda de Shell) iniciamos un explorador y navegamos a la dirección URL.
Este es el método migrado a C++/WinRT (de nuevo, expandido para mayor claridad), después de lo cual es una descripción de los detalles.
// pch.h
...
#include "winrt/Windows.System.h"
...
// MainPage.h
...
void Footer_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& e);
private:
...
// MainPage.cpp
...
using namespace winrt::Windows::Foundation;
using namespace winrt::Microsoft::UI::Xaml::Controls;
...
void MainPage::Footer_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const&)
{
auto hyperlinkButton{ sender.as<HyperlinkButton>() };
hstring tagUrl{ winrt::unbox_value<hstring>(hyperlinkButton.Tag()) };
Uri uri{ tagUrl };
Windows::System::Launcher::LaunchUriAsync(uri);
}
Como siempre, creamos el controlador de eventos public. Usamos como función en el objeto remitente para convertirlo en HyperlinkButton. En C++/WinRT, la propiedad Tag es una IInspectable (equivalente a Object). Pero no hay tostring en IInspectable. En su lugar, tenemos que desencapsular IInspectable a un valor escalar (una cadena, en este caso). De nuevo, para obtener más información sobre boxing y unboxing, consulta Boxing y unboxing de valores a IInspectable.
Las dos últimas líneas repiten los patrones de migración que ya hemos visto antes y reflejan casi exactamente la versión en C#.
HandleClipboardChanged
No hay nada nuevo implicado en la portabilidad de este método. Puede comparar las versiones de C# y C++/WinRT en el ZIP del código fuente del ejemplo Clipboard que descargó.
OnClipboardChanged y OnWindowActivated
Hasta ahora solo tenemos códigos auxiliares vacíos para estos dos controladores de eventos. Pero migrarlos es sencillo y no genera nada nuevo para discutir.
ScenarioControl_SelectionChanged
Este es otro controlador de eventos privado que pertenece a la clase MainPage de C# y se define en MainPage.xaml.cs. En C++/WinRT, lo haremos público e implementaremos en MainPage.h y MainPage.cpp.
Para este método, necesitaremos MainPage::navigating, que es un campo booleano privado, inicializado en false. Y necesitará un Frame en MainPage.xaml, llamado ScenarioFrame. Pero, aparte de esos detalles, adaptar este método no revela ninguna técnica nueva.
Si, en lugar de hacer la conversión manualmente, estás copiando código de la versión de C++/WinRT del archivo ZIP del código fuente de Clipboard sample que descargaste, verás que allí se usa MainPage::NavigateTo. Por ahora, simplemente refactorice el contenido de NavigateTo en ScenarioControl_SelectionChanged.
UpdateStatus
Hasta ahora solo tenemos un código auxiliar para MainPage.UpdateStatus. Portar su implementación, de nuevo, retoma en gran parte cuestiones ya tratadas. Un nuevo punto a tener en cuenta es que, mientras que en C# podemos comparar una cadena con String.Empty, en C++/WinRT, en su lugar llamamos a la función winrt::hstring::empty . Otra es que nullptr es el equivalente estándar en C++ de null de C#.
Puede realizar el resto del puerto con técnicas que ya hemos tratado. Aquí tienes una lista de las cosas que tendrás que hacer antes de que la versión adaptada de este método pueda compilarse.
- Para
MainPage.xaml, agregue un borde denominado StatusBorder. - Para
MainPage.xaml, agregue un TextBlock denominado StatusBlock. - Para
MainPage.xaml, agregue un StackPanel denominado StatusPanel. - Para
pch.h, agregue#include "winrt/Microsoft.UI.Xaml.Media.h". - Para
pch.h, agregue#include "winrt/Microsoft.UI.Xaml.Automation.Peers.h". - Para
MainPage.cppagregarusing namespace winrt::Microsoft::UI::Xaml::Media;. - Para
MainPage.cppagregarusing namespace winrt::Microsoft::UI::Xaml::Automation::Peers;.
Copiar el XAML y los estilos necesarios para terminar de migrar MainPage
Para XAML, el caso ideal es que puedes usar el mismo marcado XAML en un proyecto de C# y C++/WinRT. Y el ejemplo del Portapapeles es uno de esos casos.
En su archivo Styles.xaml, el ejemplo de Portapapeles incluye un ResourceDictionary de estilos en XAML, que se aplican a los botones, menús y otros elementos de la interfaz de usuario en toda la interfaz de usuario de la aplicación. La Styles.xaml página se combina en App.xaml. Y luego hay el punto de partida estándar MainPage.xaml para la interfaz de usuario, que ya hemos visto brevemente. Ahora podemos volver a usar esos tres .xaml archivos, sin cambios, en la versión de C++/WinRT del proyecto.
Al igual que con los archivos de recursos, puedes elegir hacer referencia a los mismos archivos XAML compartidos de varias versiones de la aplicación. En este tutorial, solo por motivos de simplicidad, copiaremos archivos en el proyecto de C++/WinRT y los agregaremos de esa manera.
Vaya a la \Clipboard_sample\SharedContent\xaml carpeta, seleccione y copie App.xaml y MainPage.xaml, a continuación, pegue esos dos archivos en la \Clipboard\Clipboard carpeta del proyecto de C++/WinRT y elija reemplazar los archivos cuando se le solicite.
En el proyecto de C++/WinRT en Visual Studio, haga clic en Mostrar todos los archivos para activarlo. Ahora añada una nueva carpeta, justo debajo del nodo del proyecto, y asígnele el nombre Styles. En el Explorador de archivos, vaya a la \Clipboard_sample\SharedContent\xaml carpeta, seleccione y copie Styles.xamly péguela en la \Clipboard\Clipboard\Styles carpeta que acaba de crear. De nuevo en Explorador de soluciones en el proyecto de C++/WinRT, haga clic con el botón derecho en la Styles carpeta >Agregar>elemento existente... y vaya a \Clipboard\Clipboard\Styles. En el selector de archivos, seleccione Styles y haga clic en Agregar.
Agregue una nueva carpeta al proyecto de C++/WinRT, inmediatamente en el nodo del proyecto y con el nombre Styles. Vaya a la \Clipboard_sample\SharedContent\xaml carpeta, seleccione y copie Styles.xamly péguela en la \Clipboard\Clipboard\Styles carpeta del proyecto de C++/WinRT. Haga clic con el botón derecho en la Styles carpeta (en Explorador de soluciones en el proyecto de C++/WinRT) >Agregar>elemento existente... y vaya a \Clipboard\Clipboard\Styles. En el selector de archivos, seleccione Styles y haga clic en Agregar.
Haga clic en Mostrar todos los archivos de nuevo para desactivarlo.
Ahora hemos terminado de migrar MainPage y, si ha seguido los pasos, el proyecto de C++/WinRT ahora se compilará y ejecutará.
Consolide sus archivos .idl
Además del punto de partida estándar MainPage.xaml para la interfaz de usuario, el ejemplo del Portapapeles tiene otras cinco páginas XAML específicas del escenario, junto con sus archivos de código subyacente correspondientes. Volveremos a usar el marcado XAML real de todas estas páginas, sin cambios, en la versión de C++/WinRT del proyecto. Y veremos cómo migrar el código subyacente en las próximas secciones principales. Pero antes de eso, hablemos de IDL.
Tiene sentido consolidar el IDL de las clases del entorno de ejecución en un único archivo IDL. Para obtener información sobre ese valor, consulte Incorporación de clases en tiempo de ejecución en archivos Midl (.idl). Por lo tanto, a continuación consolidaremos el contenido de CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idly OtherScenarios.idl moviendo ese IDL a un único archivo denominado Project.idl (y, a continuación, eliminaremos los archivos originales).
Mientras lo hacemos, también vamos a quitar la propiedad ficticia generada automáticamente (Int32 MyProperty;y su implementación) de cada uno de esos cinco tipos de página XAML.
En primer lugar, agregue un nuevo elemento Midl File (.idl) al proyecto de C++/WinRT. Asígnalo Project.idl. Reemplace el contenido completo de Project.idl con el siguiente código.
// Project.idl
namespace SDKTemplate
{
[default_interface]
runtimeclass CopyFiles : Microsoft.UI.Xaml.Controls.Page
{
CopyFiles();
}
[default_interface]
runtimeclass CopyImage : Microsoft.UI.Xaml.Controls.Page
{
CopyImage();
}
[default_interface]
runtimeclass CopyText : Microsoft.UI.Xaml.Controls.Page
{
CopyText();
}
[default_interface]
runtimeclass HistoryAndRoaming : Microsoft.UI.Xaml.Controls.Page
{
HistoryAndRoaming();
}
[default_interface]
runtimeclass OtherScenarios : Microsoft.UI.Xaml.Controls.Page
{
OtherScenarios();
}
}
Como puede verse, no es más que una copia del contenido de los archivos individuales .idl, todo ello dentro de un único espacio de nombres y con MyProperty eliminado de cada clase Runtime.
En el Explorador de soluciones de Visual Studio, seleccione todos los archivos IDL originales (CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idl y OtherScenarios.idl) y Edit>Remove quítelos (elija Delete en el cuadro de diálogo).
Por último, y para completar la eliminación de MyProperty, en los archivos .h y .cpp para cada uno de esos mismos cinco tipos de página XAML, elimine las declaraciones y definiciones de las funciones de descriptor de acceso int32_t MyProperty() y de mutador void MyProperty(int32_t).
Por cierto, siempre es una buena idea tener el nombre de los archivos XAML que coincidan con el nombre de la clase que representan. Por ejemplo, si tienes x:Class="MyNamespace.MyPage" en un archivo de marcado XAML, ese archivo debe denominarse MyPage.xaml. Aunque esto no es un requisito técnico, no tener que lidiar con distintos nombres para el mismo artefacto hará que tu proyecto sea más comprensible, más fácil de mantener y más fácil de manejar.
CopyFiles
En el proyecto de C#, el tipo de página XAML CopyFiles se implementa en los CopyFiles.xaml archivos de código fuente y CopyFiles.xaml.cs . Echemos un vistazo a cada uno de los miembros de CopyFiles a su vez.
rootPage
Se trata de un campo privado.
// CopyFiles.xaml.cs
...
public sealed partial class CopyFiles : Page
{
MainPage rootPage = MainPage.Current;
...
}
...
En C++/WinRT, podemos definirlo e inicializarlo como este.
// CopyFiles.h
...
struct CopyFiles : CopyFilesT<CopyFiles>
{
...
private:
SDKTemplate::MainPage rootPage{ MainPage::Current() };
};
...
De nuevo (al igual que con MainPage::current), CopyFiles::rootPage se declara como de tipo SDKTemplate::MainPage, que es el tipo proyectado y no el tipo de implementación.
CopyFiles (el constructor)
En el proyecto de C++/WinRT, el tipo CopyFiles ya tiene un constructor que contiene el código que queremos (solo llama a InitializeComponent).
CopyButton_Click
El método CopyButton_Click de C# es un controlador de eventos y, a partir de la async palabra clave de su firma, podemos indicar que el método realiza un trabajo asincrónico. En C++/WinRT, implementamos un método asincrónico como corrutina. Para obtener una introducción a la simultaneidad en C++/WinRT, junto con una descripción de lo que es una corrutina , consulta Operaciones de simultaneidad y asincrónicas con C++/WinRT.
Es habitual querer programar trabajo adicional después de que se complete una corrutina y, en tales casos, la corrutina devolvería algún tipo de objeto asíncrono sobre el que se puede esperar y que, opcionalmente, informa del progreso. Pero estas consideraciones normalmente no se aplican a un controlador de eventos. Por lo tanto, cuando tenga un controlador de eventos que realice operaciones asincrónicas, puede implementarlo como una corrutina que devuelva winrt::fire_and_forget. Para obtener más información, consulta Fire and forget.
Aunque la idea de una corrutina de tipo «disparar y olvidar» es que no importa cuándo se completa, el trabajo continúa en segundo plano (o está suspendido, a la espera de reanudarse). Puede ver en la implementación de C# que CopyButton_Click depende del puntero this (accede al miembro de datos de instancia rootPage). Por lo tanto, debemos asegurarnos de que el puntero this (un puntero a un objeto CopyFiles) siga existiendo más allá de la corrutina CopyButton_Click. En una situación como esta aplicación de ejemplo, donde el usuario navega entre páginas de interfaz de usuario, no podemos controlar directamente la duración de esas páginas. Si la página CopyFiles se destruye (al salir de ella) mientras CopyButton_Click sigue ejecutándose en un subproceso en segundo plano, no será seguro acceder a rootPage. Para que la corrutina sea correcta, debe obtener una referencia fuerte al puntero this y mantener esa referencia durante toda la ejecución de la corrutina. Para obtener más información, consulta Referencias fuertes y débiles en C++/WinRT.
Si se fija en la versión en C++/WinRT del ejemplo, concretamente en CopyFiles::CopyButton_Click, verá que se hace con una sencilla declaración en la pila.
fire_and_forget CopyFiles::CopyButton_Click(IInspectable const&, RoutedEventArgs const&)
{
auto lifetime{ get_strong() };
...
}
Echemos un vistazo a los otros aspectos del código portado que son notables.
En el código, creamos una instancia de un objeto FileOpenPicker y dos líneas más adelante accedemos a la propiedad FileTypeFilter del objeto. El tipo de retorno de esa propiedad implementa un IVector de cadenas. Y en ese IVector, llamamos al método IVector<T>.ReplaceAll(T[]). El aspecto interesante es el valor que pasamos a ese método, donde se espera una matriz. Esta es la línea de código.
filePicker.FileTypeFilter().ReplaceAll({ L"*" });
El valor que pasamos ({ L"*" }) es una lista estándar de inicializadores de C++. Contiene un único objeto, en este caso, pero una lista de inicializadores puede contener cualquier número de objetos separados por comas. Las partes de C++/WinRT que permiten pasar una lista de inicializadores a un método como este se explican en listas de inicializadores estándar.
Portaremos la palabra clave de C# await a co_await en C++/WinRT. Este es el ejemplo del código.
auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };
A continuación, considere esta línea de código de C#.
dataPackage.SetStorageItems(storageItems);
C# puede convertir implícitamente el archivo de almacenamiento< IReadOnlyList> representado por storageItems en el IEnumerable<IStorageItem> esperado por DataPackage.SetStorageItems. Pero en C++/WinRT es necesario convertir explícitamente de IVectorView<StorageFile> a IIterable<IStorageItem>. Y así tenemos otro ejemplo de la función as en acción.
dataPackage.SetStorageItems(storageItems.as<IVectorView<IStorageItem>>());
Donde usamos la null palabra clave en C# (por ejemplo, Clipboard.SetContentWithOptions(dataPackage, null)), usamos nullptr en C++/WinRT (por ejemplo, Clipboard::SetContentWithOptions(dataPackage, nullptr)).
PasteButton_Click
Este es otro controlador de eventos en forma de corrutina fire-and-forget. Echemos un vistazo a los aspectos del código portado que son notables.
En la versión de C# del ejemplo, capturamos excepciones con catch (Exception ex). En el código de C++/WinRT portado, verá la expresión catch (winrt::hresult_error const& ex). Para obtener más información sobre winrt::hresult_error y cómo trabajar con él, consulta Control de errores con C++/WinRT.
Un ejemplo de prueba de si un objeto de C# es null o no es if (storageItems != null). En C++/WinRT, podemos usar un operador de conversión a bool, que realiza internamente la comprobación contra nullptr.
Esta es una versión ligeramente simplificada de un fragmento de código de la versión de C++/WinRT portado del ejemplo.
std::wostringstream output;
output << std::wstring_view(ApplicationData::Current().LocalFolder().Path());
Construir un std::wstring_view a partir de un winrt::hstring como que ilustra una alternativa a llamar a la función hstring::c_str (para convertir winrt::hstring en una cadena de estilo C). Esta alternativa funciona gracias al operador de conversión de hstringa std::wstring_view.
Considere este fragmento de C#.
var file = storageItem as StorageFile;
if (file != null)
...
Para migrar la palabra clave de C# as a C++/WinRT, hasta ahora hemos visto que la función como se usaba un par de veces. Esa función produce una excepción si se produce un error en la conversión de tipos. Pero si queremos que la conversión devuelva nullptr si se produce un error (para que podamos controlar esa condición en el código), en su lugar usamos la función try_as .
auto file{ storageItem.try_as<StorageFile>() };
if (file)
...
Copie el CÓDIGO XAML necesario para terminar de migrar CopyFiles.
Ahora puede seleccionar todo el contenido del CopyFiles.xaml archivo en la carpeta de la shared descarga del código fuente de ejemplo original y pegarlo en el CopyFiles.xaml archivo en el proyecto de C++/WinRT (reemplazando el contenido existente de ese archivo en el proyecto de C++/WinRT).
Por último, edite CopyFiles.h y .cpp elimine la función ClickHandler ficticia, ya que acabamos de sobrescribimos el marcado XAML correspondiente.
Ahora hemos terminado de migrar CopyFiles y, si ha seguido los pasos, el proyecto de C++/WinRT ahora se compilará y ejecutará, y el escenario CopyFiles será funcional.
Copiar imagen
Para migrar el tipo de página XAML CopyImage , sigues el mismo proceso que para CopyFiles. Al migrar CopyImage, te encontrarás con el uso de la instrucción using de C#, que garantiza que los objetos que implementan la interfaz IDisposable se desechen correctamente.
if (imageReceived != null)
{
using (var imageStream = await imageReceived.OpenReadAsync())
{
... // Pass imageStream to other APIs, and do other work.
}
}
La interfaz equivalente en C++/WinRT es IClosable, con su único método Close . Este es el equivalente de C++/WinRT del código de C# anterior.
if (imageReceived)
{
auto imageStream{ co_await imageReceived.OpenReadAsync() };
... // Pass imageStream to other APIs, and do other work.
imageStream.Close();
}
Los objetos de C++/WinRT implementan IClosable principalmente para la ventaja de los lenguajes que carecen de finalización determinista. C++/WinRT tiene una finalización determinista, por lo que a menudo no es necesario llamar a IClosable::Close cuando se escribe C++/WinRT. Pero a veces es mejor darlo por terminado, y esta es una de ellas. Aquí, el identificador imageStream es un contenedor con recuento de referencias alrededor de un objeto Windows Runtime subyacente (en este caso, un objeto que implementa IRandomAccessStreamWithContentType). Aunque podemos determinar que el finalizador de imageStream (su destructor) se ejecutará al final del ámbito envolvente (los corchetes curdos), no podemos estar seguros de que el finalizador llamará a Close. Esto se debe a que pasamos imageStream a otras API y es posible que sigan contribuyendo al recuento de referencias del objeto Windows Runtime subyacente. Por lo tanto, este es un caso en el que es una buena idea llamar a Close explícitamente. Para obtener más información, consulta ¿Necesito llamar a IClosable::Close en las clases de tiempo de ejecución que utilizo?.
A continuación, considere la expresión (uint)(imageDecoder.OrientedPixelWidth * 0.5)de C#, que encontrará en el controlador de eventos OnDeferredImageRequestedHandler . Esa expresión multiplica un uint por un double, lo que da como resultado un double. A continuación, lo convierte en un uint. En C++/WinRT, podríamos usar una conversión de estilo C similar ((uint32_t)(imageDecoder.OrientedPixelWidth() * 0.5)), pero es preferible aclarar exactamente qué tipo de conversión pretendemos, y en este caso lo haríamos con static_cast<uint32_t>(imageDecoder.OrientedPixelWidth() * 0.5).
La versión de C# de CopyImage.OnDeferredImageRequestedHandler tiene una finally cláusula , pero no una catch cláusula . Fuimos un poco más lejos en la versión de C++/WinRT e implementamos una catch cláusula para que podamos informar de si la representación diferida se realizó correctamente.
La migración del resto de esta página XAML no produce nada nuevo para discutir. Recuerde eliminar la función ClickHandler ficticia. Y, al igual que con CopyFiles, el último paso del puerto es seleccionar todo el contenido de CopyImage.xamly pegarlo en el mismo archivo en el proyecto de C++/WinRT.
CopyText
Puede portar CopyText.xaml y CopyText.xaml.cs utilizando técnicas que ya hemos visto.
HistoryAndRoaming
Hay algunos puntos de interés que surgen al migrar el tipo de página XAML HistoryAndRoaming .
En primer lugar, eche un vistazo al código fuente de C# y siga el flujo de control de OnNavigatedTo a través del controlador de eventos OnHistoryEnabledChanged y, por último, a la función asincrónica CheckHistoryAndRoaming (que no se espera, por lo que básicamente se desencadena y olvida). Dado que CheckHistoryAndRoaming es asincrónica, en C++/WinRT tendremos que tener cuidado con el tiempo de vida del puntero this. Puede ver el resultado si examina la implementación en el archivo de HistoryAndRoaming.cpp código fuente. En primer lugar, cuando adjuntamos delegados a los eventos Clipboard::HistoryEnabledChanged y Clipboard::RoamingEnabledChanged, solo tomamos una referencia débil al objeto de la página HistoryAndRoaming. Para ello, creamos el delegado de modo que dependa del valor devuelto por winrt::get_weak, en lugar de depender del puntero this. Lo que significa que el propio delegado, que finalmente llama a código asíncrono, no mantiene activa la página HistoryAndRoaming si salimos de ella.
Y, en segundo lugar, cuando finalmente llegamos a nuestra corrutina CheckHistoryAndRoaming de tipo fire-and-forget, lo primero que hacemos es obtener una referencia fuerte a this para garantizar que la página HistoryAndRoaming permanezca activa al menos hasta que la corrutina termine. Para obtener más información sobre ambos aspectos descritos, consulta Referencias fuertes y débiles en C++/WinRT.
Encontramos otro punto de interés al portar CheckHistoryAndRoaming. Contiene código para actualizar la interfaz de usuario; por lo tanto, debemos estar seguros de que lo estamos haciendo en el subproceso principal de la interfaz de usuario. El subproceso que llama inicialmente a un controlador de eventos es el subproceso principal de la interfaz de usuario. Pero normalmente, un método asincrónico puede ejecutarse o reanudarse en cualquier subproceso arbitrario. En C#, la solución consiste en enviar el trabajo al subproceso de la interfaz de usuario. En C++/WinRT, podemos usar la función winrt::resume_foreground junto con el DispatcherQueue del puntero this para suspender la corrutina y reanudarla inmediatamente en el hilo principal de la interfaz de usuario.
La expresión pertinente es co_await winrt::resume_foreground(DispatcherQueue());. La versión más corta se logra cortesía de un operador de conversión proporcionado por C++/WinRT.
La migración del resto de esta página XAML no produce nada nuevo para discutir. Recuerde eliminar la función ClickHandler de prueba y copiar también el marcado XAML.
OtherScenarios
Puede portar OtherScenarios.xaml y OtherScenarios.xaml.cs utilizando técnicas que ya hemos visto.
Conclusion
Esperamos que este tutorial le haya armado con suficiente información de portabilidad y técnicas que ahora puede seguir adelante y migrar sus propias aplicaciones de C# a C++/WinRT. A modo de actualizador, puedes seguir haciendo referencia a las versiones anteriores (C#) y posteriores (C++/WinRT) del código fuente del ejemplo del Portapapeles y compararlas en paralelo para ver la correspondencia.