Het Klembord-voorbeeld overzetten naar C++/WinRT vanuit C#: een casestudy

In dit onderwerp wordt een casestudy gepresenteerd over het overzetten van een van de Universal Windows Platform (UWP) app-voorbeelden van C# naar C++/WinRT. U kunt oefening in en ervaring met porteren opdoen door de stapsgewijze handleiding te volgen en onderweg zelf het voorbeeld te porteren.

Note

De broncode die wordt overgezet, is een UWP C#-app. De doelC++/WinRT-code in dit artikel is geschreven voor WinUI 3 (Windows App SDK). Waar de UWP-bron GEBRUIKMAAKT van API's die verschillen in WinUI 3 (bijvoorbeeld Windows.UI.Core.CoreDispatcher vs. Microsoft.UI.Dispatching.DispatcherQueue), wordt in dit artikel expliciet het juiste WinUI 3-equivalent weergegeven in de C++/WinRT-uitvoer. U kunt de codepatronen uit de kolom C++/WinRT rechtstreeks in uw WinUI 3-app gebruiken.

Zie het begeleidende onderwerp Overstappen naar C++/WinRT van C# voor een uitgebreide catalogus met technische details die betrokken zijn bij het overzetten naar C++/WinRT van C#.

Een kort voorwoord over C#- en C++-broncodebestanden

In een C#-project zijn uw broncodebestanden voornamelijk .cs bestanden. Wanneer u overstapt naar C++, zult u merken dat er meer soorten broncodebestanden zijn om wennen. De reden hiervoor is het verschil tussen compilers, de manier waarop C++-broncode opnieuw wordt gebruikt en de noties van het declareren en definiëren van een type en de bijbehorende functies (de methoden).

Een functiedeclaratie beschrijft alleen de handtekening van de functie (het retourtype, de naam en de parametertypen en namen). Een functiedefinitie bevat de hoofdtekst van de functie (de implementatie).

Het is iets anders als het gaat om typen. U definieert een type door de naam op te geven en door (minimaal) alle lidfuncties (en andere leden) te declareren . Dat klopt, u kunt een type definiëren , zelfs als u de lidfuncties niet definieert.

  • Algemene C++ broncodebestanden zijn .h (dot aitch) en .cpp bestanden. Een .h bestand is een headerbestand en definieert een of meer typen. Hoewel u lidfuncties in een koptekst kunt definiëren, is dat meestal waar een .cpp bestand voor is. Voor een hypothetisch C++-type MyClass definieert u MyClass in MyClass.hen definieert u de lidfuncties in MyClass.cpp. Voor andere ontwikkelaars om uw klassen opnieuw te gebruiken, deelt u alleen de .h bestanden en objectcode. U zou uw .cpp bestanden geheim houden, omdat de implementatie uw intellectuele eigendom vormt.
  • Vooraf gecompileerde header (pch.h). Normaal gesproken is er een set headerbestanden die u in uw toepassing opneemt en u deze bestanden niet vaak wijzigt. Dus in plaats van de inhoud van die set headers te verwerken telkens wanneer u compileert, kunt u deze headers aggregeren in één bestand, die eenmaal compileren en vervolgens de uitvoer van die precompilatiestap gebruiken telkens wanneer u bouwt. U doet dit via een vooraf gecompileerd headerbestand (meestal genaamd pch.h).
  • .idl bestanden. Deze bestanden bevatten Interface Definition Language (IDL). U kunt IDL beschouwen als headerbestanden voor de Windows Runtime-typen. In de sectie-IDL voor het type MainPage bespreken we meer over IDL.

Het Klembord-voorbeeld downloaden en testen

Ga naar de webpagina van het Klembord-voorbeeld en klik op ZIP downloaden. Pak het gedownloade bestand uit en bekijk de mapstructuur.

  • De C#-versie van de voorbeeldbroncode bevindt zich in de map met de naam cs.
  • De C++/WinRT-versie van de voorbeeldbroncode bevindt zich in de map met de naam cppwinrt.
  • Andere bestanden, die worden gebruikt door zowel de C#-versie als de C++/WinRT-versie, zijn te vinden in de shared en SharedContent mappen.

In het overzicht in dit onderwerp ziet u hoe u de C++/WinRT-versie van het Klembord-voorbeeld opnieuw kunt maken door deze over te zetten vanuit de C#-broncode. Op die manier kunt u zien hoe u uw eigen C#-projecten kunt overzetten naar C++/WinRT.

Als u een idee wilt krijgen van wat het voorbeeld doet, opent u de C#-oplossing (\Clipboard_sample\cs\Clipboard.sln), wijzigt u de configuratie naar wens (mogelijk in x64), bouwt en voert u deze uit. De eigen gebruikersinterface (UI) van het voorbeeld begeleidt u stapsgewijs door de verschillende functies.

Hint

De hoofdmap van het voorbeeld dat u hebt gedownload, heeft mogelijk de naam Clipboard in plaats Clipboard_samplevan . Maar we blijven naar die map Clipboard_sample verwijzen om deze te onderscheiden van de C++/WinRT-versie die u in een latere stap gaat maken.

Een lege app maken met de naam Klembord

Note

Zie Visual Studio support for C++/WinRT voor informatie over het installeren en gebruiken van de Visual Studio-extensie (VSIX) en het NuGet-pakket voor C++/WinRT, die samen projectsjablonen en ondersteuning voor builds bieden.

Begin het overdrachtsproces door een nieuw C++/WinRT-project te maken in Microsoft Visual Studio. Maak een nieuw project met de Blank App, Packaged (WinUI 3 in Desktop)-projectsjabloon voor C++. Stel de naam in op Clipboard en zorg ervoor (zodat je mapstructuur overeenkomt met de stapsgewijze handleiding) dat Oplossing en project in dezelfde map plaatsen niet is aangevinkt.

Om een uitgangspunt te hebben, moet u ervoor zorgen dat dit nieuwe, lege project compileert en wordt uitgevoerd.

Package.appxmanifest en assetbestanden

Als de C#- en C++/WinRT-versies van het voorbeeld niet naast elkaar op dezelfde computer hoeven te worden geïnstalleerd, kunnen de bronbestanden van het app-pakketmanifest van de twee projecten identiek zijn (Package.appxmanifest). In dat geval kunt u gewoon kopiëren Package.appxmanifest van het C#-project naar het C++/WinRT-project en bent u klaar.

Voor de twee versies van het voorbeeld om naast elkaar te bestaan, hebben ze verschillende id's nodig. In dat geval opent u in het C++/WinRT-project het Package.appxmanifest bestand in een XML-editor en noteert u deze drie waarden.

  • Noteer in het element /Package/Identity de waarde van het kenmerk Name . Dit is de pakketnaam. Voor een nieuw aangemaakt project krijgt het project aanvankelijk een unieke GUID als waarde.
  • Noteer in het element /Package/Applications/Application de waarde van het kenmerk Id . Dit is de applicatie-id.
  • Noteer in het element /Package/mp:PhoneIdentity de waarde van het kenmerk PhoneProductId . Voor een nieuw aangemaakt project wordt dit ook ingesteld op dezelfde GUID als de pakketnaam.

Kopieer Package.appxmanifest vervolgens vanuit het C#-project naar het C++/WinRT-project. Ten slotte kunt u de drie waarden herstellen die u hebt genoteerd. Of u kunt de gekopieerde waarden bewerken om ze uniek en/of geschikt te maken voor de toepassing en voor uw organisatie (zoals u gewoonlijk zou doen voor een nieuw project). In dit geval kunnen we bijvoorbeeld, in plaats van de waarde van de pakketnaam te herstellen, gewoon de gekopieerde waarde wijzigen van Microsoft.SDKSamples.Clipboard.CS naar Microsoft.SDKSamples.Clipboard.CppWinRT. En we kunnen de toepassings-id ingesteld laten op App. Zolang de pakketnaam of de toepassings-id verschillend zijn, hebben de twee toepassingen verschillende toepassingsgebruikersmodel-id's (AUMID's). En dat is nodig om twee apps naast elkaar op dezelfde computer te installeren.

Voor deze stapsgewijze handleiding is het zinvol om nog een paar wijzigingen door te voeren in Package.appxmanifest. Er zijn drie voorkomens van de tekenreeks Clipboard C# Sample. Wijzig dat in Clipboard C++/WinRT Sample.

In het C++/WinRT-project zijn het Package.appxmanifest bestand en het project nu niet gesynchroniseerd met betrekking tot de assetbestanden waarnaar ze verwijzen. U kunt dit oplossen door eerst de assets uit het C++/WinRT-project te verwijderen door alle bestanden in de Assets map (in Solution Explorer in Visual Studio) te selecteren en te verwijderen (kies Verwijderen in het dialoogvenster).

Het C#-project verwijst naar assetbestanden uit een gedeelde map. U kunt hetzelfde doen in het C++/WinRT-project of u kunt de bestanden kopiëren zoals we in dit scenario doen.

Navigeer naar de map \Clipboard_sample\SharedContent\media. Selecteer de zeven bestanden die het C#-project bevat (microsoft-sdk.png, smalltile-sdk.png, splash-sdk.png, squaretile-sdk.png, storelogo-sdk.pngen tile-sdk.pngwindows-sdk.png), kopieer ze en plak ze in de \Clipboard\Clipboard\Assets map in het nieuwe project.

Klik met de rechtermuisknop op de Assets map (in Solution Explorer in het C++/WinRT-project) >Bestaand item>... en navigeer naar \Clipboard\Clipboard\Assets. Selecteer in de bestandskiezer de zeven bestanden en klik op Toevoegen.

Package.appxmanifest is nu weer gesynchroniseerd met de assetbestanden van het project.

MainPage, inclusief de functionaliteit waarmee het voorbeeld wordt geconfigureerd

Het Klembord-voorbeeld, zoals alle Universal Windows Platform (UWP) app-voorbeelden, bestaat uit een verzameling scenario's die de gebruiker één voor één kan doorlopen. De verzameling scenario's in een bepaald voorbeeld wordt geconfigureerd in de broncode van het voorbeeld. Elk scenario in de verzameling is een gegevensitem waarin een titel wordt opgeslagen, evenals het type klasse in het project waarmee het scenario wordt geïmplementeerd.

In de C#-versie van het voorbeeld ziet u twee klassen als u in het SampleConfiguration.cs broncodebestand kijkt. De meeste configuratielogica bevindt zich in de klasse MainPage , een gedeeltelijke klasse (het vormt een volledige klasse in combinatie met de markeringen in MainPage.xaml en de imperatieve code in MainPage.xaml.cs). De andere klasse in dit broncodebestand is Scenario, met de eigenschappen Title en ClassType .

In de volgende paar subsecties bekijken we hoe we MainPage en Scenario kunnen overzetten.

IDL voor het type MainPage

Laten we beginnen met deze sectie door kort te praten over Interface Definition Language (IDL) en hoe het ons helpt bij het programmeren met C++/WinRT. IDL is een soort broncode die het aanroepbare oppervlak van een Windows Runtime type beschrijft. Het aanroepbare (of openbare) oppervlak van een type wordt uitgeprojecteerd naar de wereld, zodat het type kan worden gebruikt. Dat verwachte gedeelte van het type contrasteert met de werkelijke interne implementatie van het type, wat natuurlijk niet aanroepbaar is en niet openbaar. Het is alleen het verwachte gedeelte dat we in IDL definiëren.

Als u idL-broncode (in een .idl bestand) hebt gemaakt, kunt u de IDL vervolgens compileren in machineleesbare metagegevensbestanden (ook wel bekend als Windows Metagegevens). Deze metagegevensbestanden hebben de extensie .winmden hier volgen enkele van hun gebruiksmogelijkheden.

  • Een .winmd kan de Windows Runtime typen in een onderdeel beschrijven. Wanneer u vanuit een toepassingsproject naar een Windows Runtime Component (WRC) verwijst, leest het toepassingsproject de Windows Metagegevens die behoren tot de WRC (die metagegevens kunnen zich in een afzonderlijk bestand bevinden, of worden deze verpakt in hetzelfde bestand als de WRC zelf), zodat u de typen WRC vanuit de toepassing kunt gebruiken.
  • A .winmd kan de Windows Runtime typen in één deel van uw toepassing beschrijven, zodat ze kunnen worden gebruikt door een ander deel van dezelfde toepassing. Bijvoorbeeld een Windows Runtime-type dat wordt gebruikt door een XAML-pagina in dezelfde app.
  • Om het voor u gemakkelijker te maken Windows Runtime typen (ingebouwd of van derden) te gebruiken, gebruikt .winmd het C++/WinRT-buildsysteem bestanden om wrapper-typen te genereren die de verwachte delen van die Windows Runtime typen vertegenwoordigen.
  • Om het voor u gemakkelijker te maken om uw eigen Windows Runtime typen te implementeren, verandert het C++/WinRT-buildsysteem uw IDL in een .winmd bestand en gebruikt dit vervolgens om wrappers voor uw projectie te genereren, evenals stubs waarop uw implementatie moet worden gebaseerd (verderop in dit onderwerp bespreken we meer over deze stubs).

De specifieke versie van IDL die we gebruiken met C++/WinRT is Microsoft Interface Definition Language 3.0. In de rest van deze sectie van het onderwerp gaan we het type C# MainPage in detail bekijken. We bepalen welke delen ervan moeten worden opgenomen in de projectie van het type C++/WinRT MainPage (dat wil wel, in de aanroepbare of openbare, oppervlakte) en die slechts deel kunnen uitmaken van de implementatie. Dat onderscheid is belangrijk omdat wanneer we onze IDL gaan schrijven (wat we na deze sectie doen), we alleen de aanroepbare onderdelen hierin definiëren.

De C#-broncodebestanden die samen het type MainPage implementeren, zijn: MainPage.xaml (die we binnenkort overzetten door deze te kopiëren), MainPage.xaml.csen SampleConfiguration.cs.

In de C++/WinRT-versie nemen we op dezelfde manier rekening met het type MainPage in broncodebestanden. We nemen de logica mee MainPage.xaml.cs en vertalen deze voor het grootste deel naar MainPage.h en MainPage.cpp. En voor de logica in SampleConfiguration.cs, vertalen we dat naar SampleConfiguration.h en SampleConfiguration.cpp.

De klassen in een C# Universal Windows Platform (UWP) toepassing zijn natuurlijk Windows Runtime typen. Maar wanneer u een type in een C++/WinRT-toepassing maakt, kunt u kiezen of dat type een Windows Runtime type is, of een reguliere C++-klasse/-struct/opsomming.

Elke XAML-pagina in ons project moet een Windows Runtime type zijn, dus MainPage moet een Windows Runtime type zijn. In het C++/WinRT-project is MainPage al een Windows Runtime type, dus we hoeven dat aspect ervan niet te wijzigen. Het is met name een runtimeklasse.

  • Zie het onderwerp Author-API's met C++/WinRT voor meer informatie over het al dan niet ontwerpen van een runtimeklasse voor een bepaald type.
  • In C++/WinRT bestaat de interne implementatie van een runtimeklasse en de geprojecteerde (openbare) onderdelen ervan in de vorm van twee verschillende klassen. Deze worden het implementatietype en het verwachte type genoemd. Meer informatie hierover vindt u in het onderwerp dat wordt genoemd in het vorige opsommingsteken, en ook in Api's gebruiken met C++/WinRT.
  • Voor meer informatie over de verbinding tussen runtimeklassen en IDL (.idl bestanden) kunt u het onderwerp XAML-besturingselementen lezen en volgen; verbinden met een C++/WinRT-eigenschap. In dit onderwerp wordt het proces voor het ontwerpen van een nieuwe runtimeklasse beschreven, waarvan de eerste stap bestaat uit het toevoegen van een nieuw Midl File-item (.idl) aan het project.

Voor MainPage hebben we eigenlijk al het benodigde MainPage.idl bestand in het C++/WinRT-project. Dat komt doordat de projectsjabloon deze voor ons heeft gemaakt. Maar verderop in deze uitleg voegen we nog meer .idl bestanden toe aan het project.

Binnenkort ziet u een lijst met precies de IDL die we moeten toevoegen aan het bestaande MainPage.idl bestand. Daarvoor moeten we eerst nadenken over wat wel en niet in de IDL moet worden opgenomen.

Om te bepalen welke leden van MainPage we in MainPage.idl moeten declareren (zodat ze deel gaan uitmaken van de MainPage-runtimeklasse), en welke gewoon lid kunnen zijn van het implementatietype MainPage, laten we een lijst maken van de leden van de C#-klasse MainPage. We vinden die leden door binnen MainPage.xaml.cs en in SampleConfiguration.cste kijken.

We vinden in totaal twaalf protected velden private en methoden. En we vinden de volgende public leden.

  • De standaardconstructor MainPage().
  • De statische velden Current en FEATURE_NAME.
  • De eigenschappen IsClipboardContentChangedEnabled en Scenarios.
  • De methoden BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotifications en NotifyUser.

Het zijn de public leden die kandidaten zijn om in MainPage.idlte declareren. Laten we dus eens kijken of ze deel moeten uitmaken van de MainPage-runtimeklasse of dat ze alleen deel hoeven te uitmaken van de implementatie.

  • De standaardconstructor MainPage(). Voor een XAML-pagina is het normaal om een standaardconstructor in de IDL te declareren. Op die manier kan het XAML UI-framework het type activeren.
  • Het statische veld Current wordt gebruikt vanuit de afzonderlijke XAML-pagina's van het afzonderlijke scenario voor toegang tot het exemplaar van MainPage van de toepassing. Omdat Current niet wordt gebruikt om samen te werken met het XAML-framework (noch wordt deze gebruikt voor compilatie-eenheden), kunnen we het alleen als lid van het implementatietype reserveren. In dergelijke gevallen kunt u ervoor kiezen om dat te doen met uw eigen projecten. Maar aangezien het veld een instantie van het geprojecteerde type is, lijkt het logisch om het in de IDL te declareren. Dat is wat we hier doen (en dat maakt de code ook iets schoner).
  • Het is een vergelijkbaar geval voor het statische FEATURE_NAME veld, dat wordt geopend binnen het type MainPage . Nogmaals, als u ervoor kiest om deze in de IDL te declareren, wordt onze code iets schoner.
  • De eigenschap IsClipboardContentChangedEnabled wordt alleen gebruikt in de klasse OtherScenarios . Dus tijdens het overzetten vereenvoudigen we een en ander een beetje en maken we er een privéveld van in de runtimeklasse OtherScenarios. Dus dat komt niet in de IDL.
  • De eigenschap Scenario's is een verzameling objecten van het type Scenario (een type dat we eerder hebben genoemd). In de volgende subsectie bespreken we Scenario, dus laten we de eigenschap Scenarios tot die tijd ook rusten.
  • De methoden BuildClipboardFormatsOutputString, DisplayToast en EnableClipboardContentChangedNotifications zijn hulpprogrammafuncties die meer te maken hebben met de algemene status van het voorbeeld dan over de hoofdpagina. Tijdens de poort herstructureren we deze drie methoden naar een nieuw hulpprogrammatype met de naam SampleState (dat geen Windows Runtime type hoeft te zijn). Daarom gaan deze drie methoden niet in de IDL.
  • De methode NotifyUser wordt aangeroepen vanuit de afzonderlijke XAML-pagina's van de scenario's op het exemplaar van MainPage dat wordt geretourneerd door het statische veld Current. Aangezien (zoals al vermeld) Current een exemplaar van het projected type is, moeten we NotifyUser declareren in de IDL. NotifyUser gebruikt een parameter van het type NotifyType. Dat bespreken we in de volgende subsectie.

Elk member waaraan u gegevens wilt binden, moet ook in IDL worden gedeclareerd (of u nu {x:Bind} of {Binding} gebruikt). Zie Gegevensbinding voor meer informatie.

We maken vooruitgang: we ontwikkelen een lijst met leden die moeten worden toegevoegd en die niet aan het MainPage.idl bestand moeten worden toegevoegd. Maar we moeten nog steeds de eigenschap Scenario's en het type NotifyType bespreken. Laten we dat nu doen.

IDL voor de typen Scenario en NotifyType

De klasse Scenario is gedefinieerd in SampleConfiguration.cs. We hebben een beslissing genomen over het overzetten van die klasse naar C++/WinRT. Standaard zouden we het waarschijnlijk een gewone C++ structmaken. Maar als scenario in binaire bestanden wordt gebruikt of als u wilt samenwerken met het XAML-framework, moet het in IDL worden gedeclareerd als een Windows Runtime type.

Als we de C#-broncode bestuderen, vinden we dat Scenario in deze context wordt gebruikt.

<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;

Er wordt een verzameling scenarioobjecten toegewezen aan de eigenschap ItemsBron van een keuzelijst (dit is een itembesturingselement). Aangezien Scenariowel moet samenwerken met XAML, moet het een Windows Runtime type zijn. Het moet dus worden gedefinieerd in IDL. Het definiëren van het type Scenario in IDL zorgt ervoor dat het C++/WinRT-buildsysteem een broncodedefinitie van Scenario voor je genereert in een headerbestand achter de schermen (de naam en locatie ervan zijn niet belangrijk voor deze walkthrough).

En u herinnert zich dat MainPage.Scenarios een verzameling scenarioobjecten is, die we zojuist hebben gezegd in IDL moeten zijn. Daarom moet MainPage.Scenarios zelf ook worden gedeclareerd in de IDL.

NotifyType is een enum gedefinieerd in C#'s MainPage.xaml.cs. Omdat we NotifyType doorgeven aan een methode die hoort bij de MainPage-runtimeklasse, moet NotifyType ook een Windows Runtime type zijn en moet deze worden gedefinieerd in MainPage.idl.

Nu gaan we de nieuwe typen toevoegen aan het MainPage.idl bestand en het nieuwe lid van Mainpage dat we hebben besloten in IDL te declareren. Tegelijkertijd verwijderen we uit de IDL de tijdelijke leden van Mainpage die de projectsjabloon van Visual Studio voor ons heeft aangemaakt.

Dus opent u in uw C++/WinRT-project MainPage.idl en bewerkt u het zodat het eruitziet als het onderstaande codevoorbeeld. Een van de bewerkingen is het wijzigen van de naamruimtenaam van Het Klembord in SDKTemplate. Als u wilt, kunt u eenvoudigweg de volledige inhoud van MainPage.idl vervangen door de volgende code. Een andere aanpassing is dat we de naam van Scenario::ClassType wijzigen in 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

Zie .idl voor meer informatie over de inhoud van een bestand in een C++/WinRT-project.

Met uw eigen overdrachtswerk wilt u mogelijk niet de naamruimtenaam wijzigen, zoals hierboven is gedaan. We doen dit alleen omdat de standaardnaamruimte van het C#-project dat we overzetten SDKTemplate is. terwijl de naam van het project en van de assembly Klembord is.

Maar terwijl we verdergaan met de port in deze walkthrough, zullen we in de broncode elke vermelding van de naamruimte Clipboard wijzigen in SDKTemplate. Ook is er in de projecteigenschappen van C++/WinRT een plek waar de naam van de naamruimte Clipboard wordt weergegeven, dus laten we van de gelegenheid gebruikmaken om die nu te wijzigen.

Stel in Visual Studio voor het C++/WinRT-project de projecteigenschap Common Properties>C++/WinRT>Root Namespace in op de waarde SDKTemplate.

Sla de IDL op en genereer stub-bestanden opnieuw

Het onderwerp XAML-besturingselementen; bindt aan een C++/WinRT-eigenschap introduceert het begrip stub-bestanden en toont u een overzicht van deze in actie. We hebben eerder in dit onderwerp ook stubs genoemd toen we zeiden dat het C++/WinRT-buildsysteem de inhoud van uw .idl bestanden verandert in Windows metagegevens, en vervolgens vanuit die metagegevens genereert een hulpprogramma met de naam cppwinrt.exe stubs waarop u uw implementatie kunt baseren.

Telkens wanneer u iets toevoegt, verwijdert of wijzigt in uw IDL en build, werkt het buildsysteem de stub-implementaties in die stubs-bestanden bij. Telkens wanneer u uw IDL en build wijzigt, raden we u aan deze stubs-bestanden weer te geven, eventuele gewijzigde handtekeningen te kopiëren en deze in uw project te plakken. We geven meer details en voorbeelden van hoe u dat in een ogenblik kunt doen. Maar het voordeel van dit doen is om u te allen tijde een foutloze manier te geven om te weten wat de vorm van uw implementatietype moet zijn en wat de handtekening van de methoden moet zijn.

Op dit punt in de stapsgewijze uitleg zijn we voorlopig klaar met het bewerken van het bestand MainPage.idl, dus kunt u het nu opslaan. Het project wordt momenteel niet voltooid, maar het uitvoeren van een build is nu handig omdat hiermee de stub-bestanden voor MainPage opnieuw worden gegenereerd. Bouw het project nu en negeer eventuele buildfouten.

Voor dit C++/WinRT-project worden de stub-bestanden gegenereerd in de \Clipboard\Clipboard\Generated Files\sources map. Je vindt ze daar nadat de gedeeltelijke build is voltooid (opnieuw, zoals verwacht, zal de build niet volledig slagen. Maar de stap waarin we geïnteresseerd zijn — het genereren van stubs — zal wel geslaagd zijn). De bestanden waarin we geïnteresseerd zijn, zijn MainPage.h en MainPage.cpp.

In deze twee stub-bestanden ziet u nieuwe stub-implementaties van de leden van MainPage die we bijvoorbeeld hebben toegevoegd aan de IDL (Current en FEATURE_NAME). U wilt deze stub-implementaties kopiëren naar de MainPage.h en MainPage.cpp bestanden die zich al in het project bevinden. Tegelijkertijd verwijderen we, net als bij de IDL, uit die bestaande bestanden de plaatsaanduidingsleden van MainPage die de projectsjabloon van Visual Studio ons heeft gegeven (de dummy-eigenschap met de naam MyProperty en de gebeurtenishandler met de naam ClickHandler).

Het enige lid van de huidige versie van MainPage die we willen behouden, is de constructor.

Nadat u de nieuwe leden hebt gekopieerd uit de stub-bestanden, hebt u de gewenste leden verwijderd en de naamruimte bijgewerkt, moeten de MainPage.h en MainPage.cpp bestanden in uw project eruitzien zoals de onderstaande codevermeldingen. U ziet dat er twee MainPage-typen zijn. Een in de implementatienaamruimte en een tweede in de factory_implementation naamruimte. De enige wijziging die we hebben aangebracht aan de factory_implementation, is het toevoegen van SDKTemplate aan de naamruimte.

// 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();
    }
}

Voor tekenreeksen gebruikt C# System.String. Zie de methode MainPage.NotifyUser voor een voorbeeld. In onze IDL declareren we een tekenreeks met Tekenreeks en wanneer het cppwinrt.exe hulpprogramma C++/WinRT-code voor ons genereert, wordt het type winrt::hstring gebruikt. Telkens wanneer we een tekenreeks in C#-code tegenkomen, wordt dit overgezet naar winrt::hstring. Zie De verwerking van tekenreeksen in C++/WinRT voor meer informatie.

Zie const& voor een uitleg van de parameters in de methodehandtekeningen.

Alle resterende naamruimtedeclaraties/verwijzingen bijwerken en bouwen

Voordat u het C++/WinRT-project bouwt, zoekt u eventuele declaraties van (en verwijzingen naar) de naamruimte van het Klembord en wijzigt u deze in SDKTemplate.

  • MainPage.xaml en App.xaml. De naamruimte wordt weergegeven in de waarden van de x:Class en xmlns:local kenmerken.
  • App.idl.
  • App.h.
  • App.cpp. Er zijn twee using namespace instructies (zoeken naar de subtekenreeks using namespace Clipboard) en twee kwalificaties van het type MainPage (zoeken naar Clipboard::MainPage). Die moeten veranderen.

Omdat we de eventhandler uit MainPage hebben verwijderd, gaat u ook naar MainPage.xaml en verwijdert u het element Button uit de markup.

Sla alle bestanden op. Schoon de oplossing (Build>Clean Solution) op en bouw deze vervolgens. Als u alle wijzigingen tot nu toe hebt gevolgd, precies zoals geschreven, wordt verwacht dat de build slaagt.

Implementeer de MainPage-leden die we in IDL hebben gedeclareerd

De constructor, Current, en FEATURE_NAME

Hier volgt de relevante code (van het C#-project) die we moeten overzetten.

<!-- 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";
...
}
...

Binnenkort zullen we MainPage.xaml in zijn geheel hergebruiken (door het te kopiëren). Voorlopig (hieronder) voegen we tijdelijk een TextBlock-element met de juiste naam toe aan het MainPage.xaml C++/WinRT-project.

FEATURE_NAME is een statisch veld van MainPage (een C# const -veld is in wezen statisch in het gedrag), gedefinieerd in SampleConfiguration.cs. Voor C++/WinRT maken we in plaats van een (statisch) veld de C++/WinRT-expressie van een (statische) alleen-lezen eigenschap. De C++/WinRT-manier om een property-getter uit te drukken is als een functie die de eigenschapswaarde retourneert en geen parameters heeft (een accessor). Het statische C#-veld FEATURE_NAME wordt dus de statische C++/WinRT-accessorfunctie FEATURE_NAME (in dit geval die de stringliteral retourneert).

Trouwens, we zouden hetzelfde doen als we een C#-eigenschap met het kenmerk Alleen-lezen overzetten. Voor een C#-beschrijfbare eigenschap is de C++/WinRT-manier om een eigenschapssetter uit te drukken als een void functie die de eigenschapswaarde als parameter (een mutator) gebruikt. In beide gevallen geldt dat als het C#-veld of de C#-eigenschap statisch is, de C++/WinRT-accessor en/of -mutator dat ook is.

Huidig is een statisch (niet constant) veld van MainPage. Opnieuw maken we er (de C++/WinRT-weergave van) een eigenschap met alleen-lezen-toegang van, en maken we die opnieuw statisch. Waar FEATURE_NAME constant blijft, is Current dat niet. In C++/WinRT hebben we dus een back-upveld nodig, waarna onze accessor dat retourneert. In het C++/WinRT-project declareren we dus in MainPage.h een privé statisch veld met de naam Current, waarin we huidigeMainPage.cpp definiëren/initialiseren (omdat het een statische opslagduur heeft) en we openen het via een openbare statische toegangsfunctie met de naam Current.

De constructor zelf voert een paar toewijzingen uit, die zich eenvoudig laten overzetten.

Voeg in het C++/WinRT-project een nieuw Visual C++>Code>C++-bestand (.cpp) toe met de naam SampleConfiguration.cpp.

Bewerk MainPage.xaml, MainPage.h, MainPage.cpp en SampleConfiguration.cpp zodat deze overeenkomen met de onderstaande vermeldingen.

<!-- 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";
}

Zorg er ook voor dat u de bestaande functieteksten verwijdert uit MainPage.cppMainPage::Current() en MainPage::FEATURE_NAME(), omdat we deze methoden nu elders definiëren.

Zoals u ziet, wordt MainPage::current gedeclareerd als type SDKTemplate::MainPage, het verwachte type. Het is niet van het type SDKTemplate::implementation::MainPage, het implementatietype. Het geprojecteerde type is het type dat is ontworpen om ofwel binnen het project te worden gebruikt voor XAML-interoperabiliteit, ofwel tussen binaire bestanden. Het implementatietype is wat u gebruikt om de faciliteiten te implementeren die u beschikbaar hebt gesteld voor uw projecttype. Omdat de declaratie van MainPage::current (in MainPage.h) wordt weergegeven in de implementatienaamruimte (winrt::SDKTemplate::implementation), zou een niet-gekwalificeerde MainPage naar het implementatietype hebben verwezen. We kwalificeren dus met SDKTemplate:: om duidelijk te maken dat MainPage::current een exemplaar is van het geprojecteerde type winrt::SDKTemplate::MainPage.

In de constructor zijn er enkele punten gerelateerd aan MainPage::current = *this; die een uitleg verdienen.

  • Wanneer u de this aanwijzer binnen een lid van het implementatietype gebruikt, is de this aanwijzer natuurlijk een aanwijzer naar het implementatietype.
  • Om de this aanwijzer om te zetten naar het overeenkomstige geprojecteerde type, moet u deze derefereren. Op voorwaarde dat u uw implementatietype genereert op basis van IDL (zoals we hier hebben), heeft het implementatietype een conversieoperator die wordt geconverteerd naar het verwachte type. Daarom werkt de opdracht hier.

Zie Instantiëren en retourneren van implementatietypen en interfaces voor meer informatie over deze details.

In de constructor staat ook SampleTitle().Text(FEATURE_NAME());. Het SampleTitle() onderdeel is een aanroep naar een eenvoudige accessorfunctie met de naam SampleTitle, die de TextBlock retourneert die we hebben toegevoegd aan de XAML. Wanneer u x:Name een XAML-element gebruikt, genereert de XAML-compiler een accessor voor u die voor het element wordt genoemd. Het .Text(...) deel roept de mutatorfunctie Text aan op het TextBlock-object dat de SampleTitle-accessor heeft teruggegeven. En FEATURE_NAME() roept onze statische MainPage::FEATURE_NAME accessor-functie aan om de letterlijke tekenreeks te retourneren. In die coderegel wordt de eigenschap Text van het TextBlock met de naam SampleTitle ingesteld.

Houd er rekening mee dat, aangezien tekenreeksen in de Windows Runtime breed zijn, we bij het overzetten van een tekenreeksliteral het wide-char-coderingsvoorvoegsel L gebruiken. Dus veranderen we (bijvoorbeeld) "a string literal" in L"a string literal". Zie ook brede tekenreeksconstanten.

Scenarios

Hier volgt de relevante C#-code die we moeten overzetten.

// 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) }
    };
...
}
...

Uit ons eerdere onderzoek weten we dat deze verzameling scenarioobjecten wordt weergegeven in een keuzelijst. In C++/WinRT zijn er limieten voor het soort verzameling dat we kunnen toewijzen aan de eigenschap ItemsSource van een itembesturingselement. De verzameling moet een vector of een waarneembare vector zijn en de bijbehorende elementen moeten een van de volgende zijn:

In het geval van IInspectable moeten die elementen, als ze zelf geen runtimeklassen zijn, van een type zijn dat naar en vanuit IInspectable kan worden geboxed en ge-unboxed. En dat betekent dat ze Windows Runtime-typen moeten zijn (zie Boxing en unboxing van waarden naar IInspectable).

Voor deze casestudy hebben we Scenario niet als runtimeklasse gebruikt. Dat is echter nog steeds een redelijke optie. En in uw eigen porteringswerk zullen er gevallen zijn waarbij een runtime-klasse absoluut de juiste keuze is. Als u bijvoorbeeld het elementtype waarneembaar wilt maken (zie XAML-besturingselementen; binden aan een C++/WinRT-eigenschap), of als het element methoden moet hebben om een andere reden, en dit is meer dan alleen een set gegevensleden.

Omdat we in deze walkthrough niet kiezen voor een runtime-klasse voor het type Scenario, moeten we nadenken over boxing. Als we Scenario een gewone C++ structzouden hebben gemaakt, zouden we het niet in de doos kunnen zetten. Maar we hebben Scenario als een struct in IDL gedeclareerd, dus we kunnen het boxen.

We hebben de keuze tussen het vooraf boxen van de Scenario of wachten tot we op het punt staan deze toe te wijzen aan de ItemsSource, en ze dan just-in-time te boxen. Hier volgen enkele overwegingen met betrekking tot deze twee opties.

  • Boxing van tevoren. Voor deze optie is ons gegevenslid een verzameling IInspectable die kan worden toegewezen aan de gebruikersinterface. Bij initialisatie plaatsen we de scenarioobjecten in dat gegevenslid. We hebben maar één kopie van die verzameling nodig, maar we moeten telkens een element unboxen wanneer we de velden ervan moeten lezen.
  • Boxing precies op tijd. Voor deze optie is ons datalid een verzameling van Scenario. Wanneer het tijd is om toe te wijzen aan de gebruikersinterface, plaatsen we de scenarioobjecten van het gegevenslid in een nieuwe verzameling IInspectable. We kunnen de velden van de elementen in het datalid lezen zonder unboxing, maar we hebben twee kopieën van de verzameling nodig.

Zoals u kunt zien, maken de voor- en nadelen voor een kleine verzameling als deze iets van een wasbeurt. Voor deze casestudy gaan we dus met de Just-In-Time-optie.

Het scenariolid is een veld van MainPage, gedefinieerd en geïnitialiseerd in SampleConfiguration.cs. En Scenario's is een alleen-lezen eigenschap van MainPage, gedefinieerd in MainPage.xaml.cs (en geïmplementeerd om eenvoudigweg het scenarioveld te retourneren). We doen iets vergelijkbaars in het C++/WinRT-project; maar we maken de twee leden statisch (omdat we slechts één exemplaar in de toepassing nodig hebben; zodat we er toegang toe hebben zonder dat er een klasse-exemplaar nodig is). En we noemen ze scenariosInner en scenarios, respectievelijk. We declareren scenariosInner in MainPage.h. En omdat het een statische opslagduur heeft, definiëren/initialiseren we deze in een .cpp bestand (SampleConfiguration.cppin dit geval).

Bewerk MainPage.h en SampleConfiguration.cpp zodat ze overeenkomen met de onderstaande vermeldingen.

// 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>() },
});

Zorg er ook voor dat u de bestaande hoofdtekst van de functie verwijdert voor MainPage.cppMainPage::scenarios(), omdat we die methode nu definiëren in het headerbestand.

Zoals u kunt zien, initialiseren we in SampleConfiguration.cpp het statische gegevenslid scenariosInner door een C++/WinRT-helperfunctie genaamd winrt::single_threaded_observable_vector aan te roepen. Deze functie maakt een nieuw Windows Runtime verzamelingsobject voor ons en retourneert het als een IObservableVector-interface. Omdat in dit voorbeeld de verzameling niet waarneembaar is (dit hoeft niet te zijn, omdat deze geen elementen toevoegt of verwijdert na initialisatie), kunnen we er in plaats daarvan voor kiezen om winrt::single_threaded_vector aan te roepen. Deze functie retourneert de verzameling als een IVector-interface .

Zie XAML-itemsbesturingselementen; binding aan een C++/WinRT-verzameling en Verzamelingen met C++/WinRT voor meer informatie over verzamelingen en het binden ervan.

De initialisatiecode die u zojuist hebt toegevoegd, bevat verwijzingen die nog niet in het project aanwezig zijn (bijvoorbeeld winrt::SDKTemplate::CopyText. Om dit te verhelpen, gaan we verder en voegen we vijf nieuwe lege XAML-pagina's toe aan het project.

Vijf nieuwe lege XAML-pagina's toevoegen

Voeg een nieuw Visual C++>Lege pagina (C++/WinRT)-item toe aan het project (zorg ervoor dat dit het itemsjabloon Lege pagina (C++/WinRT) is, en niet dat van Lege pagina). Geef deze de naam CopyText. De nieuwe XAML-pagina wordt gedefinieerd in de SDKTemplate-naamruimte . Dit is wat we willen.

Herhaal het bovenstaande proces nog vier keer en noem de XAML-pagina's CopyImage, CopyFilesen HistoryAndRoamingOtherScenarios.

U kunt nu opnieuw bouwen, indien gewenst.

NotifyUser

In het C#-project vindt u de implementatie van de methode MainPage.NotifyUser in MainPage.xaml.cs. MainPage.NotifyUser heeft een afhankelijkheid van MainPage.UpdateStatus en die methode heeft op zijn beurt afhankelijkheden van XAML-elementen die we nog niet hebben overgezet. We nemen dus voorlopig alleen een stub op voor de methode UpdateStatus in het C++/WinRT-project, en die zetten we later over.

Hier volgt de relevante C#-code die we moeten overzetten.

// 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 verzendt UI-updates naar de hoofdthread. In WinUI 3 gebruikt dit Microsoft. UI. Dispatching.DispatcherQueue in plaats van de oudere CoreDispatcher. Wanneer u in C++/WinRT een type wilt gebruiken vanuit een Windows of Microsoft naamruimte, moet u het bijbehorende C++/WinRT-naamruimteheaderbestand opnemen (zie Aan de slag met C++/WinRT) voor meer informatie hierover. In dit geval is, zoals u in het onderstaande codefragment zult zien, de header winrt/Microsoft.UI.Dispatching.h en zullen we deze opnemen in pch.h.

UpdateStatus is private. Dus maken we daar een privémethode van in ons implementatietype MainPage. UpdateStatus is niet bedoeld om aan te roepen in de runtimeklasse, dus we declareren deze niet in IDL.

Na het overzetten van MainPage.NotifyUser en het vervangen van MainPage.UpdateStatus door een stub, ziet het C++/WinRT-project er als volgt uit. Na deze codevermelding bekijken we enkele details.

// 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();
}
...

In C# kunt u puntnotatie gebruiken om naar geneste eigenschappen door te gaan. Het type C# MainPage heeft dus toegang tot de eigen dispatcher-eigenschap met de syntaxis Dispatcher. En C# kan verder stippelen in die waarde met syntaxis zoals Dispatcher.HasThreadAccess. In C++/WinRT worden eigenschappen geïmplementeerd als accessorfuncties, dus de syntaxis verschilt alleen in dat u haakjes toevoegt voor elke functie-aanroep.

C# C++/WinRT
Dispatcher.HasThreadAccess DispatcherQueue().HasThreadAccess()

Wanneer de C#-versie van NotifyUserDispatcher.RunAsync aanroept, gebruikt het Equivalent van WinUI 3 DispatcherQueue.TryEnqueue. De C++/WinRT-versie implementeert de callback-delegate als een lambdafunctie. In C++/WinRT leggen we de twee parameters vast die we gaan gebruiken, evenals de this aanwijzer (omdat we een lidfunctie gaan aanroepen). Er is meer informatie over het implementeren van gedelegeerden als lambdas en codevoorbeelden in het onderwerp Gebeurtenissen verwerken met behulp van gemachtigden in C++/WinRT.

De resterende MainPage-leden implementeren

Laten we een volledige lijst maken van de leden van MainPage (geïmplementeerd over MainPage.xaml.cs en SampleConfiguration.cs) zodat we kunnen zien welke we tot nu toe hebben overgezet en welke nog moeten worden gedaan.

Lid Access Status
MainPage-constructor public Geporteerd
Huidige eigenschap public Geporteerd
FEATURE_NAME-eigenschap public Geporteerd
Eigenschap IsClipboardContentChangedEnabled public Niet gestart
Eigenschap voor scenario’s public Geporteerd
Methode BuildClipboardFormatsOutputString public Niet gestart
Methode DisplayToast public Niet gestart
EnableClipboardContentChangedNotifications-methode public Niet gestart
Methode NotifyUser public Geporteerd
Methode OnNavigatedTo protected Niet gestart
isApplicationWindowActive-veld private Niet gestart
needToPrintClipboardFormat-veld private Niet gestart
scenarioveld private Overgezet
methode Button_Click private Niet gestart
Methode DisplayChangedFormats private Niet gestart
methode Footer_Click private Niet gestart
methode HandleClipboardChanged private Niet gestart
Methode OnClipboardChanged private Niet gestart
Methode OnWindowActivated private Niet gestart
ScenarioControl_SelectionChanged-methode private Niet gestart
Methode UpdateStatus private Als stub geïmplementeerd

In de volgende paar subsecties bespreken we de nog niet-geporteerde leden.

Note

Van tijd tot tijd komen we verwijzingen tegen in de broncode naar UI-elementen in de XAML-markering (in MainPage.xaml). Wanneer we deze verwijzingen tegenkomen, omzeilen we die tijdelijk door eenvoudige tijdelijke plaatshouderelementen aan de XAML toe te voegen. Op die manier wordt het project na elke subsectie verder gebouwd. Het alternatief is om de verwijzingen op te lossen door de volledige inhoud van MainPage.xaml het C#-project nu naar het C++/WinRT-project te kopiëren. Maar als we dat doen, is het een lange tijd voordat we naar een pitstop kunnen komen en opnieuw kunnen bouwen (waardoor mogelijk eventuele typefouten of andere fouten worden verborgen die we onderweg maken).

Zodra we klaar zijn met het overzetten van de imperatieve code voor de Klasse MainPage, kopiëren we de inhoud van het XAML-bestand en vertrouwen we erop dat het project nog steeds wordt gebouwd.

IsClipboardContentChangedEnabled

Dit is een get-set-C#-eigenschap die standaard is ingesteld op false. Het is lid van MainPage en wordt gedefinieerd in SampleConfiguration.cs.

Voor C++/WinRT hebben we een accessorfunctie, een mutatorfunctie en een ondersteunend gegevenslid in de vorm van een veld nodig. Omdat IsClipboardContentChangedEnabled de status van een van de scenario's in het voorbeeld vertegenwoordigt, in plaats van de status van MainPage zelf, maken we de nieuwe leden op een nieuw hulpprogrammatype met de naam SampleState. En dat implementeren we in ons SampleConfiguration.cpp broncodebestand en maken we de leden static (omdat we slechts één exemplaar in de toepassing nodig hebben; en zodat we ze kunnen openen zonder een klasse-exemplaar).

Als aanvulling op ons SampleConfiguration.cpp in het C++/WinRT-project voegt u een nieuw Visual C++>Code>Header File -item (.h) toe met de naam SampleConfiguration.h. Bewerk SampleConfiguration.h en SampleConfiguration.cpp zodat ze overeenkomen met de onderstaande vermeldingen.

// 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;
    }
}

Nogmaals, een veld met static opslag (zoals SampleState::isClipboardContentChangedEnabled) moet eenmaal in de toepassing worden gedefinieerd en een .cpp bestand is een goede plek voor dat (SampleConfiguration.cpp in dit geval).

BuildClipboardFormatsOutputString

Deze methode is een openbaar lid van MainPage en wordt gedefinieerd in 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();
}
...

In C++/WinRT maken we BuildClipboardFormatsOutputString een openbare statische methode van SampleState. We kunnen het static maken omdat het geen toegang heeft tot instantieleden.

Als u de typen Klembord en DataPackageView in C++/WinRT wilt gebruiken, moeten we het headerbestand winrt/Windows.ApplicationModel.DataTransfer.hvoor de C++/WinRT-Windows naamruimte opnemen.

In C# is de eigenschap DataPackageView.AvailableFormats een IReadOnlyList, zodat we toegang hebben tot de eigenschap Count van die eigenschap. In C++/WinRT retourneert de accessorfunctie DataPackageView::AvailableFormats een IVectorView, die een accessorfunctie grootte heeft die we kunnen aanroepen.

Als u het gebruik van het type C# System.Text.StringBuilder wilt overzetten, maken we gebruik van het standaard C++-type std::wostringstream. Dit type is een uitvoerstroom voor brede tekenreeksen (en om dit te kunnen gebruiken, moeten we het sstream headerbestand opnemen). In plaats van een toevoegmethode te gebruiken zoals u dat met een StringBuilder doet, gebruikt u de invoegoperator (<<) met een uitvoerstroom zoals wostringstream. Zie iostream programmeren en C++/WinRT-tekenreeksen opmaken voor meer informatie.

De C#-code maakt een StringBuilder met het new trefwoord. In C# zijn objecten standaard referentietypen, die zijn gedeclareerd op de heap met new. In moderne standaard C++zijn objecten standaard waardetypen, gedeclareerd op de stack (zonder gebruik new). Dus porteren we StringBuilder output = new StringBuilder(); naar C++/WinRT eenvoudigweg als std::wostringstream output;.

Het C# var -trefwoord vraagt de compiler een type af te stellen. U zet var over naar auto in C++/WinRT. Maar in C++/WinRT zijn er gevallen waarin (om kopieën te voorkomen) u een verwijzing naar een afgeleid (of afgeleid) type wilt en u een lvalue-verwijzing naar een afgeleid type uitdrukt met auto&. Er zijn ook gevallen waarin u een speciaal type verwijzing wilt dat correct bindt, ongeacht of deze is geïnitialiseerd met een lvalue of met een rvalue. En je drukt dat uit met auto&&. Dat is de vorm die u in de for-lus ziet worden gebruikt in de hieronder geporteerde code. Zie Waardecategorieën en verwijzingen naar de waarden voor een inleiding tot lvalues en rvalues.

Bewerk pch.h, SampleConfiguration.hen SampleConfiguration.cpp zodat deze overeenkomen met de onderstaande vermeldingen.

// 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

De syntaxis in de coderegel DataPackageView clipboardContent{ Clipboard::GetContent() }; maakt gebruik van een functie van moderne standaard C++ die uniforme initialisatie wordt genoemd, met het karakteristieke gebruik van accolades in plaats van een = teken. Met deze syntaxis wordt duidelijk dat initialisatie, in plaats van toewijzing, plaatsvindt. Als u de voorkeur geeft aan de vorm van syntaxis die eruitziet als toewijzing (maar eigenlijk niet is), kunt u de bovenstaande syntaxis vervangen door het equivalent DataPackageView clipboardContent = Clipboard::GetContent();. Het is echter een goed idee om vertrouwd te raken met beide manieren van initialisatie, omdat u waarschijnlijk beide vaak gebruikt in de code die u tegenkomt.

DisplayToast

DisplayToast is een openbare statische methode van de C# MainPage-klasse en u vindt deze gedefinieerd in SampleConfiguration.cs. In C++/WinRT maken we het een openbare statische methode van SampleState.

We hebben al de meeste details en technieken aangetroffen die relevant zijn voor het overzetten van deze methode. Een nieuw punt om op te merken is dat u een C#-letterlijke tekenreeks voor letterlijke tekst (@) omzet in een standaard-C++-raw string literal (LR).

Wanneer u verwijst naar de typen ToastNotification en XmlDocument in C++/WinRT, kunt u deze kwalificeren op naamruimtenaam of kunt u instructies bewerken SampleConfiguration.cpp en toevoegen using namespace , zoals in het volgende voorbeeld.

using namespace Windows::UI::Notifications;

U hebt dezelfde keuze wanneer u naar het XmlDocument-type verwijst en wanneer u naar een ander Windows Runtime type verwijst.

Naast deze items volgt u dezelfde richtlijnen die u eerder hebt gedaan om de volgende stappen uit te voeren.

  • Declareer de methode in SampleConfiguration.hen definieer deze in SampleConfiguration.cpp.
  • Bewerk pch.h deze om eventueel benodigde C++/WinRT-Windows naamruimteheaderbestanden op te nemen.
  • Bouw C++/WinRT-objecten op de stapel, niet op de heap.
  • Vervang aanroepen van get-accessors van eigenschappen door functie-aanroepsyntaxis (()).

Een veelvoorkomende oorzaak van compiler-/linkerfouten is het vergeten om de C++/WinRT-Windows naamruimteheaderbestanden op te nemen die u nodig hebt. Zie C3779: Waarom krijg ik van de compiler de foutmelding "consume_Something: functie die 'auto' retourneert, kan niet worden gebruikt voordat deze is gedefinieerd" voor meer informatie over een mogelijke foutmelding.

Als u de walkthrough wilt volgen en DisplayToast zelf wilt porteren, kunt u uw resultaten vergelijken met de code in de C++/WinRT-versie in het ZIP-bestand met de broncode van het voorbeeld Clipboard dat u hebt gedownload.

EnableClipboardContentChangedNotifications

EnableClipboardContentChangedNotifications is een openbare statische methode van de C# MainPage-klasse en wordt gedefinieerd in 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) { ... }
...

In C++/WinRT maken we het een openbare statische methode van SampleState.

In C# gebruikt u de += syntaxis en -= operator om gedelegeerden voor gebeurtenisafhandeling te registreren en in te trekken. In C++/WinRT hebt u verschillende syntactische opties om een gedelegeerde te registreren/intrekken, zoals beschreven in Gebeurtenissen afhandelen met behulp van gemachtigden in C++/WinRT. Maar de algemene vorm is dat u zich registreert en deregistreert door een paar functies aan te roepen die naar de gebeurtenis zijn genoemd. Als u zich wilt registreren, geeft u uw gemachtigde door aan de registratiefunctie en haalt u een intrekkingstoken op als resultaat (een winrt::event_token). Als u het token wilt intrekken, geeft u dat token door aan de intrekkingsfunctie. In dit geval is de hander statisch en (zoals u kunt zien in de volgende codevermelding) is de syntaxis van de functieaanroep eenvoudig.

Vergelijkbare tokens worden achter de schermen gebruikt in C#. Maar de taal maakt dat detail impliciet. C++/WinRT maakt het expliciet.

Het objecttype wordt weergegeven in de C#-gebeurtenishandlerhandtekeningen. In de C#-taal is het object een alias voor het type .NET System.Object. De tegenhanger in C++/WinRT is winrt::Windows::Foundation::IInspectable. U ziet dus IInspectable in de C++/WinRT-gebeurtenis-handlers.

Bewerk SampleConfiguration.h en SampleConfiguration.cpp zodat ze overeenkomen met de onderstaande vermeldingen.

// 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){}

Laat de delegeringen voor gebeurtenisafhandeling zelf (OnClipboardChanged en OnWindowActivated) voorlopig als stubs. Ze staan al op onze lijst met leden die we nog moeten overzetten, dus we behandelen ze in latere subsecties.

OnNavigatedTo

OnNavigatedTo is een beveiligde methode van de C# MainPage-klasse en wordt gedefinieerd in MainPage.xaml.cs. Hier is het, samen met XAML ListBox waarnaar het verwijst.

<!-- 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;
    }
}

Het is een belangrijke en interessante methode, omdat hier onze verzameling scenarioobjecten wordt toegewezen aan de gebruikersinterface. De C#-code bouwt een System.Collections.Generic.List met scenarioobjecten en wijst dat toe aan de eigenschap ItemsBron van een keuzelijst (dit is een itembesturingselement). En in C# gebruiken we tekenreeksinterpolatie om de titel voor elk scenarioobject te bouwen (let op het gebruik van het $ speciale teken).

In C++/WinRT maken we OnNavigatedTo een openbare methode van MainPage. En we voegen een stub ListBox-element toe aan de XAML, zodat een build slaagt. Na de codevermelding bekijken we enkele details.

<!-- 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);
    }
}
...

Opnieuw roepen we de winrt::single_threaded_observable_vector functie aan, maar deze keer om een verzameling IInspectable te maken. Dat was onderdeel van de beslissing die we hebben genomen om de boksen van onze Scenario-objecten op just-in-time-basis uit te voeren.

En in plaats van C#'s gebruik van tekenreeksinterpolatie gebruiken we hier een combinatie van de to_hstring-functie en de samenvoegingsoperator van winrt::hstring.

isApplicationWindowActive

In C# is isApplicationWindowActive een eenvoudig privéveld bool dat hoort bij de klasse MainPage en wordt gedefinieerd in SampleConfiguration.cs. De standaardinstelling is false. In C++/WinRT maken we het een privé statisch veld van SampleState (om de redenen die we al hebben beschreven) in de SampleConfiguration.h en SampleConfiguration.cpp bestanden, met dezelfde standaardwaarde.

We hebben al gezien hoe u een statisch veld declareert, definieert en initialiseert. Kijk voor een vernieuwing terug naar wat we hebben gedaan met het veld isClipboardContentChangedEnabled en doe hetzelfde met isApplicationWindowActive.

needToPrintClipboardFormat

Hetzelfde patroon als isApplicationWindowActive (zie de kop direct vóór deze).

Button_Click

Button_Click is een persoonlijke methode (gebeurtenisafhandeling) van de C# MainPage-klasse en wordt gedefinieerd in MainPage.xaml.cs. Hier is het, samen met de XAML SplitView waarnaar wordt verwezen, en de ToggleButton die het registreert.

<!-- MainPage.xaml -->
...
<SplitView x:Name="Splitter" ... />
...
<ToggleButton Click="Button_Click" .../>
...
private void Button_Click(object sender, RoutedEventArgs e)
{
    Splitter.IsPaneOpen = !Splitter.IsPaneOpen;
}

En dit is het equivalent, dat is overgezet naar C++/WinRT. Houd er rekening mee dat in de C++/WinRT-versie de gebeurtenis-handler is public (zoals u kunt zien, declareert u deze vóór de private:declaraties). Dit komt doordat een gebeurtenisafhandelaar die is geregistreerd in XAML-opmaak, zoals deze, in C++/WinRT als public moet zijn gemarkeerd zodat de XAML-opmaak er toegang toe heeft. Als u daarentegen een gebeurtenis-handler registreert in imperatieve code (zoals in MainPage::EnableClipboardContentChangedNotifications eerder), hoeft de gebeurtenis-handler niet te zijn public.

<!-- MainPage.xaml -->
...
<StackPanel ...>
    ...
    <SplitView x:Name="Splitter" />
</StackPanel>
...
// MainPage.h
...
    void Button_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& e);
private:
...

// MainPage.cpp
void MainPage::Button_Click(Windows::Foundation::IInspectable const& /* sender */, Microsoft::UI::Xaml::RoutedEventArgs const& /* e */)
{
    Splitter().IsPaneOpen(!Splitter().IsPaneOpen());
}

DisplayChangedFormats

In C# is DisplayChangedFormats een privémethode die hoort bij de klasse MainPage en wordt deze gedefinieerd in SampleConfiguration.cs.

private void DisplayChangedFormats()
{
    string output = "Clipboard content has changed!" + Environment.NewLine;
    output += BuildClipboardFormatsOutputString();
    NotifyUser(output, NotifyType.StatusMessage);
}

In C++/WinRT maken we het een privé statisch veld van SampleState (het heeft geen toegang tot exemplaarleden), in de SampleConfiguration.h en SampleConfiguration.cpp bestanden. De C#-code voor deze methode maakt geen gebruik van System.Text.StringBuilder; maar er is voldoende tekenreeksopmaak die voor de C++/WinRT-versie een andere goede plek is om std::wostringstream te gebruiken.

In plaats van de eigenschap static System.Environment.NewLine , die wordt gebruikt in de C#-code, voegen we de standaard C++ std::endl (een nieuw regelteken) in de uitvoerstroom in.

// 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);
}

Er is een kleine inefficiëntie in het ontwerp van de bovenstaande C++/WinRT-versie. Eerst maken we een std::wostringstream. Maar we noemen ook de methode BuildClipboardFormatsOutputString (die we eerder hebben overgezet). Deze methode maakt een eigen std::wostringstream. En het zet zijn stream om in een winrt::hstring en geeft die vervolgens terug. We roepen de hstring::c_str functie aan om die geretourneerde hstring terug te zetten in een C-stijltekenreeks en dat vervolgens in onze stroom in te voegen. Het zou efficiënter zijn om slechts één std::wostringstream te maken en die (of een verwijzing ernaar) door te geven, zodat methoden er rechtstreeks tekenreeksen in kunnen invoegen.

Dat doen we in de C++/WinRT-versie van de broncode van het Klembord-voorbeeld (in de ZIP die u hebt gedownload). In deze broncode is er een nieuwe statische privémethode met de naam SampleState::AddClipboardFormatsOutputString, die een verwijzing naar een uitvoerstroom gebruikt en werkt. En vervolgens worden de methoden SampleState::D isplayChangedFormats en SampleState::BuildClipboardFormatsOutputString geherstructureerd om die nieuwe methode aan te roepen. Het is functioneel gelijk aan de codevermeldingen in dit onderwerp, maar dit is efficiënter.

Footer_Click is een asynchrone gebeurtenis-handler die deel uitmaakt van de C# MainPage-klasse en deze is gedefinieerd in MainPage.xaml.cs. De onderstaande codevermelding is functioneel gelijk aan de methode in de broncode die u hebt gedownload. Maar hier heb ik het uitgepakt van één regel naar vier, om het gemakkelijker te maken om te zien wat het doet, en dus hoe we het moeten overzetten.

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);
}

Hoewel de methode technisch gezien asynchroon is, doet deze niets na de awaitmethode, dus het heeft niet het await (noch het async trefwoord) nodig. Deze worden waarschijnlijk gebruikt om het IntelliSense-bericht in Visual Studio te voorkomen.

De equivalente C++/WinRT-methode is ook asynchroon (omdat het Launcher.LaunchUriAsync aanroept). Maar dat hoeft niet co_await, en het hoeft ook geen asynchroon object terug te geven. Zie co_await voor informatie over en asynchrone objecten.

Laten we nu eens praten over wat de methode doet. Omdat dit een gebeurtenis-handler is voor de click-gebeurtenis van een HyperlinkButton, is het object met de naam afzender eigenlijk een HyperlinkButton. De typeconversie is dus veilig (we kunnen deze conversie ook hebben uitgedrukt als sender as HyperlinkButton). Vervolgens halen we de waarde van de eigenschap Tag op (als u de XAML-markering in het C#-project bekijkt, ziet u dat dit is ingesteld op een tekenreeks die een web-URL vertegenwoordigt). Hoewel de eigenschap FrameworkElement.Tag (HyperlinkButton een FrameworkElement) van het type object is, kunnen we in C# dat met Object.ToString markeren. Met de resulterende tekenreeks maken we een URI-object . En tot slot (met behulp van de Shell) starten we een browser en gaan we naar de URL.

Hier volgt de methode die is overgezet naar C++/WinRT (opnieuw uitgebreid voor duidelijkheid), waarna een beschrijving van de details is.

// 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);
}

Zoals altijd maken we de eventhandler public. We gebruiken de functie als op het afzenderobject om het te converteren naar HyperlinkButton. In C++/WinRT is de eigenschap Tag een IInspectable (het equivalent van Object). Maar er is geen Tostring voor IInspectable. In plaats daarvan moeten we de IInspectable uitpakken naar een scalaire waarde (in dit geval een tekenreeks). Zie Voor meer informatie over boksen en uitpakken waarden voor IInspectable voor meer informatie over boksen en uitpakken.

De laatste twee regels herhalen poortpatronen die we eerder hebben gezien en ze echoën de C#-versie.

HandleClipboardChanged

Er is niets nieuws bij het overzetten van deze methode. U kunt de C#- en C++/WinRT-versies vergelijken in het ZIP-bestand van de broncode van het Klembord-voorbeeld dat u hebt gedownload.

OnClipboardChanged en OnWindowActivated

Tot nu toe hebben we alleen lege stubs van deze twee eventhandlers. Maar het overzetten van ze is eenvoudig, en er komt niets nieuws bij om te bespreken.

ScenarioControl_SelectionChanged

Dit is een andere privé-gebeurtenishandler die deel uitmaakt van de C# MainPage-klasse en is gedefinieerd in MainPage.xaml.cs. In C++/WinRT maken we het openbaar en implementeren we het in MainPage.h en MainPage.cpp.

Voor deze methode hebben we MainPage::navigating nodig. Dit is een privé booleaanse veld, geïnitialiseerd naar false. En u hebt een frame nodig in MainPage.xaml, genaamd ScenarioFrame. Maar behalve die details toont het overzetten van deze methode geen nieuwe technieken.

Als u, in plaats van handmatig te porteren, code kopieert uit de C++/WinRT-versie in het ZIP-bestand van de broncode van het Clipboard-voorbeeld dat u hebt gedownload, dan ziet u dat daar een MainPage::NavigateTo wordt gebruikt. Voor nu kunt u de inhoud van NavigateTo herstructureren in ScenarioControl_SelectionChanged.

UpdateStatus

We hebben tot nu toe slechts een stub voor MainPage.UpdateStatus. Het overzetten van de implementatie ervan herhaalt grotendeels wat al bekend is. Een nieuw punt om op te merken is dat we in C# een tekenreeks kunnen vergelijken met String.Empty, in C++/WinRT roepen we in plaats daarvan de winrt::hstring::lege functie aan. Een andere is het nullptr standaard C++-equivalent van C#'s null.

U kunt de rest van de poort uitvoeren met technieken die we al hebben behandeld. Hier is een lijst met de zaken die u moet doen voordat de geporteerde versie van deze methode kan worden gecompileerd.

  • Voeg MainPage.xamleen rand toe met de naam StatusBorder.
  • Voeg MainPage.xamleen TextBlock toe met de naam StatusBlock.
  • Voeg MainPage.xamleen StackPanel toe met de naam StatusPanel.
  • Voeg #include "winrt/Microsoft.UI.Xaml.Media.h" toe om pch.h.
  • Voeg #include "winrt/Microsoft.UI.Xaml.Automation.Peers.h" toe om pch.h.
  • Om MainPage.cpp toe te voegen using namespace winrt::Microsoft::UI::Xaml::Media;.
  • Om MainPage.cpptoe te voegenusing namespace winrt::Microsoft::UI::Xaml::Automation::Peers;.

Kopieer de XAML en stijlen die nodig zijn voor het voltooien van de overdracht van MainPage

Voor XAML is het ideale geval dat u dezelfde XAML-markeringen kunt gebruiken in een C# en een C++/WinRT-project. En het Klembord-voorbeeld is een van deze gevallen.

In het Styles.xaml-bestand heeft het Clipboard-voorbeeld een XAML-ResourceDictionary met stijlen, die worden toegepast op de knoppen, menu's en andere UI-elementen in de hele gebruikersinterface van de toepassing. De Styles.xaml pagina wordt samengevoegd in App.xaml. En dan is er het standaard MainPage.xaml startpunt voor de gebruikersinterface, die we al kort hebben gezien. We kunnen deze drie .xaml bestanden nu opnieuw gebruiken, ongewijzigd, in de C++/WinRT-versie van het project.

Net als bij assetbestanden kunt u ervoor kiezen om te verwijzen naar dezelfde, gedeelde XAML-bestanden uit meerdere versies van uw toepassing. In deze procedure kopiëren we bestanden in het C++/WinRT-project en voegen we ze op die manier toe.

Navigeer naar de \Clipboard_sample\SharedContent\xaml map, selecteer en kopieer App.xaml en MainPage.xamlplak deze twee bestanden in de \Clipboard\Clipboard map in uw C++/WinRT-project, waarbij u ervoor kiest om bestanden te vervangen wanneer u hierom wordt gevraagd.

Klik in het C++/WinRT-project in Visual Studio op Alle bestanden weergeven om het in te schakelen. Voeg nu een nieuwe map toe, direct onder het projectknooppunt en geef deze een Stylesnaam. Navigeer in Verkenner naar de \Clipboard_sample\SharedContent\xaml map, selecteer en kopieer Styles.xamlen plak deze in de \Clipboard\Clipboard\Styles map die u zojuist hebt gemaakt. Klik in Solution Explorer in het C++/WinRT-project met de rechtermuisknop op de Styles map >Bestaand item>... en navigeer naar \Clipboard\Clipboard\Styles. Selecteer Styles en klik in de bestandskiezer op Toevoegen.

Voeg een nieuwe map toe aan het C++/WinRT-project, direct onder het projectknooppunt en met de naam Styles. Navigeer naar de \Clipboard_sample\SharedContent\xaml map, selecteer en kopieer Styles.xamlen plak deze in de \Clipboard\Clipboard\Styles map in uw C++/WinRT-project. Klik met de rechtermuisknop op de Styles map (in Solution Explorer in het C++/WinRT-project) >Bestaand item>... en navigeer naar \Clipboard\Clipboard\Styles. Selecteer Styles en klik in de bestandskiezer op Toevoegen.

Klik nogmaals op Alle bestanden weergeven om deze uit te schakelen.

We zijn nu klaar met het overzetten van MainPage en als u de stappen hebt gevolgd, wordt uw C++/WinRT-project nu gebouwd en uitgevoerd.

Uw .idl bestanden samenvoegen

Naast het standaard MainPage.xaml startpunt voor de gebruikersinterface heeft het Klembordvoorbeeld vijf andere scenariospecifieke XAML-pagina's, samen met de bijbehorende code-behind-bestanden. We gebruiken de werkelijke XAML-opmaak van al deze pagina's, ongewijzigd, in de C++/WinRT-versie van het project. En in de volgende paar hoofdsecties bekijken we hoe u de code-behind kunt overzetten. Maar daarvoor gaan we het hebben over IDL.

Er is waarde in het consolideren van de IDL voor uw runtimeklassen in één IDL-bestand. Raadpleeg Runtimeklassen onderbrengen in MIDL-bestanden (.idl) voor meer informatie over deze waarde. Vervolgens bundelen we de inhoud van CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idl en OtherScenarios.idl door die IDL naar één bestand met de naam Project.idl te verplaatsen (en vervolgens de oorspronkelijke bestanden te verwijderen).

Terwijl we dat doen, gaan we ook de automatisch gegenereerde dummy-eigenschap (Int32 MyProperty;en de implementatie) van elk van deze vijf XAML-paginatypen verwijderen.

Voeg eerst een nieuw Midl-bestand (.idl)- item toe aan het C++/WinRT-project. Geef deze de naam Project.idl. Vervang de volledige inhoud van Project.idl door de volgende code.

// 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();
    }
}

Zoals u kunt zien, is dat slechts een kopie van de inhoud van de afzonderlijke .idl bestanden, allemaal binnen één naamruimte en met MyProperty verwijderd uit elke runtimeklasse.

Selecteer in Solution Explorer in Visual Studio alle oorspronkelijke IDL-bestanden (CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idlen ) en OtherScenarios.idlBewerk>ze (kies Verwijderen in het dialoogvenster).

Verwijder ten slotte — en om het verwijderen van MyProperty te voltooien — in de bestanden .h en .cpp voor elk van diezelfde vijf XAML-paginatypen de declaraties en definities van de accessorfunctie int32_t MyProperty() en de mutatorfunctie void MyProperty(int32_t).

Het is trouwens altijd een goed idee om de naam van uw XAML-bestanden te laten overeenkomen met de naam van de klasse die ze vertegenwoordigen. Als u bijvoorbeeld een XAML-opmaakbestand hebt x:Class="MyNamespace.MyPage" , moet dat bestand de naam MyPage.xamlhebben. Hoewel dit geen technische vereiste is, is het niet nodig om verschillende namen voor hetzelfde artefact te gebruiken, waardoor uw project begrijpelijker en beter te onderhouden is en gemakkelijker kan worden gebruikt.

CopyFiles

In het C#-project wordt het paginatype CopyFiles XAML geïmplementeerd in de CopyFiles.xaml en CopyFiles.xaml.cs broncodebestanden. Laten we eens kijken naar elk van de leden van CopyFiles .

rootPage

Dit is een privéveld.

// CopyFiles.xaml.cs
...
public sealed partial class CopyFiles : Page
{
    MainPage rootPage = MainPage.Current;
    ...
}
...

In C++/WinRT kunnen we deze als volgt definiëren en initialiseren.

// CopyFiles.h
...
struct CopyFiles : CopyFilesT<CopyFiles>
{
    ...
private:
    SDKTemplate::MainPage rootPage{ MainPage::Current() };
};
...

Opnieuw (net als bij MainPage::current), wordt CopyFiles::rootPage gedeclareerd als type SDKTemplate::MainPage, het verwachte type en niet het implementatietype.

CopyFiles (de constructor)

In het C++/WinRT-project heeft het type CopyFiles al een constructor die de gewenste code bevat (het roept alleen InitializeComponent aan).

CopyButton_Click

De methode C# CopyButton_Click is een gebeurtenis-handler en uit het trefwoord in de async handtekening kunnen we zien dat de methode asynchroon werkt. In C++/WinRT implementeren we een asynchrone methode als een coroutine. Zie Gelijktijdigheid en asynchrone bewerkingen met C++/WinRT voor een inleiding tot gelijktijdigheid in C++/WinRT, samen met een beschrijving van wat een coroutine is.

Het is gebruikelijk om aanvullend werk te plannen nadat een coroutine is voltooid, en in zulke gevallen retourneert de coroutine een asynchroon objecttype waarop gewacht kan worden en dat optioneel de voortgang rapporteert. Maar deze overwegingen zijn doorgaans niet van toepassing op een gebeurtenis-handler. Dus wanneer u een gebeurtenis-handler hebt die asynchrone bewerkingen uitvoert, kunt u dit implementeren als een coroutine die winrt::fire_and_forget retourneert. Zie Fire and forget voor meer informatie.

Hoewel het idee van een fire-and-forget-coroutine is dat het u niet uitmaakt wanneer die wordt voltooid, gaat het werk op de achtergrond nog steeds door (of is het opgeschort, in afwachting van hervatting). U kunt aan de C#-implementatie zien dat CopyButton_Click afhankelijk is van de aanwijzer this (het benadert het instantiegegevenslid rootPage). We moeten er dus zeker van zijn dat de this aanwijzer (een aanwijzer naar een CopyFiles-object ) de CopyButton_Click coroutine overleeft. In een situatie zoals deze voorbeeldtoepassing, waarbij de gebruiker tussen ui-pagina's navigeert, kunnen we de levensduur van deze pagina's niet rechtstreeks beheren. Als de pagina CopyFiles wordt vernietigd (door ervan weg te navigeren) terwijl CopyButton_Click nog steeds wordt uitgevoerd op een achtergrondthread, is het niet veilig om toegang te krijgen tot rootPage. Om de coroutine juist te maken, moet deze een sterke verwijzing naar de this aanwijzer verkrijgen en die verwijzing behouden voor de duur van de coroutine. Zie Sterke en zwakke verwijzingen in C++/WinRT voor meer informatie.

Als u in de C++/WinRT-versie van het voorbeeld kijkt, ziet u bij CopyFiles::CopyButton_Click dat dit wordt gedaan met een eenvoudige declaratie op de stack.

fire_and_forget CopyFiles::CopyButton_Click(IInspectable const&, RoutedEventArgs const&)
{
    auto lifetime{ get_strong() };
    ...
}

Laten we eens kijken naar de andere aspecten van de overgezet code die opmerkelijk zijn.

In de code instantiëren we een FileOpenPicker-object en twee regels later hebben we toegang tot de eigenschap FileTypeFilter van dat object. Het retourtype van die eigenschap implementeert een IVector van tekenreeksen. En op die IVector roepen we de methode IVector<T>.ReplaceAll(T[]) aan. Het interessante aspect is de waarde die we aan die methode doorgeven, waarbij een matrix wordt verwacht. Hier is de coderegel.

filePicker.FileTypeFilter().ReplaceAll({ L"*" });

De waarde die we doorgeven ({ L"*" }) is een standaard C++ -initialisatielijst. Het bevat één object, in dit geval, maar een initialisatielijst kan een willekeurig aantal door komma's gescheiden objecten bevatten. De onderdelen van C++/WinRT waarmee u eenvoudig een initialisatielijst kunt doorgeven aan een methode zoals deze, worden uitgelegd in Standaard-initialisatielijsten.

We zetten het C# await -trefwoord over in co_await C++/WinRT. Hier volgt het voorbeeld uit de code.

auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };

Bekijk vervolgens deze regel C#-code.

dataPackage.SetStorageItems(storageItems);

C# kan het IReadOnlyList<StorageFile> dat wordt vertegenwoordigd door storageItems impliciet converteren naar de IEnumerable<IStorageItem> die wordt verwacht door DataPackage.SetStorageItems. Maar in C++/WinRT moeten we expliciet converteren van IVectorView<StorageFile> naar IIterable<IStorageItem>. En dus hebben we een ander voorbeeld van de functie als in actie.

dataPackage.SetStorageItems(storageItems.as<IVectorView<IStorageItem>>());

Waar we het null trefwoord gebruiken in C# (bijvoorbeeld Clipboard.SetContentWithOptions(dataPackage, null)), gebruiken nullptr we in C++/WinRT (bijvoorbeeld Clipboard::SetContentWithOptions(dataPackage, nullptr)).

PasteButton_Click

Dit is nog een andere gebeurtenisafhandelaar in de vorm van een fire-and-forget-coroutine. Laten we eens kijken naar de aspecten van de overgezet code die opmerkelijk zijn.

In de C#-versie van het voorbeeld vangen we uitzonderingen met catch (Exception ex). In de gepoorte C++/WinRT-code ziet u de expressie catch (winrt::hresult_error const& ex). Zie Foutafhandeling met C++/WinRT voor meer informatie over winrt::hresult_error en hoe u ermee kunt werken.

Een voorbeeld van het testen of een C#-object al dan niet is nullif (storageItems != null). In C++/WinRT kunnen we gebruikmaken van een conversieoperator naar bool, die intern controleert op nullptr.

Hier volgt een enigszins vereenvoudigde versie van een codefragment van de gepoorte C++/WinRT-versie van het voorbeeld.

std::wostringstream output;
output << std::wstring_view(ApplicationData::Current().LocalFolder().Path());

Het construeren van een std::wstring_view uit een winrt::hstring op die manier vormt een alternatief voor het aanroepen van de functie hstring::c_str (om de winrt::hstring om te zetten in een tekenreeks in C-stijl). Dit alternatief werkt dankzij de conversieoperator van hstringnaar std::wstring_view.

Houd rekening met dit fragment van C#.

var file = storageItem as StorageFile;
if (file != null)
...

Om het C# as-trefwoord om te zetten naar C++/WinRT, hebben we de functie as tot nu toe een paar keer zien langskomen. Deze functie genereert een uitzondering als de typeconversie mislukt. Maar als we willen dat de conversie wordt geretourneerd nullptr als deze mislukt (zodat we die voorwaarde in de code kunnen verwerken), gebruiken we in plaats daarvan de try_as functie.

auto file{ storageItem.try_as<StorageFile>() };
if (file)
...

Kopieer de XAML die nodig is om het overzetten van CopyFiles te voltooien

U kunt nu de volledige inhoud van het CopyFiles.xaml bestand selecteren in de shared map van de oorspronkelijke broncodedownload en dit in het bestand in het CopyFiles.xaml C++/WinRT-project plakken (waarbij u de bestaande inhoud van dat bestand in het C++/WinRT-project vervangt).

Bewerk CopyFiles.h en .cpp verwijder ten slotte de dummy ClickHandler-functie , omdat we de bijbehorende XAML-markeringen net hebben overschreven.

We zijn nu klaar met het overzetten van CopyFiles en als u de stappen hebt gevolgd, wordt uw C++/WinRT-project nu gebouwd en uitgevoerd en is het CopyFiles-scenario functioneel.

CopyImage

Als u het paginatype CopyImage XAML wilt overzetten, volgt u hetzelfde proces als voor CopyFiles. Tijdens het overzetten van CopyImage komt u het gebruik van de C# using-instructie tegen, die ervoor zorgt dat objecten die de IDisposable-interface implementeren op de juiste manier worden vrijgegeven.

if (imageReceived != null)
{
    using (var imageStream = await imageReceived.OpenReadAsync())
    {
        ... // Pass imageStream to other APIs, and do other work.
    }
}

De overeenkomstige interface in C++/WinRT is IClosable, met de enige Close-methode. Hier volgt het C++/WinRT-equivalent van de bovenstaande C#-code.

if (imageReceived)
{
    auto imageStream{ co_await imageReceived.OpenReadAsync() };
    ... // Pass imageStream to other APIs, and do other work.
    imageStream.Close();
}

C++/WinRT-objecten implementeren IClosable voornamelijk ten behoeve van talen die geen deterministische finalisatie hebben. C++/WinRT heeft deterministische finalisatie en daarom hoeven we vaak IClosable niet aan te roepen::Sluit wanneer we C++/WinRT schrijven. Maar er zijn momenten waarop het goed is om het te noemen, en dit is een van die tijden. Hier is de imageStream-id een wrapper met verwijzingen rond een onderliggend Windows Runtime-object (in dit geval een object dat IRandomAccessStreamWithContentType implementeert). Hoewel we kunnen vaststellen dat de finalizer van imageStream (de destructor ervan) aan het einde van het omsluitende bereik (de accolades) zal worden uitgevoerd, kunnen we niet zeker weten dat die finalizer Close zal aanroepen. Dat komt doordat we imageStream hebben doorgegeven aan andere API's en ze mogelijk nog steeds bijdragen aan het referentieaantal van het onderliggende Windows Runtime-object. Dit is dus een geval waarin het een goed idee is om Close expliciet aan te roepen. Zie voor meer informatie Moet ik IClosable::Close aanroepen voor runtimeklassen die ik gebruik?.

Bekijk vervolgens de C#-expressie (uint)(imageDecoder.OrientedPixelWidth * 0.5), die u vindt in de gebeurtenis-handler OnDeferredImageRequestedHandler . Deze expressie vermenigvuldigt een uint met een double, wat resulteert in een double. Vervolgens cast het dat naar een uint. In C++/WinRT kunnen we een vergelijkbare C-stijl cast ((uint32_t)(imageDecoder.OrientedPixelWidth() * 0.5)) gebruiken, maar het verdient de voorkeur om precies duidelijk te maken wat voor soort cast we willen, en in dit geval zouden we dat doen met static_cast<uint32_t>(imageDecoder.OrientedPixelWidth() * 0.5).

De C#-versie van CopyImage.OnDeferredImageRequestedHandler heeft een finally component, maar geen catch component. We zijn iets verder gegaan in de C++/WinRT-versie en hebben een catch component geïmplementeerd, zodat we kunnen rapporteren of de vertraagde rendering wel of niet is gelukt.

Het overzetten van de rest van deze XAML-pagina levert niets nieuws op om te bespreken. Vergeet niet om de dummy ClickHandler-functie te verwijderen. En net als bij CopyFiles is de laatste stap in de poort het selecteren van de volledige inhoud van CopyImage.xamlen plakken in hetzelfde bestand in het C++/WinRT-project.

CopyText

U kunt CopyText.xaml en CopyText.xaml.cs overzetten met technieken die we al hebben behandeld.

HistoryAndRoaming

Er zijn enkele interessante punten die zich voordoen tijdens het overzetten van het paginatype HistoryAndRoaming XAML.

Bekijk eerst de C#-broncode en volg de controlestroom van OnNavigatedTo via de gebeurtenis-handler OnHistoryEnabledChanged en ten slotte naar de asynchrone functie CheckHistoryAndRoaming (die niet wordt verwacht, dus het is in wezen geactiveerd en vergeten). Omdat CheckHistoryAndRoaming asynchroon is, moeten we voorzichtig zijn in C++/WinRT over de levensduur van de this aanwijzer. U kunt het resultaat zien als u de implementatie in het HistoryAndRoaming.cpp broncodebestand bekijkt. Ten eerste: wanneer we delegates koppelen aan de gebeurtenissen Clipboard::HistoryEnabledChanged en Clipboard::RoamingEnabledChanged, nemen we alleen een zwakke verwijzing naar het paginaobject HistoryAndRoaming. Dit doen we door de gemachtigde te maken met een afhankelijkheid van de waarde die wordt geretourneerd door winrt::get_weak, in plaats van een afhankelijkheid van de this aanwijzer. Dit betekent dat de delegate zelf, die uiteindelijk asynchrone code aanroept, de pagina HistoryAndRoaming niet actief houdt als we ervan weg navigeren.

En ten tweede: wanneer we eindelijk bij onze fire-and-forget-CheckHistoryAndRoaming-coroutine aankomen, is het eerste wat we doen een sterke referentie naar this vastleggen, om te garanderen dat de pagina HistoryAndRoaming ten minste in leven blijft totdat de coroutine is voltooid. Zie Sterke en zwakke verwijzingen in C++/WinRT voor meer informatie over beide aspecten die zojuist zijn beschreven.

We vinden nog een aandachtspunt bij het overzetten van CheckHistoryAndRoaming. Het bevat code voor het bijwerken van de gebruikersinterface; We moeten er dus zeker van zijn dat we dat doen in de hoofd-UI-thread. De thread die in eerste instantie een gebeurtenishandler aanroept, is de hoofdthread van de gebruikersinterface. Maar meestal kan een asynchrone methode elke willekeurige thread uitvoeren en/of hervatten. In C# is de oplossing om werk naar de UI-thread te verzenden. In C++/WinRT kunnen we de functie winrt::resume_foreground samen met de this van de aanwijzer gebruiken om de coroutine te onderbreken en onmiddellijk te hervatten op de hoofd-UI-thread.

De relevante expressie is co_await winrt::resume_foreground(DispatcherQueue());. De kortere versie wordt gerealiseerd met dank aan een conversieoperator die wordt geleverd door C++/WinRT.

Het overzetten van de rest van deze XAML-pagina levert niets nieuws op om te bespreken. Vergeet niet om de dummy ClickHandler-functie te verwijderen en om de XAML-markering te kopiëren.

OtherScenarios

U kunt OtherScenarios.xaml en OtherScenarios.xaml.cs overzetten met behulp van technieken die we al hebben behandeld.

Conclusion

Hopelijk heeft deze handleiding u voorzien van voldoende informatie en technieken om nu zelf uw eigen C#-toepassingen over te zetten naar C++/WinRT. Als u een vernieuwingsfunctie gebruikt, kunt u teruggaan naar de versies vóór (C#) en na (C++/WinRT) van de broncode in het Klembord-voorbeeld en deze naast elkaar vergelijken om de correspondentie te zien.