Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Cette rubrique présente une étude de cas sur le portage de l’un des exemples d’application Plateforme Windows universelle (UWP) de C# vers C++/WinRT. Vous pouvez acquérir une pratique de portage et une expérience en suivant la procédure pas à pas et le portage de l’exemple pour vous-même au fur et à mesure.
Note
Le code source en cours de portage est une application C# UWP. Le code C++/WinRT de destination dans cet article est écrit pour WinUI 3 (SDK d'application Windows). Chaque fois que la source UWP utilise des API qui diffèrent dans WinUI 3 (par exemple, par exemple, Windows.UI.Core.CoreDispatcher vs. Microsoft.UI.Dispatching.DispatcherQueue), cet article affiche explicitement l’équivalent WinUI 3 correct dans la sortie C++/WinRT. Vous pouvez utiliser les modèles de code de la colonne C++/WinRT directement dans votre application WinUI 3.
Pour obtenir un catalogue complet des détails techniques impliqués dans le portage vers C++/WinRT à partir de C#, consultez la rubrique complémentaire Déplacer vers C++/WinRT à partir de C#.
Brève préface sur les fichiers de code source C# et C++
Dans un projet C#, vos fichiers de code source sont principalement des fichiers .cs. Lorsque vous passez à C++, vous remarquerez qu’il existe davantage de types de fichiers de code source à utiliser. La raison est de faire la différence entre les compilateurs, la façon dont le code source C++ est réutilisé, et les notions de déclaration et dedéfinition d’un type et de ses fonctions (ses méthodes).
Une déclaration de fonction décrit uniquement la signature de la fonction (son type de retour, son nom et ses types de paramètres et ses noms). Une définition de fonction inclut le corps de la fonction (son implémentation).
C’est un peu différent quand il s’agit de types. Vous définissez un type en fournissant son nom ainsi qu’en déclarant, au minimum, simplement toutes ses fonctions membres (et ses autres membres). C’est correct, vous pouvez définir un type même si vous ne définissez pas ses fonctions membres.
- Les fichiers de code source C++ courants sont
.h(point h) et les fichiers.cpp. Un.hfichier est un fichier d’en-tête et définit un ou plusieurs types. Bien que vous puissiez définir des fonctions membres dans un en-tête, il s’agit généralement de ce pour quoi un.cppfichier est destiné. Par conséquent, pour un type C++ hypothétique , vous définissez MyClass dansMyClass.h, et vous définissez ses fonctions membres dansMyClass.cpp. Pour que d’autres développeurs réutilisent vos classes, vous devez partager uniquement les fichiers et le.hcode objet. Vous devez conserver vos.cppfichiers secrets, car l’implémentation constitue votre propriété intellectuelle. - En-tête précompilé (
pch.h). En règle générale, il existe un ensemble de fichiers d’en-tête que vous incluez dans votre application, et vous ne modifiez pas ces fichiers très souvent. Par conséquent, plutôt que de traiter le contenu de cet ensemble d’en-têtes chaque fois que vous compilez, vous pouvez agréger ces en-têtes dans un fichier, compiler une fois, puis utiliser la sortie de cette étape de précompilation chaque fois que vous générez. Pour ce faire, utilisez un fichier d’en-tête précompilé (généralement nommépch.h). - Fichiers
.idl. Ces fichiers contiennent le langage IDL (Interface Definition Language). Vous pouvez considérer IDL comme des fichiers d’en-tête pour les types Windows Runtime. Nous allons en savoir plus sur IDL dans la section IDL pour le type MainPage.
Télécharger et tester l’exemple de Presse-papiers
Visitez l’exemple de page web Du Presse-papiers , puis cliquez sur Télécharger le fichier ZIP. Décompressez le fichier téléchargé et examinez la structure des dossiers.
- La version C# de l’exemple de code source est contenue dans le dossier nommé
cs. - La version C++/WinRT de l’exemple de code source est contenue dans le dossier nommé
cppwinrt. - D’autres fichiers, communs à la version C# et à la version C++/WinRT, se trouvent dans les dossiers
sharedetSharedContent.
Le didacticiel de cette rubrique montre comment recréer la version C++/WinRT de l’exemple Presse-papiers en le portant à partir du code source C#. De cette façon, vous pouvez voir comment vous pouvez porter vos propres projets C# vers C++/WinRT.
Pour avoir une idée de ce que fait l’exemple, ouvrez la solution C# (\Clipboard_sample\cs\Clipboard.sln), modifiez la configuration appropriée (peut-être sur x64), générez et exécutez. Le propre interface utilisateur de l’exemple vous guide tout au long de ses différentes fonctionnalités, étape par étape.
Tip
Le dossier racine de l’exemple que vous avez téléchargé peut être nommé Clipboard plutôt que Clipboard_sample. Mais nous allons continuer à faire référence à ce dossier pour Clipboard_sample le distinguer de la version C++/WinRT que vous allez créer à une étape ultérieure.
Créer une application vide, nommée Presse-papiers
Note
Pour plus d’informations sur l’installation et l’utilisation de l’extension Visual Studio C++/WinRT (VSIX) et du package NuGet (qui fournissent ensemble un modèle de projet et une prise en charge de build), consultez Visual Studio prise en charge de C++/WinRT.
Commencez le processus de portage en créant un projet C++/WinRT dans Microsoft Visual Studio. Créez un projet C++ à l’aide du modèle de projet Application vide, empaquetée (WinUI 3 sur Desktop). Nommez-le Presse-papiers et, (afin que votre structure de dossiers corresponde à la procédure pas à pas), vérifiez que Placer la solution et le projet dans le même répertoire n’est pas cochée.
Pour obtenir une base de référence, assurez-vous que ce nouveau projet, vide, génère et s’exécute.
Le fichier Package.appxmanifest et les fichiers de ressources
Si les versions C# et C++/WinRT de l’exemple n’ont pas besoin d’être installées côte à côte sur le même ordinateur, les fichiers sources du manifeste du package d’application des deux projets (Package.appxmanifest) peuvent être identiques. Dans ce cas, vous pouvez simplement copier Package.appxmanifest à partir du projet C# vers le projet C++/WinRT, et vous avez terminé.
Pour que les deux versions de l’exemple coexistent, elles ont besoin d’identificateurs différents. Dans ce cas, dans le projet C++/WinRT, ouvrez le Package.appxmanifest fichier dans un éditeur XML et notez ces trois valeurs.
- Dans l’élément /Package/Identity , notez la valeur de l’attribut Name . Il s’agit du nom du package. Pour un projet nouvellement créé, le projet lui donne une valeur initiale d’un GUID unique.
- Dans l’élément /Package/Applications/Application , notez la valeur de l’attribut ID . Il s’agit de l’ID d’application.
- Dans l’élément /Package/mp :PhoneIdentity , notez la valeur de l’attribut PhoneProductId . Là encore, pour un projet nouvellement créé, il est défini sur le même GUID que le nom du package.
Package.appxmanifest Copiez ensuite du projet C# vers le projet C++/WinRT. Enfin, vous pouvez restaurer les trois valeurs que vous avez notées. Vous pouvez également modifier les valeurs copiées pour les rendre uniques et/ou appropriées pour l’application et pour votre organisation (comme vous le feriez habituellement pour un nouveau projet). Par exemple, dans ce cas, au lieu de restaurer la valeur du nom du package, nous pouvons simplement modifier la valeur copiée de Microsoft. SDKSamples.Clipboard.CS à Microsoft. SDKSamples.Clipboard.CppWinRT. Et nous pouvons laisser l’ID d’application défini sur App. Tant que le nom du package ou l’ID d’application sont différents, les deux applications auront des ID de modèle utilisateur d’application (AUMID). Et c’est ce qui est nécessaire pour que deux applications soient installées côte à côte sur le même ordinateur.
Pour les besoins de cette procédure pas à pas, il est judicieux d’apporter quelques autres modifications dans Package.appxmanifest. Il existe trois occurrences de la chaîne Exemple C# du Presse-papiers. Remplacez-le par l’exemple C++/WinRT Clipboard.
Dans le projet C++/WinRT, le Package.appxmanifest fichier et le projet ne sont plus synchronisés par rapport aux fichiers de ressources qu’ils référencent. Pour remédier à cela, supprimez d’abord les ressources du projet C++/WinRT en sélectionnant tous les fichiers du Assets dossier (dans Explorateur de solutions dans Visual Studio) et en les supprimant (choisissez Supprimer dans la boîte de dialogue).
Le projet C# fait référence aux fichiers de ressources à partir d’un dossier partagé. Vous pouvez faire de même dans le projet C++/WinRT, ou vous pouvez copier les fichiers comme nous le ferons dans cette procédure pas à pas.
Accédez au dossier \Clipboard_sample\SharedContent\media. Sélectionnez les sept fichiers que le projet C# inclut (microsoft-sdk.png, smalltile-sdk.png, splash-sdk.png, squaretile-sdk.png, storelogo-sdk.png, tile-sdk.pnget windows-sdk.png), copiez-les, puis collez-les dans le \Clipboard\Clipboard\Assets dossier du nouveau projet.
Cliquez avec le bouton droit sur le Assets dossier (dans Explorateur de solutions dans le projet C++/WinRT) >Ajouter> unélément existant... et accédez à \Clipboard\Clipboard\Assets. Dans le sélecteur de fichiers, sélectionnez les sept fichiers, puis cliquez sur Ajouter.
Package.appxmanifest est maintenant de nouveau synchronisé avec les fichiers de ressources du projet.
MainPage, y compris les fonctionnalités qui configurent l’exemple
L’exemple du Presse-papiers, comme tous les exemples d’applications Plateforme Windows universelle (UWP), se compose d’un ensemble de scénarios que l’utilisateur peut parcourir un à un. La collection de scénarios dans un exemple donné est configurée dans le code source de l’exemple. Chaque scénario de la collection est un élément de données qui stocke un titre, ainsi que le type de la classe dans le projet qui implémente le scénario.
Dans la version C# de l’exemple, si vous regardez dans le SampleConfiguration.cs fichier de code source, deux classes s’affichent. La plupart de la logique de configuration se trouve dans la classe MainPage , qui est une classe partielle (elle forme une classe complète lorsqu’elle est combinée avec le balisage et MainPage.xaml le code impératif dans MainPage.xaml.cs). L’autre classe de ce fichier de code source est Scenario, avec ses propriétés Title et ClassType .
Au cours des prochaines sous-sections, nous allons examiner comment porter MainPage et Scenario.
IDL pour le type MainPage
Commençons cette section en parlant brièvement du langage IDL (Interface Definition Language) et de la façon dont elle nous aide quand nous programmations avec C++/WinRT. IDL est un type de code source qui décrit la surface pouvant être appelée d’un type Windows Runtime. La surface appelante (ou publique) d’un type est projetée dans le monde, afin que le type puisse être consommé. Cette partie exposée du type s’oppose à l’implémentation interne réelle du type, qui n’est évidemment pas invocable et n’est pas publique. Il s’agit uniquement de la partie projetée que nous définissons dans IDL.
Après avoir créé du code source IDL (dans un .idl fichier), vous pouvez ensuite compiler l’IDL dans des fichiers de métadonnées lisibles par l’ordinateur (également appelé métadonnées Windows). Ces fichiers de métadonnées ont l’extension .winmd, et voici quelques-unes de leurs utilisations.
- A
.winmdpeut décrire les types Windows Runtime dans un composant. Lorsque vous référencez un composant Windows Runtime (WRC) à partir d'un projet d'application, le projet d'application lit les Windows Métadonnées appartenant au WRC (que les métadonnées peuvent se trouver dans un fichier distinct, ou qu'elles peuvent être empaquetées dans le même fichier que le WRC lui-même) afin de pouvoir consommer les types du WRC à partir de l'application. - Un
.winmdpeut décrire les types Windows Runtime dans une partie de votre application afin qu’ils puissent être consommés par une autre partie de la même application. Par exemple, un type Windows Runtime consommé par une page XAML dans la même application. - Pour vous permettre d’utiliser plus facilement les types Windows Runtime (intégrés ou tiers), le système de génération C++/WinRT utilise les fichiers
.winmdpour générer des types d’encapsulation représentant les parties projetées de ces types Windows Runtime. - Pour faciliter l'implémentation de vos propres types Windows Runtime, le système de build C++/WinRT transforme votre IDL en fichier
.winmd, puis utilise celui-ci pour générer des wrappers pour votre projection, ainsi que des stubs sur lesquels baser votre implémentation (nous allons en parler plus loin dans cette rubrique).
La version spécifique d’IDL que nous utilisons avec C++/WinRT est Microsoft langage de définition d’interface 3.0. Dans le reste de cette section de la rubrique, nous allons examiner le type MainPage C# en détail. Nous allons décider quelles parties doivent être dans la projection du type MainPage C++/WinRT (c’est-à-dire, dans sa surface pouvant être appelée, ou publique), et qui peut simplement faire partie de son implémentation. Cette distinction est importante car lorsque nous arrivons à créer notre IDL (que nous ferons dans la section après celle-ci), nous allons définir uniquement les parties pouvant être appelées là-bas.
Les fichiers de code source C# qui implémentent ensemble le type MainPage sont : MainPage.xaml (que nous allons bientôt porter, en le copiant), MainPage.xaml.cset SampleConfiguration.cs.
Dans la version C++/WinRT, nous allons répartir notre type MainPage dans des fichiers de code source d’une manière similaire. Nous allons prendre la logique dans MainPage.xaml.cs et la traduire pour la plupart des parties vers MainPage.h et MainPage.cpp. Et pour la logique dans SampleConfiguration.cs, nous allons traduire cela en SampleConfiguration.h et SampleConfiguration.cpp.
Les classes d’une application Plateforme Windows universelle (UWP) C# sont bien sûr des types Windows Runtime. Toutefois, lorsque vous créez un type dans une application C++/WinRT, vous pouvez choisir si ce type est un type Windows Runtime ou une classe/struct/énumération C++ standard.
Toute page XAML de notre projet doit être un type Windows Runtime. MainPage doit donc être un type Windows Runtime. Dans le projet C++/WinRT, MainPage est déjà un type Windows Runtime. Nous n'avons donc pas besoin de modifier cet aspect. Plus précisément, il s’agit d’une classe runtime.
- Pour plus d’informations sur la création ou non d’une classe runtime pour un type donné, consultez la rubrique Créer des API avec C++/WinRT.
- Dans C++/WinRT, l’implémentation interne d’une classe runtime et les parties projetées (publiques) existent sous la forme de deux classes différentes. Il s’agit du type d’implémentation et du type projeté. Vous pouvez en apprendre davantage à ce sujet dans la rubrique mentionnée au point précédent, ainsi que dans Utiliser des API avec C++/WinRT.
- Pour plus d’informations sur le lien entre les classes d’exécution et les fichiers IDL (
.idl), vous pouvez lire la rubrique Contrôles XAML ; lier à une propriété C++/WinRT et la suivre pas à pas. Cette rubrique décrit le processus de création d’une nouvelle classe runtime, dont la première étape consiste à ajouter un nouvel élément de fichier Midl (.idl) au projet.
Pour MainPage, nous avons en fait le fichier nécessaire MainPage.idl déjà dans le projet C++/WinRT. C’est parce que le modèle de projet l’a créé pour nous. Mais plus loin dans cette procédure pas à pas, nous allons ajouter d’autres .idl fichiers au projet.
Nous allons bientôt voir une liste indiquant exactement quel IDL nous devons ajouter au fichier existant MainPage.idl. Avant cela, nous devons réfléchir à ce qui doit, et à ce qui ne doit pas, figurer dans l’IDL.
Pour déterminer les membres de MainPage dans lesquels nous devons déclarer MainPage.idl (afin qu’ils fassent partie de la classe runtime MainPage ), et qui peuvent simplement être membres du type d’implémentation MainPage , nous allons faire une liste des membres de la classe MainPage C#. Nous trouvons ces membres en regardant dans MainPage.xaml.cs et dans SampleConfiguration.cs.
Nous trouvons un total de douze champs protected et private méthodes. Et nous trouvons les membres suivants public .
- Constructeur par défaut
MainPage(). - Champs statiques Actif et FEATURE_NAME.
- Les propriétés IsClipboardContentChangedEnabled et Scenarios.
- Les méthodes BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotifications et NotifyUser.
Ce sont ces public membres qui sont susceptibles d’être déclarés dans MainPage.idl. Examinons donc chacun d’eux et voyons s’ils doivent faire partie de la classe runtime MainPage ou s’ils doivent uniquement faire partie de son implémentation.
- Constructeur par défaut
MainPage(). Pour une page XAML, il est normal de déclarer un constructeur par défaut dans son IDL. De cette façon, le framework d’interface utilisateur XAML peut activer le type. - Le champ statique Actif est utilisé à partir des pages XAML du scénario individuel pour accéder à l’instance de MainPage de l’application. Étant donné que Current n’est pas utilisé pour interagir avec l’infrastructure XAML (ni utilisé entre les unités de compilation), nous pourrions le réserver pour être uniquement membre du type d’implémentation. Avec vos propres projets dans des cas comme celui-ci, vous pouvez choisir de le faire. Toutefois, étant donné que le champ est une instance du type projeté, il est logique de le déclarer dans l’IDL. C’est ce que nous allons faire ici (et cela rend également le code légèrement plus propre).
- Il s’agit d’un cas similaire pour le champ de FEATURE_NAME statique, accessible dans le type MainPage . Là encore, le choix de le déclarer dans l’IDL rend notre code légèrement plus propre.
- La propriété IsClipboardContentChangedEnabled est utilisée uniquement dans la classe OtherScenarios . Par conséquent, pendant le port, nous allons simplifier les choses un peu et en faire un champ privé de la classe runtime OtherScenarios . Pour que ça n’aille pas dans l’IDL.
- La propriété Scenarios est une collection d’objets de type Scenario (un type que nous avons mentionné précédemment). Nous parlerons de Scenario dans la sous-section suivante ; laissons donc également de côté la propriété Scenarios jusque-là.
- Les méthodes BuildClipboardFormatsOutputString, DisplayToast et EnableClipboardContentChangedNotifications sont des fonctions utilitaires qui semblent davantage liées à l’état général de l’exemple qu’à la page principale. Par conséquent, pendant le port, nous refactorisons ces trois méthodes sur un nouveau type d'utilitaire nommé SampleState (qui n'a pas besoin d'être un type Windows Runtime). Pour cette raison, ces trois méthodes ne seront pas intégrées à l’IDL.
- La méthode NotifyUser est appelée depuis les pages XAML de chaque scénario, sur l’instance de MainPage renvoyée par le champ statique Current. Étant donné que (comme indiqué déjà) Current est une instance du type projeté, nous devons déclarer NotifyUser dans l’IDL. NotifyUser prend un paramètre de type NotifyType. Nous allons en parler dans la sous-section suivante.
Tout membre auquel vous souhaitez lier des données doit également être déclaré dans l’IDL (que vous utilisiez {x:Bind} ou {Binding}). Pour plus d’informations, consultez Liaison de données.
Nous progressons : nous développons une liste des membres à ajouter, et qui ne doivent pas être ajoutés au MainPage.idl fichier. Mais nous devons toujours discuter de la propriété Scenarios et du type NotifyType . Nous allons donc le faire ensuite.
IDL pour les types Scenario et NotifyType
La classe Scenario est définie dans SampleConfiguration.cs. Nous avons une décision à prendre sur la manière de porter cette classe en C++/WinRT. Par défaut, nous le ferions probablement en C++ structordinaire. Toutefois, si Le scénario est utilisé dans des fichiers binaires ou pour interagir avec l’infrastructure XAML, il doit être déclaré dans IDL en tant que type Windows Runtime.
En étudiant le code source C#, nous constatons que le scénario est utilisé dans ce contexte.
<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;
Une collection d’objets Scenario est affectée à la propriété ItemsSource d’un ListBox (qui est un contrôle d’éléments). Dans la mesure où le scénariodoit interagir avec XAML, il doit s’agir d’un type Windows Runtime. Il doit donc être défini dans IDL. Le fait de définir le type Scenario dans l’IDL amène le système de génération C++/WinRT à générer pour vous une définition de code source de Scenario dans un fichier d’en-tête interne (dont le nom et l’emplacement n’ont pas d’importance dans cette procédure pas à pas).
Et vous vous souviendrez que MainPage.Scenarios est une collection d’objets Scenario, dont nous venons de dire qu’ils doivent être en IDL. Pour cette raison, MainPage.Scenarios lui-même doit également être déclaré dans l’IDL.
NotifyType est un enum déclaré dans le MainPage.xaml.cs de C#. Étant donné que nous transmettons NotifyType à une méthode appartenant à la classe runtime MainPage, NotifyType doit également être un type Windows Runtime ; et il doit être défini dans MainPage.idl.
Nous allons maintenant ajouter au MainPage.idl fichier les nouveaux types et le nouveau membre de Mainpage que nous avons décidé de déclarer dans IDL. En même temps, nous allons supprimer de l’IDL les membres fictifs de Mainpage générés par le modèle de projet Visual Studio.
Par conséquent, dans votre projet C++/WinRT, ouvrez MainPage.idlet modifiez-le afin qu’il ressemble à la liste ci-dessous. Notez que l’une des modifications consiste à remplacer le nom de l’espace de noms de Clipboard par SDKTemplate. Si vous le souhaitez, vous pouvez simplement remplacer l’intégralité du contenu de MainPage.idl par le code suivant. Un autre ajustement à noter est que nous modifions le nom de Scenario ::ClassType en 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
Pour plus d’informations sur le contenu d’un .idl fichier dans un projet C++/WinRT, consultez Microsoft langage de définition d’interface 3.0.
Avec votre propre travail de portage, vous ne souhaiterez peut-être pas ou n’avez pas besoin de modifier le nom de l’espace de noms comme nous l’avons fait ci-dessus. Nous le faisons ici uniquement parce que l’espace de noms par défaut du projet C# que nous portons est SDKTemplate, alors que le nom du projet et de l’assembly est Clipboard.
Toutefois, au fur et à mesure que nous poursuivons ce portage dans ce guide pas à pas, nous remplacerons, dans le code source, chaque occurrence du nom d’espace de noms Clipboard par SDKTemplate. Il y a aussi un endroit dans les propriétés du projet C++/WinRT où apparaît le nom de l’espace de noms Clipboard, nous allons donc en profiter pour le modifier maintenant.
Dans Visual Studio, pour le projet C++/WinRT, définissez la propriété du projet Propriétés communes>C++/WinRT>Espace de noms racine sur SDKTemplate.
Enregistrez l’IDL et recréez les fichiers stub
La rubrique Contrôles XAML ; liaison à une propriété C++/WinRT présente la notion de fichiers d’ébauche et vous montre, étape par étape, comment ils fonctionnent. Nous avons également mentionné les stubs précédemment dans cette rubrique lorsque nous avons mentionné que le système de génération C++/WinRT transforme le contenu de vos .idl fichiers en métadonnées Windows, puis à partir de ces métadonnées, un outil nommé cppwinrt.exe génère des stubs sur lesquels vous pouvez baser votre implémentation.
Chaque fois que vous ajoutez, supprimez ou modifiez quelque chose dans votre IDL et générez, le système de génération met à jour les implémentations stub dans ces fichiers stubs. Ainsi, chaque fois que vous modifiez votre IDL et générez, nous vous recommandons d’afficher ces fichiers stubs, de copier les signatures modifiées et de les coller dans votre projet. Nous allons donner plus de détails et d’exemples d’une façon exacte de le faire dans un moment. Mais l’avantage de faire cela est de vous donner un moyen fiable, sans risque d’erreur, de savoir à tout moment quelle doit être la forme de votre type d’implémentation et quelle doit être la signature de ses méthodes.
À ce stade de la procédure pas à pas, nous avons terminé de modifier le MainPage.idl fichier pour le moment. Vous devez donc l’enregistrer maintenant. Le projet ne pourra pas être compilé jusqu’au bout pour le moment, mais lancer une compilation maintenant est utile, car cela régénère les fichiers stub pour MainPage. Générez donc le projet maintenant et ignorez les erreurs de build.
Pour ce projet C++/WinRT, les fichiers stub sont générés dans le \Clipboard\Clipboard\Generated Files\sources dossier. Vous les trouverez là une fois la compilation partielle terminée (à nouveau, comme prévu, la compilation ne réussira pas entièrement. Mais l’étape qui nous intéresse — la génération des stubs — aura bien réussi). Les fichiers qui nous intéressent sont MainPage.h et MainPage.cpp.
Dans ces deux fichiers stub, vous verrez de nouvelles implémentations stub des membres de MainPage que nous avons ajoutés à l’IDL (Actuel et FEATURE_NAME, par exemple). Vous devrez copier ces implémentations factices dans les fichiers MainPage.h et MainPage.cpp déjà présents dans le projet. En même temps, tout comme nous l'avons fait avec l'IDL, nous allons supprimer de ces fichiers existants les membres de l'espace réservé de Mainpage que le modèle de projet Visual Studio nous a donné (la propriété factice nommée MyProperty et le gestionnaire d'événements nommé ClickHandler).
En fait, le seul membre de la version actuelle de MainPage que nous voulons conserver est le constructeur.
Une fois que vous avez copié les nouveaux membres depuis les fichiers stub, supprimé les membres indésirables et mis à jour l’espace de noms, les fichiers MainPage.h et MainPage.cpp de votre projet devraient ressembler aux extraits de code ci-dessous. Notez qu’il existe deux types MainPage . L’un dans l’espace de noms implementation, et le second dans l’espace de noms factory_implementation. La seule modification que nous avons apportée au factory_implementation est d’ajouter SDKTemplate à son espace de noms.
// 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();
}
}
Pour les chaînes, C# utilise System.String. Consultez la méthode MainPage.NotifyUser pour obtenir un exemple. Dans notre IDL, nous déclarons une chaîne avec String et lorsque l’outil génère du cppwinrt.exe code C++/WinRT pour nous, il utilise le type winrt ::hstring . Chaque fois que nous allons rencontrer une chaîne dans le code C#, nous allons le porter vers winrt ::hstring. Pour plus d’informations, consultez Gestion des chaînes en C++/WinRT.
Pour obtenir une explication des paramètres dans les const& signatures de méthode, consultez Le passage de paramètre.
Mettre à jour toutes les déclarations et références d’espace de noms restantes, puis compiler
Avant de générer le projet C++/WinRT, recherchez toutes les déclarations de l’espace de noms Clipboard (et les références à celui-ci), puis remplacez-les par SDKTemplate.
-
MainPage.xamletApp.xaml. L’espace de noms apparaît dans les valeurs des attributsx:Classetxmlns:local. -
App.idl. -
App.h. -
App.cpp. Il existe deux directivesusing namespace(recherchez la sous-chaîneusing namespace Clipboard) et deux qualifications de type MainPage (recherchezClipboard::MainPage). Ceux-ci ont besoin de changer.
Puisque nous avons supprimé le gestionnaire d’événements de MainPage, allez également dans MainPage.xaml et supprimez l’élément Button du balisage.
Enregistrez tous les fichiers. Nettoyez la solution (Générer> unesolution propre), puis générez-la. Après avoir suivi toutes les modifications jusqu’à présent, exactement comme écrit, la build devrait réussir.
Implémenter les membres MainPage que nous avons déclarés dans IDL
Le constructeur, Current et FEATURE_NAME
Voici le code approprié (à partir du projet C#) que nous devons porter.
<!-- 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";
...
}
...
Bientôt, nous allons réutiliser MainPage.xaml dans son intégralité (en le copiant). Pour l’instant (ci-dessous), nous allons ajouter temporairement un élément TextBlock , avec le nom approprié, dans le MainPage.xaml projet C++/WinRT.
FEATURE_NAME est un champ statique de MainPage (un champ C# const est essentiellement statique dans son comportement), défini dans SampleConfiguration.cs. Pour C++/WinRT, au lieu d’un champ (statique), nous allons le rendre l’expression C++/WinRT d’une propriété en lecture seule (statique). En C++/WinRT, un accesseur de lecture de propriété s’exprime par une fonction qui renvoie la valeur de la propriété et ne prend aucun paramètre (un accesseur). Par conséquent, le champ statique FEATURE_NAME en C# devient la fonction d’accesseur statique FEATURE_NAME en C++/WinRT (dans ce cas, qui retourne la chaîne littérale).
Au passage, nous ferions la même chose si nous conversions une propriété C# en lecture seule. Pour une propriété accessible en écriture C#, la méthode C++/WinRT d’exprimer un jeu de propriétés est une void fonction qui prend la valeur de propriété en tant que paramètre (un mutateur). Dans les deux cas, si le champ ou la propriété C# est statique, l’accesseur et/ou le mutateur C++/WinRT l’est également.
Current est un champ statique (et non une constante) de MainPage. Là encore, nous allons le rendre (l’expression C++/WinRT de) une propriété en lecture seule, puis le rendre statique. Où FEATURE_NAME est constante, Current n’est pas. Ainsi, en C++/WinRT, nous aurons besoin d’un champ sous-jacent, et notre accesseur le retournera. Par conséquent, dans le projet C++/WinRT, nous allons déclarer dans MainPage.h un champ statique privé nommé current, nous allons définir/initialiser le courant dans MainPage.cpp (car il a une durée de stockage statique) et nous allons y accéder via une fonction d’accesseur statique publique nommée Current.
Le constructeur lui-même effectue deux ou trois affectations, qui sont faciles à porter.
Dans le projet C++/WinRT, ajoutez un nouvel élément Visual C++>Code>Fichier C++ (.cpp) avec le nom SampleConfiguration.cpp.
Modifiez MainPage.xaml, MainPage.h, MainPage.cppet SampleConfiguration.cpp pour faire correspondre les descriptions ci-dessous.
<!-- 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";
}
Veillez également à supprimer les corps de fonctions existants de MainPage.cppMainPage ::Current() et MainPage ::FEATURE_NAME(), car nous définissons maintenant ces méthodes ailleurs.
Comme vous pouvez le voir, MainPage ::current est déclaré comme étant de type SDKTemplate ::MainPage, qui est le type projeté. Il ne s’agit pas du type SDKTemplate ::implementation ::MainPage, qui est le type d’implémentation. Le type projeté est celui qui est conçu pour être consommé dans le projet pour l’interopérabilité XAML ou entre les fichiers binaires. Le type d’implémentation correspond à ce que vous utilisez pour implémenter les fonctionnalités que vous avez exposées dans votre type projeté. Étant donné que la déclaration de MainPage ::current (in MainPage.h) apparaît dans l’espace de noms d’implémentation (winrt ::SDKTemplate ::implementation), un MainPage non qualifié aurait fait référence au type d’implémentation. Nous utilisons donc SDKTemplate:: afin d’indiquer clairement que nous voulons que MainPage::current soit une instance du type projeté winrt::SDKTemplate::MainPage.
Dans le constructeur, il y a quelques points concernant MainPage::current = *this; qui méritent une explication.
- Lorsque vous utilisez le
thispointeur à l’intérieur d’un membre du type d’implémentation, lethispointeur est bien sûr un pointeur vers le type d’implémentation. - Pour convertir le
thispointeur en type projeté correspondant, déréférencez-le. À condition de générer votre type d’implémentation à partir d’IDL (comme nous l’avons ici), le type d’implémentation a un opérateur de conversion qui se convertit en son type projeté. C’est pourquoi l’attribution fonctionne ici.
Pour plus d’informations sur ces détails, consultez Instanciation et retour des types et interfaces d’implémentation.
Il y a également SampleTitle().Text(FEATURE_NAME()); dans le constructeur. La SampleTitle() partie est un appel à une fonction d’accesseur simple nommée SampleTitle, qui retourne le TextBlock que nous avons ajouté au code XAML. Chaque fois que vous x:Name un élément XAML, le compilateur XAML génère pour vous un accesseur portant le nom de l’élément. L’élément .Text(...) appelle la fonction de mutation Text de l’objet TextBlock renvoyé par l’accesseur SampleTitle. Et FEATURE_NAME() appelle notre fonction d’accesseur statique MainPage::FEATURE_NAME pour renvoyer la chaîne littérale. Au total, cette ligne de code définit la propriété Text du TextBlock nommé SampleTitle.
Notez que, comme les chaînes de caractères utilisent des caractères larges dans Windows Runtime, pour porter une chaîne littérale, nous la faisons précéder du préfixe d’encodage en caractères larges L. Nous remplaçons donc (par exemple) « un littéral de chaîne » par L« un littéral de chaîne ». Voir également littéraux de chaîne large.
Scénarios
Voici le code C# approprié que nous devons porter.
// 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) }
};
...
}
...
À partir de notre enquête précédente, nous savons que cette collection d’objets Scenario est affichée dans un ListBox. Dans C++/WinRT, il existe des limites au type de collection que nous pouvons affecter à la propriété ItemsSource d’un contrôle d’éléments. La collection doit être un vecteur ou un vecteur observable, et ses éléments doivent être l’un des éléments suivants :
- Soit des classes d’exécution, soit
- IInspectable.
Dans le cas de IInspectable, si les éléments ne sont pas eux-mêmes des classes d’exécution, alors ils doivent être d’un type pouvant être encapsulé et désencapsulé dans et hors de IInspectable. Cela signifie qu’ils doivent être des types Windows Runtime (voir Mise en boîte et déballage des valeurs en IInspectable).
Pour cette étude de cas, nous n’avons pas fait de Scénario une classe runtime. C’est encore une option raisonnable, cependant. Et dans le cadre de vos propres travaux de portage, il y aura des cas où une classe d’exécution sera clairement la bonne solution. Par exemple, si vous devez rendre le type d’élément observable (voir contrôles XAML ; lier à une propriété C++/WinRT), ou si l’élément doit avoir des méthodes pour toute autre raison, et qu’il s’agit d’un ensemble de membres de données.
Étant donné que, dans cette procédure pas à pas, nous n’utilisons pas de classe d’exécution pour le type Scenario, nous devons alors tenir compte du boxing. Si nous avions fait de Scenario un C++ struct ordinaire, nous n’aurions pas pu l’encapsuler. Mais nous avons déclaré Scenario comme struct dans l’IDL, et nous pouvons donc l’encapsuler.
Il ne nous reste donc que le choix de boxer le Scenario à l’avance, ou d’attendre le moment d’effectuer l’affectation à ItemsSource pour les boxer au dernier moment. Voici quelques considérations relatives à ces deux options.
- Boxe à l’avance. Pour cette option, notre élément de données est une collection de IInspectable prête à être assignée à l’interface utilisateur. Lors de l’initialisation, nous boxons les objets Scenario dans ce membre de données. Nous n’avons besoin que d’une seule copie de cette collection, mais nous devons déballer un élément chaque fois que nous devions lire ses champs.
- Boxe juste à temps. Pour cette option, notre champ de données est une collection de Scenario. Au moment de l’affectation à l’interface utilisateur, nous encapsulons les objets Scenario contenus dans le membre de données dans une nouvelle collection de IInspectable. Nous pouvons lire les champs des éléments du membre de données sans désempaqueter, mais nous avons besoin de deux copies de la collection.
Comme vous pouvez le voir, pour une petite collection comme celle-ci, les avantages et les inconvénients s’équilibrent plus ou moins. Donc, pour cette étude de cas, nous allons opter pour l’option just-in-time.
Le membre des scénarios est un champ de MainPage, défini et initialisé dans SampleConfiguration.cs. Et Scenarios est une propriété en lecture seule de MainPage, définie dans MainPage.xaml.cs (et implémentée pour simplement retourner le champ scénarios ). Nous allons faire quelque chose de similaire dans le projet C++/WinRT ; mais nous allons rendre les deux membres statiques (étant donné que nous avons besoin d’une seule instance dans l’application ; et pour pouvoir y accéder sans avoir besoin d’une instance de classe). Et nous allons les nommer scénariosInner et scénarios, respectivement. Nous allons déclarer scenariosInner dans MainPage.h. Et, étant donné qu’elle a une durée de stockage statique, nous allons la définir/l’initialiser dans un .cpp fichier (SampleConfiguration.cppdans ce cas).
Modifiez MainPage.h et SampleConfiguration.cpp pour qu’ils correspondent aux listes ci-dessous.
// 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>() },
});
Veillez également à supprimer le corps de fonction existant de MainPage.cppMainPage ::scenarios(), car nous définissons maintenant cette méthode dans le fichier d’en-tête.
Comme vous pouvez le voir, dans SampleConfiguration.cpp, nous initialisons le membre de données statique scenariosInner en appelant une fonction d’assistance C++/WinRT nommée winrt::single_threaded_observable_vector. Cette fonction crée un objet de collection Windows Runtime pour nous et le retourne en tant qu’interface IObservableVector. Étant donné que, dans cet exemple, la collection n’est pas observable (elle n’a pas besoin d’être, car elle n’ajoute ni ne supprime pas d’éléments après l’initialisation), nous pourrions plutôt avoir choisi d’appeler winrt ::single_threaded_vector. Cette fonction retourne la collection en tant qu’interface IVector .
Pour plus d’informations sur les collections et leur liaison, consultez les contrôles d’éléments XAML ; liaison à une collection C++/WinRT et Collections avec C++/WinRT.
Le code d’initialisation que vous venez d’ajouter fait référence à des types qui ne sont pas encore dans le projet (par exemple, winrt::SDKTemplate::CopyText. Pour remédier à cela, allons-y et ajoutons cinq nouvelles pages XAML vides au projet.
Ajouter cinq nouvelles pages XAML vides
Ajoutez au projet un nouvel élément Visual C++>Page vide (C++/WinRT) (assurez-vous qu’il s’agit bien du modèle d’élément Page vide (C++/WinRT), et non de celui Page vide). Nommez-le CopyText. La nouvelle page XAML est définie dans l’espace de noms SDKTemplate , c’est-à-dire ce que nous voulons.
Répétez le processus ci-dessus quatre fois et nommez les pages CopyImageXAML, CopyFiles, HistoryAndRoaminget OtherScenarios.
Vous serez maintenant en mesure de générer à nouveau, si vous le souhaitez.
NotifyUser
Dans le projet C#, vous trouverez l’implémentation de la méthode MainPage.NotifyUser dans MainPage.xaml.cs.
MainPage.NotifyUser a une dépendance sur MainPage.UpdateStatus, et cette méthode à son tour a des dépendances sur les éléments XAML que nous n’avons pas encore portés. Pour l’instant, nous allons simplement créer une implémentation factice de la méthode UpdateStatus dans le projet C++/WinRT, et nous en ferons le portage plus tard.
Voici le code C# approprié que nous devons porter.
// 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 distribue les mises à jour de l’interface utilisateur vers le thread principal. Dans WinUI 3, cela utilise Microsoft. Interface utilisateur. Dispatching.DispatcherQueue au lieu de l’ancien CoreDispatcher. Dans C++/WinRT, chaque fois que vous souhaitez utiliser un type à partir d’un espace de noms Windows ou Microsoft, vous devez inclure le fichier d’en-tête d’espace de noms C++/WinRT correspondant (pour plus d’informations à ce sujet, consultez Prise en main de C++/WinRT). Dans ce cas, comme vous le verrez dans la liste de codes ci-dessous, l'en-tête est winrt/Microsoft.UI.Dispatching.h, et nous allons l'inclure dans pch.h.
UpdateStatus est privé. Nous allons donc en faire une méthode privée dans notre type d’implémentation MainPage. UpdateStatus n’est pas destiné à être appelé sur la classe runtime. Nous ne le déclarerons donc pas dans IDL.
Après avoir porté MainPage.NotifyUser et créé une version factice de MainPage.UpdateStatus, voici ce que nous avons dans le projet C++/WinRT. Après cette liste de codes, nous examinerons quelques-uns des détails.
// 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#, vous pouvez utiliser la notation par point pour accéder à des propriétés imbriquées. Par conséquent, le type MainPage C# peut accéder à sa propre propriété Dispatcher avec la syntaxe Dispatcher. Et C# peut en outre accéder à cette valeur avec une syntaxe comme Dispatcher.HasThreadAccess. Dans C++/WinRT, les propriétés sont implémentées en tant que fonctions d’accesseur. La syntaxe diffère donc uniquement dans le fait que vous ajoutez des parenthèses pour chaque appel de fonction.
| C# | C++/WinRT |
|---|---|
Dispatcher.HasThreadAccess |
DispatcherQueue().HasThreadAccess() |
Lorsque la version C# de NotifyUser appelle Dispatcher.RunAsync, l’équivalent WinUI 3 utilise DispatcherQueue.TryEnqueue. La version C++/WinRT implémente le délégué de rappel en tant que fonction lambda. Dans C++/WinRT, nous capturez les deux paramètres que nous allons utiliser, ainsi que le this pointeur (car nous allons appeler une fonction membre). Vous trouverez plus d’informations sur l’implémentation de délégués en tant qu’expressions lambda et des exemples de code, dans la rubrique Gérer les événements à l’aide de délégués en C++/WinRT.
Implémenter les membres MainPage restants
Nous allons faire une liste complète des membres de MainPage (implémentés à travers MainPage.xaml.cs et SampleConfiguration.cs) afin que nous puissions voir ceux que nous avons portés jusqu’à présent, et ceux qui sont encore à faire.
| Membre | Accès | Status |
|---|---|---|
| constructeur de MainPage | public |
Adapté |
| Propriété actuelle | public |
Porté |
| propriété FEATURE_NAME | public |
Porté |
| propriété IsClipboardContentChangedEnabled | public |
Non commencé |
| Propriété Scénarios | public |
Porté |
| BuildClipboardFormatsOutputString , méthode | public |
Non commencé |
| DisplayToast , méthode | public |
Non commencé |
| méthode EnableClipboardContentChangedNotifications | public |
Non commencé |
| Méthode NotifyUser | public |
Porté |
| OnNavigatedTo , méthode | protected |
Non commencé |
| le champ isApplicationWindowActive | private |
Non commencé |
| champ needToPrintClipboardFormat | private |
Non commencé |
| champ scénarios | private |
Porté |
| méthode Button_Click | private |
Non commencé |
| DisplayChangedFormats , méthode | private |
Non commencé |
| méthode Footer_Click | private |
Non commencé |
| méthode HandleClipboardChanged | private |
Non commencé |
| méthode OnClipboardChanged | private |
Non commencé |
| OnWindowActivated , méthode | private |
Non commencé |
| méthode ScenarioControl_SelectionChanged | private |
Non commencé |
| UpdateStatus , méthode | private |
Détoublé |
Nous allons donc parler des membres qui n’ont pas encore été portés dans les sous-sections suivantes.
Note
De temps à autre, nous allons rencontrer des références dans le code source aux éléments d’interface utilisateur dans le balisage XAML (en MainPage.xaml). À mesure que nous arrivons à ces références, nous allons temporairement les contourner en ajoutant des éléments d’espace réservé simples au code XAML. Ainsi, le projet continuera de générer après chaque sous-section. L’alternative consiste à résoudre les références en copiant l’intégralité du contenu du MainPage.xaml projet C# vers le projet C++/WinRT maintenant. Mais si nous faisons cela, il faudra longtemps avant que nous puissions faire une pause et relancer une compilation (ce qui risque de masquer les fautes de frappe ou autres erreurs que nous commettons en cours de route).
Une fois le portage du code impératif pour la classe MainPage terminé, nous pourrons alors copier le contenu du fichier XAML et être certains que le projet compilera toujours.
IsClipboardContentChangedEnabled
Il s’agit d’une propriété C# get-set qui a la falsevaleur par défaut . Il s’agit d’un membre de MainPage et est défini dans SampleConfiguration.cs.
Pour C++/WinRT, nous aurons besoin d’un accesseur, d’un mutateur et d’un membre de données sous-jacent servant de champ. Étant donné que IsClipboardContentChangedEnabled représente l’état de l’un des scénarios de l’exemple, plutôt que l’état de MainPage lui-même, nous allons créer les nouveaux membres sur un nouveau type d’utilitaire appelé SampleState. Et nous allons implémenter cela dans notre SampleConfiguration.cpp fichier de code source, et nous allons rendre les membres static (étant donné que nous n’avons besoin que d’une seule instance dans l’application ; et afin que nous puissions y accéder sans avoir besoin d’une instance de classe).
Pour accompagner notre SampleConfiguration.cpp dans le projet C++/WinRT, ajoutez un nouvel élément Visual C++>Code>Fichier d’en-tête (.h) nommé SampleConfiguration.h. Modifiez SampleConfiguration.h et SampleConfiguration.cpp pour qu’ils correspondent aux listes ci-dessous.
// 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;
}
}
Là encore, un champ avec static stockage (par exemple SampleState ::isClipboardContentChangedEnabled) doit être défini une fois dans l’application, et un .cpp fichier est un bon endroit pour cela (SampleConfiguration.cpp dans ce cas).
BuildClipboardFormatsOutputString
Cette méthode est un membre public de MainPage et elle est définie dans 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();
}
...
Dans C++/WinRT, nous allons rendre BuildClipboardFormatsOutputString une méthode statique publique de SampleState. Nous pouvons le déclarer static, car il n’accède à aucun membre d’instance.
Pour utiliser les types Clipboard et DataPackageView en C++/WinRT, il faut inclure le fichier d’en-tête winrt/Windows.ApplicationModel.DataTransfer.h de l’espace de noms Windows de C++/WinRT.
En C#, la propriété DataPackageView.AvailableFormats est un IReadOnlyList. Nous pouvons donc accéder à la propriété Count de cette propriété. Dans C++/WinRT, la fonction d’accesseur DataPackageView ::AvailableFormats retourne un IVectorView, qui a une fonction d’accesseur Size que nous pouvons appeler.
Pour porter l’utilisation du type C# System.Text.StringBuilder , nous allons utiliser le type C++ standard std ::wostringstream. Ce type est un flux de sortie pour les chaînes larges (et pour l’utiliser, nous devons inclure le sstream fichier d’en-tête). Au lieu d’utiliser une méthode Append comme vous le faites avec un StringBuilder, vous utilisez l’opérateur d’insertion (<<) avec un flux de sortie tel que wostringstream. Pour plus d’informations, consultez programmation iostream et mise en forme des chaînes C++/WinRT.
Le code C# construit un StringBuilder avec le new mot clé. En C#, les objets sont des types de référence par défaut, déclarés sur le tas avec new. Dans la norme moderne C++, les objets sont des types valeur par défaut, déclarés sur la pile (sans utiliser new). Nous portons StringBuilder output = new StringBuilder(); donc vers C++/WinRT comme simplement std::wostringstream output;.
Le mot clé C# var demande au compilateur de déduire un type. Vous portez var vers auto en C++/WinRT. Toutefois, dans C++/WinRT, il existe des cas où, afin d’éviter les copies, vous souhaitez une reference vers un type inféré (ou déduit), et vous exprimez une référence lvalue à un type déduit au moyen de auto&. Il existe également des cas où vous souhaitez un type particulier de référence qui se lie correctement, qu’elle soit initialisée avec une lvalue ou avec une rvalue. Et vous exprimez cela avec auto&&. Il s’agit du formulaire que vous voyez utilisé dans la for boucle dans le code porté ci-dessous. Pour une présentation des valeurs lvalues et rvalues, consultez les catégories valeur et les références à celles-ci.
Modifiez pch.h, SampleConfiguration.het SampleConfiguration.cpp pour correspondre aux descriptions ci-dessous.
// 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 syntaxe de la ligne de code DataPackageView clipboardContent{ Clipboard::GetContent() }; utilise une fonctionnalité de l’initialisation standard moderne C++ appelée initialisation uniforme, avec son utilisation caractéristique des crochets courbés au lieu d’un = signe. Cette syntaxe indique clairement que l’initialisation, plutôt que l’affectation, est en cours. Si vous préférez la forme de syntaxe qui ressemble à l’affectation (mais en fait n’est pas), vous pouvez remplacer la syntaxe ci-dessus par l’équivalent DataPackageView clipboardContent = Clipboard::GetContent();. Il est judicieux de se familiariser avec les deux façons d’exprimer l’initialisation, cependant, car vous êtes susceptible de voir les deux utilisés fréquemment dans le code que vous rencontrez.
DisplayToast
DisplayToast est une méthode statique publique de la classe MainPage C# et vous la trouverez définie dans SampleConfiguration.cs. Dans C++/WinRT, nous allons le rendre une méthode statique publique de SampleState.
Nous avons déjà rencontré la plupart des détails et techniques pertinents pour le portage de cette méthode. Un nouvel élément à noter est que vous portez un littéral de chaîne détaillé C# (@) vers un littéral de chaîne brute C++ standard (LR).
En outre, lorsque vous référencez les types ToastNotification et XmlDocument en C++/WinRT, vous pouvez les qualifier par nom d’espace de noms, ou vous pouvez modifier SampleConfiguration.cpp et ajouter using namespace des directives telles que l’exemple suivant.
using namespace Windows::UI::Notifications;
Vous avez le même choix lorsque vous référencez le type XmlDocument, et chaque fois que vous référencez un autre type Windows Runtime.
Outre ces éléments, suivez simplement les mêmes instructions que celles que vous avez effectuées précédemment pour effectuer les étapes suivantes.
- Déclarez la méthode dans
SampleConfiguration.h, puis définissez-la dansSampleConfiguration.cpp. - Modifiez
pch.hpour inclure tous les fichiers d’en-tête d’espace de noms Windows C++/WinRT nécessaires. - Construisez des objets C++/WinRT sur la pile, et non sur le tas.
- Remplacez les appels aux accesseurs de propriété get par la syntaxe d’appel de fonction (
()).
Une cause très courante des erreurs de compilation/édition de liens est l’oubli d’inclure les fichiers d’en-tête de l’espace de noms Windows de C++/WinRT dont vous avez besoin. Pour plus d’informations sur une erreur possible, consultez C3779 : Pourquoi le compilateur me donne-t-il une erreur « consume_Something : fonction qui retourne « auto » ne peut pas être utilisée avant qu’elle soit définie ?
Si vous souhaitez suivre le guide pas à pas et porter DisplayToast vous-même, vous pouvez comparer vos résultats avec le code de la version C++/WinRT dans l’archive ZIP du code source de l’exemple Clipboard sample que vous avez téléchargée.
EnableClipboardContentChangedNotifications
EnableClipboardContentChangedNotifications est une méthode statique publique de la classe MainPage C# et elle est définie dans 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) { ... }
...
Dans C++/WinRT, nous allons le rendre une méthode statique publique de SampleState.
En C#, vous utilisez la syntaxe des opérateurs += et -= pour abonner et désabonner des délégués de gestion d’événements. Dans C++/WinRT, vous avez plusieurs options syntactiques pour inscrire/révoquer un délégué, comme décrit dans Gérer les événements à l’aide de délégués en C++/WinRT. Mais, de manière générale, vous vous abonnez et vous vous désabonnez au moyen d’appels à une paire de fonctions portant le nom de l’événement. Pour vous enregistrer, vous transmettez votre délégué à la fonction d’enregistrement et récupérez en échange un jeton de révocation (un winrt::event_token). Pour révoquer, vous transmettez ce jeton à la fonction de révocation. Dans ce cas, le hander est statique et (comme vous pouvez le voir dans la liste de codes suivante) la syntaxe d’appel de fonction est simple.
Des jetons similaires sont réellement utilisés, en arrière-plan, en C#. Mais la langue rend ce détail implicite. C++/WinRT le rend explicite.
Le type d’objet apparaît dans les signatures du gestionnaire d’événements C#. Dans le langage C#, l’objet est un alias pour le type System.Object .NET. L’équivalent en C++/WinRT est winrt ::Windows ::Foundation ::IInspectable. Vous verrez donc IInspectable dans les gestionnaires d’événements C++/WinRT.
Modifiez SampleConfiguration.h et SampleConfiguration.cpp pour qu’ils correspondent aux listes ci-dessous.
// 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){}
Laissez les délégués de gestion d’événements eux-mêmes (OnClipboardChanged et OnWindowActivated) à l’état de squelettes pour l’instant. Ils figurent déjà sur notre liste des éléments à porter, donc nous les aborderons dans les sous-sections suivantes.
OnNavigatedTo
OnNavigatedTo est une méthode protégée de la classe MainPage C# et elle est définie dans MainPage.xaml.cs. Le voici, ainsi que le XAML ListBox auquel il fait référence.
<!-- 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;
}
}
Il s’agit d’une méthode importante et intéressante, car voici où notre collection d’objets Scenario est affectée à l’interface utilisateur. Le code C# génère un objet System.Collections.Generic.List of Scenario et l’affecte à la propriété ItemsSource d’un ListBox (qui est un contrôle d’éléments). En C#, nous utilisons l’interpolation de chaîne pour générer le titre de chaque objet Scenario (notez l’utilisation du $ caractère spécial).
Dans C++/WinRT, nous allons rendre OnNavigatedTo une méthode publique de MainPage. Et nous allons ajouter un élément ListBox stub au code XAML afin qu’une build réussisse. Après la liste des codes, nous allons examiner certains des détails.
<!-- 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);
}
}
...
Là encore, nous appelons la fonction winrt::single_threaded_observable_vector, mais cette fois pour créer une collection de IInspectable. Cela faisait partie de notre décision d’effectuer l’encapsulage de nos objets Scenario à la demande.
Et, à la place de l’utilisation, en C#, de l’interpolation de chaînes ici, nous utilisons la fonction to_hstring combinée à l’opérateur de concaténation de winrt::hstring.
isApplicationWindowActive
En C#, isApplicationWindowActive est un champ privé bool simple appartenant à la classe MainPage , et il est défini dans SampleConfiguration.cs. Sa valeur par défaut est false. Dans C++/WinRT, nous allons en faire un champ membre statique privé de SampleState (pour les raisons que nous avons déjà décrites) dans les fichiers SampleConfiguration.h et SampleConfiguration.cpp, avec la même valeur par défaut.
Nous avons déjà vu comment déclarer, définir et initialiser un champ statique. Pour vous rafraîchir la mémoire, repensez à ce que nous avons fait avec le champ isClipboardContentChangedEnabled et faites de même avec isApplicationWindowActive.
needToPrintClipboardFormat
Même modèle que isApplicationWindowActive (voir le titre juste avant celui-ci).
Button_Click
Button_Click est une méthode privée (gestion des événements) de la classe MainPage C# et définie dans MainPage.xaml.cs. Le voici, ainsi que le SplitView XAML auquel il fait référence, et le ToggleButton qui l’enregistre.
<!-- MainPage.xaml -->
...
<SplitView x:Name="Splitter" ... />
...
<ToggleButton Click="Button_Click" .../>
...
private void Button_Click(object sender, RoutedEventArgs e)
{
Splitter.IsPaneOpen = !Splitter.IsPaneOpen;
}
Et voici l’équivalent, porté vers C++/WinRT. Notez que dans la version C++/WinRT, le gestionnaire d’événements est public (comme vous pouvez le voir, vous le déclarez avant les private:déclarations). Cela est dû au fait qu’un gestionnaire d’événements inscrit dans le balisage XAML, comme celui-ci, doit être public en C++/WinRT pour que le balisage XAML y accède. En revanche, si vous inscrivez un gestionnaire d’événements dans du code impératif (comme nous l’avons fait dans MainPage ::EnableClipboardContentChangedNotifications précédemment), le gestionnaire d’événements n’a pas besoin d’être 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());
}
Afficher les formats modifiés
En C#, DisplayChangedFormats est une méthode privée appartenant à la classe MainPage , et elle est définie dans SampleConfiguration.cs.
private void DisplayChangedFormats()
{
string output = "Clipboard content has changed!" + Environment.NewLine;
output += BuildClipboardFormatsOutputString();
NotifyUser(output, NotifyType.StatusMessage);
}
Dans C++/WinRT, nous allons en faire un champ statique privé de SampleState (il n’accède à aucun membre d’instance), dans les fichiers SampleConfiguration.h et SampleConfiguration.cpp. Le code C# de cette méthode n’utilise pas System.Text.StringBuilder ; mais il fait suffisamment de mise en forme de chaîne pour la version C++/WinRT, il s’agit d’un autre bon endroit pour utiliser std ::wostringstream.
Au lieu de la propriété System.Environment.NewLine statique, qui est utilisée dans le code C#, nous allons insérer le C++ std::endl standard (un caractère de nouvelle ligne) dans le flux de sortie.
// 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);
}
Il existe une petite inefficacité dans la conception de la version C++/WinRT ci-dessus. Tout d’abord, nous créons un std ::wostringstream. Mais nous appelons également la méthode BuildClipboardFormatsOutputString (que nous avons porté précédemment). Cette méthode crée son propre std ::wostringstream. Et il transforme son flux en un winrt ::hstring et retourne cela. Nous appelons la fonction hstring ::c_str pour renvoyer ce hstring en chaîne de style C, puis nous l’insérons dans notre flux. Il serait plus efficace de créer un seul std::wostringstream et d’en faire passer une référence, afin que les méthodes puissent y insérer directement des chaînes.
C’est ce que nous faisons dans la version C++/WinRT du code source de l’exemple Clipboard (dans le fichier ZIP que vous avez téléchargé). Dans ce code source, il existe une nouvelle méthode statique privée nommée SampleState ::AddClipboardFormatsOutputString, qui prend et fonctionne sur une référence à un flux de sortie. Et ensuite, les méthodes SampleState::DisplayChangedFormats et SampleState::BuildClipboardFormatsOutputString sont refactorisées pour appeler cette nouvelle méthode. Elle est fonctionnellement équivalente aux listes de code de cette rubrique, mais elle est plus efficace.
Footer_Click
Footer_Click est un gestionnaire d’événements asynchrone appartenant à la classe MainPage C# et défini dans MainPage.xaml.cs. La liste de codes ci-dessous est fonctionnellement équivalente à la méthode dans le code source que vous avez téléchargé. Mais ici, je l’ai développé d’une ligne en quatre, pour qu’il soit plus facile de voir ce qu’il fait et, par conséquent, comment nous devrions le porter.
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);
}
Bien que, techniquement, la méthode soit asynchrone, elle ne fait rien après le await, il n’a donc pas besoin du await (ni du async mot clé). Il les utilise probablement pour éviter le message IntelliSense dans Visual Studio.
La méthode C++/WinRT équivalente sera également asynchrone (car elle appelle Launcher.LaunchUriAsync). Mais il n’a pas besoin de co_await, ni de retourner un objet asynchrone. Pour plus d’informations sur co_await et les objets asynchrones, consultez Concurrence et opérations asynchrones avec C++/WinRT.
Maintenant, parlons de ce que fait la méthode. Étant donné qu’il s’agit d’un gestionnaire d’événements pour l’événement Click d’un HyperlinkButton, l’objet nommé sender est en fait un HyperlinkButton. Par conséquent, la conversion de type est sécurisée (nous pourrions également avoir exprimé cette conversion en tant que sender as HyperlinkButton). Ensuite, nous récupérons la valeur de la propriété Tag (si vous examinez le balisage XAML dans le projet C#, vous verrez qu’il s’agit d’une chaîne représentant une URL web). Bien que la propriété FrameworkElement.Tag (HyperlinkButton est un FrameworkElement) soit de type objet, en C#, nous pouvons chaîner cela avec Object.ToString. À partir de la chaîne résultante, nous construisons un objet Uri . Et enfin (à l’aide du shell), nous lançons un navigateur et accédons à l’URL.
Voici la méthode transférée vers C++/WinRT (à nouveau, développée pour plus de clarté), après quoi est une description des détails.
// 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);
}
Comme toujours, nous créons le gestionnaire d’événements public. Nous utilisons la fonction as sur l’objet sender pour le caster en HyperlinkButton. Dans C++/WinRT, la propriété Tag est un IInspectable (équivalent d’Object). Mais il n’y a pas de Tostring dans IInspectable. Au lieu de cela, nous devons convertir le IInspectable en une valeur scalaire (une chaîne, en l’occurrence). Là encore, pour plus d’informations sur la boxe et l’unboxing, consultez les valeurs boxing et unboxing sur IInspectable.
Les deux dernières lignes répètent les modèles de portage que nous avons vus précédemment, et ils font presque écho à la version C#.
HandleClipboardChanged
Il n’y a rien de nouveau impliqué dans le portage de cette méthode. Vous pouvez comparer les versions C# et C++/WinRT dans le fichier ZIP du code source de l’exemple Clipboard que vous avez téléchargé.
OnClipboardChanged et OnWindowActivated
Jusqu’à présent, nous n’avons que des stubs vides pour ces deux gestionnaires d’événements. Mais les porter est simple, et cela n’apporte rien de nouveau à la discussion.
ScenarioControl_SelectionChanged
Il s’agit d’un autre gestionnaire d’événements privé appartenant à la classe MainPage C# et défini dans MainPage.xaml.cs. En C++/WinRT, nous allons le rendre public, et l’implémenter dans MainPage.h et MainPage.cpp.
Pour cette méthode, nous aurons besoin de MainPage ::navigation, qui est un champ booléen privé, initialisé à false. Et vous aurez besoin d’une trame dans MainPage.xaml, nommée ScenarioFrame. Mais, à part ces détails, le portage de cette méthode ne révèle aucune nouvelle technique.
Si, au lieu d’effectuer le portage manuellement, vous copiez du code depuis la version C++/WinRT dans le fichier ZIP du code source de l’exemple Clipboard sample que vous avez téléchargé, alors vous verrez qu’un MainPage::NavigateTo y est utilisé. Pour l’instant, refactorisez simplement le contenu de NavigateTo dans ScenarioControl_SelectionChanged.
UpdateStatus
Nous n’avons qu’un stub jusqu’à présent pour MainPage.UpdateStatus. Le portage de son implémentation, encore une fois, revient en grande partie sur des éléments déjà connus. Un nouveau point à noter est que, dans C#, nous pouvons comparer une chaîne à String.Empty, en C++/WinRT, nous appelons plutôt la fonction winrt ::hstring ::empty . Une autre est que nullptr est l’équivalent standard en C++ du null de C#.
Vous pouvez effectuer le reste du port avec des techniques que nous avons déjà abordées. Voici une liste des types d’opérations que vous devez effectuer avant la compilation de la version portée de cette méthode.
- Pour
MainPage.xamlajouter une bordure nommée StatusBorder. - Pour
MainPage.xamlajouter un TextBlock nommé StatusBlock. - Pour
MainPage.xamlajouter un StackPanel nommé StatusPanel. - À
pch.h, ajoutez#include "winrt/Microsoft.UI.Xaml.Media.h". - À
pch.h, ajoutez#include "winrt/Microsoft.UI.Xaml.Automation.Peers.h". - Pour
MainPage.cppajouterusing namespace winrt::Microsoft::UI::Xaml::Media;. - Pour
MainPage.cppajouterusing namespace winrt::Microsoft::UI::Xaml::Automation::Peers;.
Copier le code XAML et les styles nécessaires pour terminer le portage de MainPage
Pour XAML, le cas idéal est que vous pouvez utiliser le même balisage XAML sur un projet C# et C++/WinRT. Et l’exemple Clipboard fait partie de ces cas.
Dans le fichier Styles.xaml de l’exemple Clipboard, se trouve un ResourceDictionary XAML contenant des styles, appliqués aux boutons, menus et autres éléments d’interface utilisateur dans toute l’interface utilisateur de l’application. La Styles.xaml page est fusionnée en App.xaml. Il existe ensuite le point de départ standard MainPage.xaml de l’interface utilisateur, que nous avons déjà vu brièvement. Nous pouvons maintenant réutiliser ces trois .xaml fichiers, inchangés, dans la version C++/WinRT du projet.
Comme pour les fichiers de ressources, vous pouvez choisir de référencer les mêmes fichiers XAML partagés à partir de plusieurs versions de votre application. Dans cette procédure pas à pas, nous allons simplement copier des fichiers dans le projet C++/WinRT et les ajouter de cette façon.
Accédez au \Clipboard_sample\SharedContent\xaml dossier, sélectionnez et copiez App.xaml et MainPage.xamlcollez ces deux fichiers dans le \Clipboard\Clipboard dossier de votre projet C++/WinRT, en choisissant de remplacer les fichiers lorsque vous y êtes invité.
Dans le projet C++/WinRT dans Visual Studio, cliquez sur Afficher tous les fichiers pour l’activer. Ajoutez maintenant un nouveau dossier, immédiatement sous le nœud du projet et nommez-le Styles. Dans l’Explorateur de fichiers, accédez au \Clipboard_sample\SharedContent\xaml dossier, sélectionnez et copiez-le Styles.xaml, puis collez-le dans le \Clipboard\Clipboard\Styles dossier que vous venez de créer. De retour dans Explorateur de solutions dans le projet C++/WinRT, cliquez avec le bouton droit sur le Styles dossier >Ajouter> unélément existant... et accédez à \Clipboard\Clipboard\Styles. Dans le sélecteur de fichiers, sélectionnez Styles et cliquez sur Ajouter.
Ajoutez un nouveau dossier au projet C++/WinRT, immédiatement sous le nœud du projet et nommé Styles. Accédez au \Clipboard_sample\SharedContent\xaml dossier, sélectionnez et copiez-le Styles.xaml, puis collez-le dans le \Clipboard\Clipboard\Styles dossier de votre projet C++/WinRT. Cliquez avec le bouton droit sur le Styles dossier (dans Explorateur de solutions dans le projet C++/WinRT) >Ajouter> unélément existant... et accédez à \Clipboard\Clipboard\Styles. Dans le sélecteur de fichiers, sélectionnez Styles et cliquez sur Ajouter.
Cliquez à nouveau sur Afficher tous les fichiers pour le désactiver.
Nous avons maintenant terminé le portage de MainPage, et si vous avez suivi les étapes, votre projet C++/WinRT sera maintenant généré et exécuté.
Consolider vos .idl fichiers
Outre le point de départ standard MainPage.xaml de l’interface utilisateur, l’exemple Presse-papiers comporte cinq autres pages XAML spécifiques au scénario, ainsi que leurs fichiers code-behind correspondants. Nous allons réutiliser le balisage XAML réel de toutes ces pages, inchangé, dans la version C++/WinRT du projet. Et nous allons examiner comment porter le code-behind dans les sections principales suivantes. Mais avant cela, parlons d’IDL.
Il existe une valeur dans la consolidation de l’IDL pour vos classes runtime dans un seul fichier IDL. Pour en savoir plus sur cette valeur, consultez Factorisation des classes d’exécution dans des fichiers MIDL (.idl). Nous allons ensuite consolider le contenu de CopyFiles.idl, , CopyImage.idlCopyText.idl, et HistoryAndRoaming.idlOtherScenarios.idl en déplaçant cet IDL dans un seul fichier nommé Project.idl (puis en supprimant les fichiers d'origine).
Nous allons également supprimer la propriété factice générée automatiquement (Int32 MyProperty;et son implémentation) de chacun de ces cinq types de pages XAML.
Tout d’abord, ajoutez un nouvel élément Midl File (.idl) au projet C++/WinRT. Nommez-le Project.idl. Remplacez tout le contenu de Project.idl par le code suivant.
// 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();
}
}
Comme vous pouvez le voir, ce n’est qu’une copie du contenu de chacun des fichiers .idl, le tout dans un seul espace de noms, et avec MyProperty supprimé de chaque classe d’exécution.
Dans Explorateur de solutions dans Visual Studio, sélectionnez plusieurs fichiers IDL d’origine (CopyFiles.idl, , CopyImage.idl, CopyText.idlHistoryAndRoaming.idlet ) et OtherScenarios.idlmodifiez-les> (choisissezSupprimer dans la boîte de dialogue).
Enfin — et pour achever la suppression de MyProperty —, dans les fichiers .h et .cpp pour chacun de ces mêmes cinq types de pages XAML, supprimez les déclarations et les définitions des fonctions d’accesseur int32_t MyProperty() et de mutateur void MyProperty(int32_t).
En outre, il est toujours judicieux d’avoir le nom de vos fichiers XAML correspondant au nom de la classe qu’ils représentent. Par exemple, si vous avez x:Class="MyNamespace.MyPage" dans un fichier de balisage XAML, ce fichier doit être nommé MyPage.xaml. Bien qu’il ne s’agisse pas d’une exigence technique, il n’est pas nécessaire de jugler différents noms pour le même artefact afin que votre projet soit plus compréhensible et plus facile à gérer, et plus facile à utiliser.
CopyFiles
Dans le projet C#, le type de page XAML CopyFiles est implémenté dans les fichiers de code source CopyFiles.xaml et CopyFiles.xaml.cs. Examinons chacun des membres de CopyFiles à son tour.
rootPage
Il s’agit d’un champ privé.
// CopyFiles.xaml.cs
...
public sealed partial class CopyFiles : Page
{
MainPage rootPage = MainPage.Current;
...
}
...
Dans C++/WinRT, nous pouvons le définir et l’initialiser comme suit.
// CopyFiles.h
...
struct CopyFiles : CopyFilesT<CopyFiles>
{
...
private:
SDKTemplate::MainPage rootPage{ MainPage::Current() };
};
...
Là encore (comme avec MainPage ::current), CopyFiles ::rootPage est déclaré comme étant de type SDKTemplate ::MainPage, qui est le type projeté, et non le type d’implémentation.
CopyFiles (le constructeur)
Dans le projet C++/WinRT, le type CopyFiles a déjà un constructeur contenant le code souhaité (il appelle simplement InitializeComponent).
CopyButton_Click
La méthode CopyButton_Click C# est un gestionnaire d’événements et, à partir du async mot clé dans sa signature, nous pouvons indiquer que la méthode effectue un travail asynchrone. Dans C++/WinRT, nous implémentons une méthode asynchrone en tant que coroutine. Pour une introduction à la concurrence en C++/WinRT, ainsi qu’une description de ce qu’est une coroutine , consultez Les opérations simultanées et asynchrones avec C++/WinRT.
Il est courant de vouloir planifier d’autres opérations après l’achèvement d’une coroutine et, dans ce cas, la coroutine renverrait un type d’objet asynchrone pouvant être attendu et indiquant éventuellement sa progression. Toutefois, ces considérations ne s’appliquent généralement pas à un gestionnaire d’événements. Par conséquent, lorsque vous disposez d’un gestionnaire d’événements qui effectue des opérations asynchrones, vous pouvez l’implémenter en tant que coroutine qui retourne winrt ::fire_and_forget. Pour plus d’informations, consultez Fire and forget.
Bien que le principe d’une coroutine fire-and-forget soit que vous ne vous préoccupiez pas du moment où elle se termine, le travail continue malgré tout en arrière-plan (ou reste suspendu, en attente d’être repris). Vous pouvez voir à partir de l’implémentation C# qui CopyButton_Click dépend du this pointeur (il accède au membre rootPagede données de l’instance). Nous devons donc nous assurer que le this pointeur (un pointeur vers un objet CopyFiles) survive à la coroutine CopyButton_Click. Dans un cas tel que cet exemple d’application, où l’utilisateur navigue entre les pages d’interface utilisateur, nous ne pouvons pas contrôler directement la durée de vie de ces pages. Si la page CopyFiles est détruite (en quittant cette page) alors que CopyButton_Click est toujours en cours d’exécution dans un thread d’arrière-plan, il ne sera pas sûr d’accéder à rootPage. Pour que la coroutine soit correcte, elle doit obtenir une référence forte vers le pointeur this et conserver cette référence pendant toute la durée de vie de la coroutine. Pour plus d’informations, consultez Références fortes et faibles en C++/WinRT.
Si vous examinez la version C++/WinRT de l’exemple, dans CopyFiles::CopyButton_Click, vous verrez que cela se fait avec une simple déclaration sur la pile.
fire_and_forget CopyFiles::CopyButton_Click(IInspectable const&, RoutedEventArgs const&)
{
auto lifetime{ get_strong() };
...
}
Examinons les autres aspects du code porté qui sont remarquables.
Dans le code, nous instancions un objet FileOpenPicker et deux lignes plus tard, nous accédons à la propriété FileTypeFilter de cet objet. Le type de retour de cette propriété implémente un IVector de chaînes. Et sur cet IVector, nous appelons la méthode IVector<T>.ReplaceAll(T[]). L’aspect intéressant est la valeur que nous transmettons à cette méthode, où un tableau est attendu. Voici la ligne de code.
filePicker.FileTypeFilter().ReplaceAll({ L"*" });
La valeur que nous transmettons ({ L"*" }) est une liste d’initialiseurs C++ standard. Il contient un seul objet, dans ce cas, mais une liste d’initialiseurs peut contenir n’importe quel nombre d’objets séparés par des virgules. Les éléments de C++/WinRT qui vous permettent de transmettre une liste d’initialiseurs à une méthode telle que celle-ci sont expliquées dans les listes d’initialiseurs Standard.
Nous portons le mot clé C# await vers co_await C++/WinRT. Voici l’exemple du code.
auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };
Ensuite, considérez cette ligne de code C#.
dataPackage.SetStorageItems(storageItems);
C# est en mesure de convertir implicitement le fichier StorageFile< IReadOnlyList> représenté par storageItems en IEnumerable<IStorageItem> attendu par DataPackage.SetStorageItems. Mais dans C++/WinRT, nous devons convertir explicitement IVectorView<StorageFile> en IIterable<IStorageItem>. Nous avons donc un autre exemple de la fonction as à l’œuvre.
dataPackage.SetStorageItems(storageItems.as<IVectorView<IStorageItem>>());
Où nous utilisons le null mot clé en C# (par exemple, Clipboard.SetContentWithOptions(dataPackage, null)), nous utilisons nullptr en C++/WinRT (par exemple, Clipboard::SetContentWithOptions(dataPackage, nullptr)).
PasteButton_Click
Il s’agit d’un autre gestionnaire d’événements sous la forme d’un coroutine fire-and-forget. Examinons les aspects du code porté qui sont remarquables.
Dans la version C# de l’exemple, nous interceptons les exceptions avec catch (Exception ex). Dans le code C++/WinRT porté, vous verrez l’expression catch (winrt::hresult_error const& ex). Pour plus d’informations sur winrt ::hresult_error et sur son utilisation, consultez Gestion des erreurs avec C++/WinRT.
Exemple de test indiquant si un objet C# est null ou non if (storageItems != null). Dans C++/WinRT, nous pouvons nous appuyer sur un opérateur de conversion vers bool, qui effectue en interne le test par rapport à nullptr.
Voici une version légèrement simplifiée d’un fragment de code à partir de la version C++/WinRT portée de l’exemple.
std::wostringstream output;
output << std::wstring_view(ApplicationData::Current().LocalFolder().Path());
La construction d’un std ::wstring_view à partir d’un winrt ::hstring comme cela illustre une alternative à l’appel de la fonction hstring ::c_str (pour transformer le winrt ::hstring en chaîne de style C). Cette alternative fonctionne grâce à l’opérateur de conversion de hstringen std ::wstring_view.
Considérez ce fragment de C#.
var file = storageItem as StorageFile;
if (file != null)
...
Pour porter le mot clé C# as en C++/WinRT, jusqu’à présent, nous avons vu la fonction as employée à deux reprises. Cette fonction lève une exception si la conversion de type échoue. Mais si nous voulons que la conversion retourne nullptr si elle échoue (afin que nous puissions gérer cette condition dans le code), nous utilisons plutôt la fonction try_as.
auto file{ storageItem.try_as<StorageFile>() };
if (file)
...
Copiez le code XAML nécessaire pour terminer le portage de CopyFiles
Vous pouvez maintenant sélectionner l’intégralité du contenu du CopyFiles.xaml fichier dans le shared dossier du téléchargement de l’exemple de code source d’origine, puis le coller dans le CopyFiles.xaml fichier dans le projet C++/WinRT (en remplaçant le contenu existant de ce fichier dans le projet C++/WinRT).
Enfin, modifiez CopyFiles.h et .cpp, puis supprimez la fonction fictive ClickHandler, car nous venons d’écraser le balisage XAML correspondant.
Nous avons maintenant terminé le portage de CopyFiles, et si vous avez suivi les étapes, votre projet C++/WinRT sera maintenant généré et exécuté, et le scénario CopyFiles sera fonctionnel.
Copier l'image
Pour porter le type de page XAML CopyImage , vous suivez le même processus que pour CopyFiles. Lors du portage de CopyImage, vous rencontrerez l’instruction C# using statement, ce qui garantit que les objets qui implémentent l’interface IDisposable sont correctement libérés.
if (imageReceived != null)
{
using (var imageStream = await imageReceived.OpenReadAsync())
{
... // Pass imageStream to other APIs, and do other work.
}
}
L’interface équivalente en C++/WinRT est IClosable, avec sa méthode Close unique. Voici l’équivalent C++/WinRT du code C# ci-dessus.
if (imageReceived)
{
auto imageStream{ co_await imageReceived.OpenReadAsync() };
... // Pass imageStream to other APIs, and do other work.
imageStream.Close();
}
Les objets C++/WinRT implémentent IClosable principalement pour les langages qui n’ont pas de finalisation déterministe. C++/WinRT a une finalisation déterministe, et nous n’avons donc pas souvent besoin d’appeler IClosable ::Close lorsque nous écrivons C++/WinRT. Mais il y a des moments où il est bon de l’appeler, et c’est l’un de ces moments. Ici, l’identificateur imageStream est un wrapper compté par référence autour d’un objet Windows Runtime sous-jacent (dans ce cas, un objet qui implémente IRandomAccessStreamWithContentType). Bien que nous puissions déterminer que le finaliseur d’imageStream (son destructeur) s’exécutera à la fin de l’étendue englobante (les crochets curly), nous ne pouvons pas être certain que ce finaliseur appelle Close. Cela est dû au fait que nous avons passé imageStream à d'autres API, et qu'ils contribuent peut-être encore au nombre de références de l'objet Windows Runtime sous-jacent. Il s’agit donc d’un cas où il est judicieux d’appeler Close explicitement. Pour plus d’informations, consultez Dois-je appeler IClosable::Close sur les classes d’exécution que j’utilise ?.
Ensuite, considérez l’expression (uint)(imageDecoder.OrientedPixelWidth * 0.5)C#, que vous trouverez dans le gestionnaire d’événements OnDeferredImageRequestedHandler . Cette expression multiplie un uint par un double, entraînant un double. Il le convertit ensuite en un uint. En C++/WinRT, nous pourrions utiliser un cast de style C similaire ((uint32_t)(imageDecoder.OrientedPixelWidth() * 0.5)), mais il est préférable de le rendre clair exactement quel type de cast nous avons l’intention, et dans ce cas, nous le ferions avec static_cast<uint32_t>(imageDecoder.OrientedPixelWidth() * 0.5).
La version C# de CopyImage.OnDeferredImageRequestedHandler a une finally clause, mais pas une catch clause. Nous sommes allés un peu plus loin dans la version C++/WinRT, et avons implémenté une catch clause afin de pouvoir signaler si le rendu retardé a réussi.
Le portage du reste de cette page XAML ne donne rien de nouveau à discuter. N’oubliez pas de supprimer la fonction ClickHandler factice. Et, comme avec CopyFiles, la dernière étape du port consiste à sélectionner l’intégralité du contenu et CopyImage.xamlà le coller dans le même fichier dans le projet C++/WinRT.
CopyText
Vous pouvez porter CopyText.xaml et CopyText.xaml.cs en utilisant des techniques que nous avons déjà abordées.
HistoryAndRoaming
Il existe certains points d’intérêt qui surviennent lors du portage du type de page XAML HistoryAndRoaming .
Tout d’abord, examinez le code source C# et suivez le flux de contrôle de OnNavigatedTo via le gestionnaire d’événements OnHistoryEnabledChanged , et enfin à la fonction asynchrone CheckHistoryAndRoaming (qui n’est pas attendu, donc il est essentiellement déclenché et oublié). Étant donné que CheckHistoryAndRoaming est asynchrone, nous devons être prudents en C++/WinRT sur la durée de vie du this pointeur. Vous pouvez voir le résultat si vous examinez l’implémentation dans le fichier de HistoryAndRoaming.cpp code source. Tout d’abord, lorsque nous associons des délégués aux événements Clipboard::HistoryEnabledChanged et Clipboard::RoamingEnabledChanged, nous prenons uniquement une référence faible à l’objet de la page HistoryAndRoaming. Nous le faisons en créant le délégué avec une dépendance sur la valeur retournée par winrt ::get_weak, au lieu d’une dépendance sur le this pointeur. Cela signifie que le délégué lui-même, qui finit par appeler du code asynchrone, ne maintient pas la page HistoryAndRoaming en vie si nous la quittons.
Et deuxièmement, lorsque nous atteignons enfin notre coroutine de type fire-and-forget CheckHistoryAndRoaming, la première chose que nous faisons consiste à prendre une référence forte sur this afin de garantir que la page HistoryAndRoaming reste active au moins jusqu’à ce que la coroutine se termine enfin. Pour plus d’informations sur les deux aspects décrits, consultez Les références fortes et faibles en C++/WinRT.
Nous trouvons un autre point d’intérêt lors du portage checkHistoryAndRoaming. Il contient du code pour mettre à jour l’interface utilisateur ; Nous devons donc être sûrs que nous faisons cela sur le thread d’interface utilisateur principal. Le thread qui appelle initialement un gestionnaire d’événements est le thread d’interface utilisateur principal. Toutefois, en règle générale, une méthode asynchrone peut exécuter et/ou reprendre sur n’importe quel thread arbitraire. En C#, la solution consiste à distribuer le travail au thread d’interface utilisateur. Dans C++/WinRT, nous pouvons utiliser la fonction winrt::resume_foreground avec la propriété DispatcherQueue du pointeur this pour suspendre la coroutine et reprendre immédiatement sur le thread principal de l’interface utilisateur.
L’expression pertinente est co_await winrt::resume_foreground(DispatcherQueue());. La version plus courte est obtenue grâce à un opérateur de conversion fourni par C++/WinRT.
Le portage du reste de cette page XAML ne donne rien de nouveau à discuter. N’oubliez pas de supprimer la fonction ClickHandler factice et de copier le balisage XAML.
OtherScenarios
Vous pouvez porter OtherScenarios.xaml et OtherScenarios.xaml.cs en utilisant des techniques que nous avons déjà abordées.
Conclusion
J’espère que ce guide pas à pas vous a fourni les informations et les techniques de portage nécessaires pour que vous puissiez désormais porter vos propres applications C# vers C++/WinRT. À titre de rappel, vous pouvez continuer à vous reporter aux versions avant (C#) et après (C++/WinRT) du code source dans l’exemple Clipboard, et les comparer côte à côte pour voir la correspondance.
Rubriques connexes
Windows developer