Conversione dell'esempio Clipboard da C# a C++/WinRT - Case study

Questo argomento presentata un case study sulla conversione di uno degli esempi di app della piattaforma UWP (Universal Windows Platform) da C# a C++/WinRT. Per esercitarti e acquisire esperienza nella conversione, segui la procedura dettagliata e prova tu stesso a eseguire la conversione dell'esempio.

Per un catalogo completo dei dettagli tecnici relativi alla conversione a C++/WinRT da C#, vedere l'argomento complementare Passare a C++/WinRT da C#.

Breve introduzione ai file di codice sorgente C# e C++

In un progetto C#, i file di codice sorgente sono principalmente file .cs. Quando inizierai a utilizzare il linguaggio C++, noterai che dovrai acquisire familiarità con altri tipi di file di codice sorgente. Ciò è dovuto alla differenza tra i compilatori, al modo in cui viene riutilizzato il codice sorgente C++ e alle nozioni di dichiarazione e definizione di un tipo e delle relative funzioni (metodi).

Una dichiarazione di funzione descrive solo la firma della funzione, ovvero il tipo restituito, il nome, nonché i nomi e i tipi di parametro. Una definizione di funzione include il corpo della funzione, ovvero la relativa implementazione.

Per quanto riguarda i tipi, questi concetti sono leggermente diversi. Un tipo viene definito specificandone il nome e semplicemente dichiarando (come minimo) tutte le relative funzioni membro (e gli altri membri). In pratica, puoi definire un tipo anche se non definisci le relative funzioni membro.

  • I file di codice sorgente C++ comuni sono file .h (dot aitch) .cpp. Un file .h è un file di intestazione e definisce uno o più tipi. Anche se puoi definire funzioni membro in un'intestazione, questa operazione viene in genere eseguita tramite un file .cpp. Pertanto, per un ipotetico tipo C++ MyClass, definirai MyClass in MyClass.h e le relative funzioni membro in MyClass.cpp. Per consentire ad altri sviluppatori di riutilizzare le classi, è sufficiente condividere i file .h e il codice dell'oggetto. È opportuno mantenere segreti i file .cpp, perché l'implementazione è una tua proprietà intellettuale.
  • Intestazione precompilata (pch.h). Nell'applicazione è in genere incluso un set di file di intestazione che non viene cambiato spesso. Di conseguenza, invece di elaborare il contenuto di tale set di intestazioni a ogni compilazione, puoi aggregare le intestazioni in un solo file, compilarlo una volta e quindi usare l'output di questo passaggio ogni volta che esegui la compilazione. Questa operazione viene eseguita tramite un file di intestazione precompilata, in genere denominato pch.h.
  • File .idl. Questi file contengono il linguaggio di definizione dell'interfaccia (IDL, Interface Definition Language). È possibile considerare i file IDL come file di intestazione per i tipi di Windows Runtime. Per altre informazioni su IDL, vedi la sezione IDL per il tipo MainPage.

Scaricare e testare l'esempio Clipboard

Visita la pagina Web Esempio Clipboard e fai clic su Scarica ZIP. Decomprimi il file scaricato e osserva la struttura di cartelle.

  • La versione C# del codice sorgente di esempio è contenuta nella cartella denominata cs.
  • La versione C++/WinRT del codice sorgente di esempio è contenuta nella cartella denominata cppwinrt.
  • Gli altri file, usati sia dalla versione C# che dalla versione C++/WinRT, sono reperibili nelle cartelle shared e SharedContent.

La procedura dettagliata in questo argomento illustra come puoi ricreare la versione C++/WinRT dell'esempio Clipboard convertendola dal codice sorgente C#. Potrai così vedere come sia possibile convertire i progetti C# in C++/WinRT.

Per avere un'idea del funzionamento dell'esempio, apri la soluzione C# (\Clipboard_sample\cs\Clipboard.sln), modifica la configurazione in base alle tue esigenze (ad esempio, in x64), compila ed esegui. L'interfaccia utente dell'esempio illustra le varie funzionalità, una alla volta.

Suggerimento

La cartella radice dell'esempio scaricato potrebbe essere denominata Clipboard anziché Clipboard_sample. Tuttavia, continueremo a fare riferimento a tale cartella come Clipboard_sample per distinguerla dalla versione C++/WinRT che verrà creata in un passaggio successivo.

Creare un'app vuota (C++/WinRT) denominata Clipboard

Nota

Per informazioni sull'installazione e sull'uso dell'Estensione C++/WinRT per Visual Studio (VSIX) e del pacchetto NuGet, che insieme forniscono il modello di progetto e il supporto della compilazione, vedi Supporto di Visual Studio per C++/WinRT.

Inizia il processo di conversione creando un nuovo progetto C++/WinRT in Microsoft Visual Studio. Crea un nuovo progetto usando il modello App vuota (C++/WinRT). Imposta il nome su Clipboard e, per fare in modo che la struttura di cartelle corrisponda a quella della procedura dettagliata, verifica che l'opzione Inserisci soluzione e progetto nella stessa directory sia deselezionata.

Per ottenere una baseline, verifica che questo nuovo progetto vuoto venga compilato ed eseguito.

Package.appxmanifest e file di asset

Se non è necessario installare side-by-side nello stesso computer le versioni C# e C++/WinRT dell'esempio, i file di origine del manifesto del pacchetto dell'app (Package.appxmanifest) possono essere identici. In tal caso devi solo copiare Package.appxmanifest dal progetto C# al progetto C++/WinRT.

Perché le due versioni dell'esempio possano coesistere, devono avere identificatori diversi. In tal caso nel progetto C++/WinRT apri il file Package.appxmanifest in un editor XML e prendi nota di questi tre valori.

  • Nell'elemento /Package/Identity prendi nota del valore dell'attributo Name. È il nome del pacchetto. Per un progetto appena creato, il progetto assegnerà un GUID univoco come valore iniziale.
  • Nell'elemento /Package/Applications/Application prendere nota del valore dell'attributo Id. È l'ID applicazione.
  • Nell'elemento /Package/mp:PhoneIdentity prendi nota del valore dell'attributo PhoneProductId. Anche in questo caso, per un progetto appena creato, questo valore verrà impostato sullo stesso GUID su cui è impostato il nome del pacchetto.

Copia quindi Package.appxmanifest dal progetto C# al progetto C++/WinRT. Infine puoi ripristinare i tre valori di cui hai preso nota oppure puoi modificare i valori copiati per renderli univoci e/o appropriati per l'applicazione e per l'organizzazione (come faresti normalmente per un nuovo progetto). In questo caso, ad esempio, invece di ripristinare il valore del nome del pacchetto, puoi limitarti a modificare il valore copiato Microsoft.SDKSamples.Clipboard.CS sostituendolo con Microsoft.SDKSamples.Clipboard.CppWinRT e lasciare l'ID dell'applicazione impostato su App. Finché il nome del pacchetto o l'ID dell'applicazione è diverso, le due applicazioni avranno un ID modello utente applicazione (AUMID) diverso. Questo è ciò che serve per l'installazione affiancata di due app nello stesso computer.

Per questa procedura dettagliata, è utile apportare alcune altre modifiche in Package.appxmanifest. Sono presenti tre occorrenze della stringa Clipboard C# Sample. Sostituiscile con Clipboard C++/WinRT Sample.

Nel progetto C++/WinRT il file Package.appxmanifest e il progetto ora non sono sincronizzati con i file di asset a cui fanno riferimento. Per risolvere il problema, rimuovi prima di tutto gli asset dal progetto C++/WinRT selezionando tutti i file nella cartella Assets (in Esplora soluzioni in Visual Studio) e rimuovendoli (scegli Elimina nella finestra di dialogo).

Il progetto C# fa riferimento ai file di asset da una cartella condivisa. Puoi eseguire la stessa operazione nel progetto C++/WinRT oppure puoi copiare i file come si farà in questa procedura dettagliata.

Passa alla cartella \Clipboard_sample\SharedContent\media. Selezionare i sette file inclusi nel progetto C# (microsoft-sdk.png, smalltile-sdk.png, splash-sdk.png, squaretile-sdk.png, storelogo-sdk.png, tile-sdk.png e windows-sdk.png), copiarli e incollarli nella cartella \Clipboard\Clipboard\Assets del nuovo progetto.

Fare clic con il pulsante destro del mouse sulla cartella Assets (in Esplora soluzioni nel progetto C++/WinRT) >Aggiungi>Elemento esistente... e passa a \Clipboard\Clipboard\Assets. In selezione file seleziona i sette file e fai clic su Aggiungi.

Package.appxmanifest ora è di nuovo sincronizzato con i file di asset del progetto.

MainPage, inclusa la funzionalità di configurazione dell'esempio

L'esempio Clipboard, come tutti gli esempi di app UWP (Universal Windows Platform) è costituito da una raccolta di scenari che l'utente può analizzare uno alla volta. La raccolta di scenari in un determinato esempio è configurata nel codice sorgente dell'esempio. Ogni scenario della raccolta è un elemento di dati che archivia un titolo, oltre al tipo della classe nel progetto che implementa lo scenario.

Nella versione C# dell'esempio, se osservi il file del codice sorgente SampleConfiguration.cs, vedrai due classi. La maggior parte della logica di configurazione si trova nella classe MainPage, che è una classe parziale. Forma infatti una classe completa se combinata con il markup in MainPage.xaml e con il codice imperativo in MainPage.xaml.cs. L'altra classe di questo file di codice sorgente è Scenario, con le proprietà Title e ClassType.

Nelle prossime sottosezioni si vedrà come convertire MainPage e Scenario.

IDL per il tipo MainPage

In questa parte iniziale della sezione si illustrerà brevemente il linguaggio di definizione dell'interfaccia (IDL) e si mostreranno i vantaggi offerti da questo linguaggio per la programmazione con C++/WinRT. IDL è un tipo di codice sorgente che descrive la superficie chiamabile di un tipo di Windows Runtime. La superficie chiamabile (o pubblica) di un tipo viene proiettata nel mondo esterno, in modo da consentirne l'utilizzo. Questa parte proiettata del tipo è in contrasto con la sua effettiva implementazione interna, che naturalmente non è chiamabile e non è pubblica. In IDL viene definita soltanto la parte proiettata.

Dopo aver creato codice sorgente IDL (all'interno di un file .idl), puoi compilare il codice IDL in file di metadati leggibili dal computer, noti anche come metadati di Windows. Questi file di metadati hanno estensione .winmd e di seguito sono riportati alcuni dei possibili utilizzi.

  • Un file .winmd può descrivere i tipi di Windows Runtime in un componente. Quando fai riferimento a un componente Windows Runtime (WRC) da un progetto di applicazione, quest'ultimo legge i metadati di Windows appartenenti al WRC (i metadati possono trovarsi in un file separato oppure essere inclusi nello stesso file del WRC), in modo da consentire l'utilizzo dei tipi del WRC dall'interno dell'applicazione.
  • Un file .winmd può descrivere i tipi di Windows Runtime in una parte dell'applicazione in modo che possano essere utilizzati da un'altra parte della stessa applicazione. È il caso, ad esempio, di un tipo di Windows Runtime utilizzato da una pagina XAML nella stessa app.
  • Per semplificare l'utilizzo dei tipi di Windows Runtime (incorporati o di terze parti), il sistema di compilazione di C++/WinRT usa i file .winmd per generare tipi di wrapper per rappresentare le parti proiettate dei tipi di Windows Runtime.
  • Per semplificare l'implementazione dei tipi di Windows Runtime personalizzati, il sistema di compilazione C++/WinRT converte il codice IDL in un file .winmd e quindi usa tale file per generare wrapper per la proiezione, nonché stub su cui basare l'implementazione. Informazioni più approfondite sugli stub verranno fornite più avanti in questo argomento.

La versione specifica del linguaggio IDL usata con C++/WinRT è Microsoft Interface Definition Language 3.0. Nella parte restante di questa sezione dell'argomento verrà esaminato in dettaglio il tipo MainPage C#. Si deciderà quali parti di questo tipo devono essere incluse nella proiezione del tipo MainPage C++/WinRT, ovvero nella sua superficie chiamabile o pubblica, e quali parti possono appartenere soltanto alla sua implementazione. Questa distinzione è importante perché, quando verrà creato il file IDL (nella sezione successiva), nel file saranno definite soltanto le parti chiamabili.

I file del codice sorgente C# che insieme implementano il tipo MainPage sono: MainPage.xaml (che tra verrà convertito a breve copiandolo), MainPage.xaml.cs e SampleConfiguration.cs.

Nella versione C++/WinRT il tipo MainPage verrà fattorizzato nei file del codice sorgente in modo simile. La logica in MainPage.xaml.cs verrà per la maggior parte convertita in MainPage.h e MainPage.cpp e la logica in SampleConfiguration.cs verrà convertita in SampleConfiguration.h e SampleConfiguration.cpp.

Le classi in un'applicazione UWP (Universal Windows Platform) C# sono ovviamente tipi di Windows Runtime. Quando tuttavia crei un tipo in un'applicazione C++/WinRT, puoi scegliere se deve essere un tipo di Windows Runtime o una normale classe/struct/enumerazione C++.

Qualsiasi pagina XAML nel progetto deve essere un tipo di Windows Runtime, quindi deve esserlo anche MainPage. Nel progetto C++/WinRT MainPage è già un tipo di Windows Runtime, quindi non è necessario modificarlo in tal senso. Si tratta in particolare di una classe di runtime.

  • Per informazioni più dettagliate sull'opportunità di creare o meno una classe di runtime per un determinato tipo, vedi l'argomento Creare API con C++/WinRT.
  • In C++/WinRT l'implementazione interna di una classe di runtime e le relative parti proiettate (pubbliche) esistono nella forma di due classi diverse, note rispettivamente come tipo di implementazione e tipo proiettato. Per altre informazioni su questi tipi, vedi l'argomento citato nel punto elenco precedente e in Utilizzare API con C++/WinRT.
  • Per informazioni sulla connessione tra classi di runtime e IDL (file .idl), puoi leggere e seguire l'argomento Controlli XAML, binding a una proprietà C++/WinRT, che illustra il processo di creazione di una nuova classe di runtime, in cui il primo passaggio consiste nell'aggiungere un nuovo elemento File Midl (.idl) al progetto.

Per MainPage, il file MainPage.idl necessario è in realtà già presente nel progetto C++/WinRT, poiché è già stato creato automaticamente dal modello di progetto. Più avanti in questa procedura dettagliata, si aggiungeranno altri file .idl al progetto.

A breve vedrai esattamente quale IDL è necessario aggiungere al file MainPage.idl esistente, ma prima è importante capire che cosa inserire nel file IDL e cosa no.

Per determinare quali membri di MainPage è necessario dichiarare in MainPage.idl (in modo che diventino parte della classe di runtime MainPage) e quali possono essere semplicemente membri del tipo di implementazione MainPage, si creerà un elenco di membri della classe MainPage C#. Per trovare tali membri, cerca in MainPage.xaml.cs e in SampleConfiguration.cs.

Troverai un totale di dodici campi e metodi protected e private e i membri public seguenti.

  • Il costruttore predefinito MainPage().
  • I campi statici Current e FEATURE_NAME.
  • Le proprietà IsClipboardContentChangedEnabled e Scenarios.
  • I metodi BuildClipboardFormatsOutputString, DisplayToast, EnableClipboardContentChangedNotifications e NotifyUser.

Poiché questi membri public sono candidati per la dichiarazione in MainPage.idl, verranno esaminati singolarmente per stabilire se devono far parte della classe di runtime MainPage o solo dell'implementazione.

  • Il costruttore predefinito MainPage(). Per una pagina XAML, è normale dichiarare un costruttore predefinito nel file IDL. In questo modo, il framework interfaccia utente XAML può attivare il tipo.
  • Il campo statico Current viene usato dalle singole pagine XAML dello scenario per accedere all'istanza di MainPage dell'applicazione. Poiché Current non viene usato per interagire con il framework XAML (né nelle unità di compilazione), è possibile riservarlo esclusivamente come membro del tipo di implementazione. Potresti scegliere di farlo con i tuoi progetti in casi come questo. Poiché tuttavia il campo è un'istanza del tipo proiettato, è logico dichiararlo nel file IDL ed è proprio ciò che si farà qui, semplificando anche il codice.
  • Il caso del campo FEATURE_NAME statico, accessibile nel tipo MainPage, è simile. Anche in questo caso, scegliendo di dichiararlo nel file IDL, il codice risulta più semplice.
  • La proprietà IsClipboardContentChangedEnabled viene usata solo nella classe OtherScenarios, quindi durante la conversione, per semplificare l'operazione, verrà impostata come campo privato della classe di runtime OtherScenarios e non verrà inserita nel file IDL.
  • La proprietà Scenarios è una raccolta di oggetti di tipo Scenario (citato in precedenza). Si parlerà del tipo Scenario nella sottosezione successiva, quindi anche la proprietà Scenarios per il momento non verrà presa in considerazione.
  • I metodi BuildClipboardFormatsOutputString, DisplayToast ed EnableClipboardContentChangedNotifications sono funzioni di utilità relative allo stato generale dell'esempio più che alla pagina principale. Durante la conversione, verrà quindi effettuato il refactoring di questi tre metodi in un nuovo tipo di utilità denominato SampleState (che non deve necessariamente essere un tipo di Windows Runtime). Per tale motivo, questi tre metodi non vengono inseriti nel file IDL.
  • Il metodo NotifyUser viene chiamato dalle singole pagine XAML dello scenario sull'istanza di MainPage restituita dal campo Current statico. Poiché (come si è già sottolineato) Current è un'istanza del tipo proiettato, è necessario dichiarare NotifyUser nel file IDL. NotifyUser accetta un parametro di tipo NotifyType, di cui si parlerà nella sottosezione successiva.

Anche tutti i membri a cui vuoi associare dati devono essere dichiarati in IDL, indipendentemente dal fatto che venga usato {x:Bind} o {Binding}. Per altre informazioni, vedi Data binding.

Si sta creando elenco dei membri da aggiungere e di quelli da non aggiungere al file MainPage.idl, ma restano ancora da prendere in esame la proprietà Scenarios e il tipo NotifyType, che saranno gli argomenti della sezione successiva.

IDL per i tipi Scenario e NotifyType

La classe Scenario è definita in SampleConfiguration.cs. È necessario decidere come convertire la classe in C++/WinRT. Per impostazione predefinita, è probabile che si creerebbe una normale struct C++, ma, se Scenario viene usato nei file binari o per interagire con il framework XAML, è necessario dichiararlo nel file IDL come tipo di Windows Runtime.

Se si esamina il codice sorgente C#, si può notare che Scenario viene usato in questo contesto.

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

Una raccolta di oggetti Scenario viene assegnata alla proprietà ItemsSource di ListBox (un controllo di elementi). Poiché è necessario che Scenario interagisca con XAML, deve essere un tipo di Windows Runtime. quindi deve essere definito nel file IDL. In seguito alla definizione del tipo Scenario nel file IDL, il sistema di compilazione C++/WinRT genera automaticamente una definizione del codice sorgente di Scenario in un file di intestazione in background. Il nome e la posizione di questo file non sono importanti per questa procedura dettagliata.

Come ricorderai, MainPage.Scenarios è una raccolta di oggetti Scenario, che, come si è detto, devono essere presenti nel file IDL. Per questo motivo, è necessario dichiarare nel file IDL anche MainPage.Scenarios stesso.

NotifyType è un elemento enum dichiarato nel file MainPage.xaml.cs di C#. Poiché NotifyType viene passato a un metodo appartenente alla classe di runtime MainPage, anche NotifyType deve essere un tipo di Windows Runtime e deve essere definito in MainPage.idl.

Ora aggiungiamo al file MainPage.idl i nuovi tipi e il nuovo membro di Mainpage che si è deciso di dichiarare nel file IDL. Contemporaneamente si rimuoveranno dal file IDL i membri segnaposto di Mainpage creati dal modello di progetto di Visual Studio.

Nel progetto C++/WinRT apri MainPage.idl e modificalo in modo che sia simile al listato seguente. Nota che una delle modifiche prevede la sostituzione del nome dello spazio dei nomi Clipboard con SDKTemplate. Se vuoi, puoi sostituire l'intero contenuto di MainPage.idl con il codice seguente. Nota anche che il nome di Scenario::ClassType viene sostituito con Scenario::ClassName.

// MainPage.idl
namespace SDKTemplate
{
    struct Scenario
    {
        String Title;
        Windows.UI.Xaml.Interop.TypeName ClassName;
    };

    enum NotifyType
    {
        StatusMessage,
        ErrorMessage
    };

    [default_interface]
    runtimeclass MainPage : Windows.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);
    };
}

Nota

Per altre informazioni sul contenuto di un file .idl in un progetto C++/WinRT, vedi Microsoft Interface Definition Language 3.0.

È possibile che nella procedura di conversione non si voglia o non si abbia bisogno di modificare il nome dello spazio dei nomi come è stato fatto sopra. In questo caso, il nome viene modificato perché lo spazio dei nomi predefinito del progetto C# da convertire è SDKTemplate, mentre il nome del progetto dell'assembly è Clipboard.

Nel corso della conversione eseguita in questa procedura dettagliata, ogni occorrenza del nome dello spazio dei nomi Clipboard nel codice sorgente verrà tuttavia sostituita con SDKTemplate. Lo spazio dei nomi Clipboard compare anche tra le proprietà del progetto C++/WinRT, quindi anche questa occorrenza verrà modificata ora.

In Visual Studio, per il progetto C++/WinRT, imposta la proprietà del progettoProprietà comuni>C++/WinRT>Spazio dei nomi radice sul valore SDKTemplate.

Salvare il file IDL e generare nuovamente i file stub

L'argomento Controlli XAML, binding a una proprietà C++/WinRT introduce la nozione di file stub e illustra una procedura dettagliata relativa all'uso di tali file. Si è già fatto riferimento agli stub in precedenza in questo argomento, quando è stato spiegato che il sistema di compilazione C++/WinRT converte il contenuto dei file .idl in metadati di Windows e quindi, da tali metadati, uno strumento denominato cppwinrt.exe genera stub su cui puoi basare la tua implementazione.

Ogni volta che aggiungi, rimuovi o modifichi elementi nel codice IDL ed esegui la compilazione, il sistema di compilazione aggiorna le implementazioni di stub nei file stub. Pertanto, ogni volta che modifichi il file IDL ed esegui la compilazione, è consigliabile visualizzare i file stub, copiare eventuali firme modificate e incollarle nel progetto. Di seguito verranno fornite informazioni più specifiche e saranno riportati esempi di come eseguire questa operazione. Il vantaggio di questa operazione consiste tuttavia nel fornire un metodo senza errori per conoscere in qualsiasi momento come devono essere la forma del tipo di implementazione e la firma dei relativi metodi.

A questo punto della procedura dettagliata, hai terminato di modificare il file MainPage.idl per il momento, quindi ora è opportuno salvarlo. Per il momento il progetto non verrà compilato completamente, ma l'esecuzione di una compilazione a questo punto è utile perché genera di nuovo i file stub per MainPage. Compilare quindi il progetto ora e ignorare eventuali errori di compilazione.

Per questo progetto C++/WinRT, i file stub vengono generati nella cartella \Clipboard\Clipboard\Generated Files\sources, Saranno disponibili al termine della compilazione parziale. La compilazione, come previsto, non verrà eseguita interamente, ma il passaggio che interessa, la generazione degli stub, avrà esito positivo. I file interessati sono MainPage.h e MainPage.cpp.

In questi due file stub vedrai nuove implementazioni di stub dei membri di MainPage che sono stati aggiunti al file IDL (ad esempio, Current e FEATURE_NAME). È consigliabile copiare queste implementazioni di stub nei file MainPage.h e MainPage.cpp già presenti nel progetto. Contemporaneamente, come è stato fatto con il file IDL, rimuoverai dai file esistenti i membri segnaposto di Mainpage presenti nel modello di progetto di Visual Studio (la proprietà fittizia MyProperty e il gestore dell'evento denominato ClickHandler).

In realtà l'unico membro della versione corrente di MainPage da conservare è il costruttore.

Dopo aver copiato i nuovi membri dai file stub, eliminato i membri non desiderati e aggiornato lo spazio dei nomi, i file MainPage.h e MainPage.cpp del progetto dovrebbero avere un aspetto simile a quello degli elenchi di codice seguenti. Si noti che esistono due tipi di MainPage: uno nello spazio dei nomi implementation e un altro nello spazio dei nomi factory_implementation. L'unica modifica apportata al tipo factory_implementation è l'aggiunta di SDKTemplate al relativo spazio dei nomi.

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

Per le stringhe, C# usa System.String. Per un esempio, vedi il metodo MainPage.NotifyUser. Nel file IDL dichiariamo una stringa con String e, quando lo strumento cppwinrt.exe genera il codice C++/WinRT, usa il tipo winrt::hstring. Ogni volta che incontrerai una stringa nel codice C#, la convertirai in winrt::hstring. Per altre informazioni, vedi Gestione delle stringhe in C++/WinRT.

Per una spiegazione dei parametri const& nelle firme del metodo, vedi Passaggio di parametri.

Aggiornare tutte le dichiarazioni o i riferimenti rimanenti dello spazio dei nomi e compilare

Prima di compilare il progetto C++/WinRT, trova tutte le dichiarazioni dello spazio dei nomi Clipboard (e i riferimenti a tale spazio dei nomi) e sostituiscili con SDKTemplate.

  • MainPage.xaml e App.xaml. Lo spazio dei nomi viene visualizzato nei valori degli attributi x:Class e xmlns:local.
  • App.idl.
  • App.h.
  • App.cpp. Esistono due direttive using namespace (cerca la sottostringa using namespace Clipboard) e due qualifiche del tipo MainPage (cerca Clipboard::MainPage), che è necessario modificare.

Poiché hai rimosso il gestore dell'evento da MainPage, passa anche a MainPage.xaml ed elimina l'elemento Button dal markup.

Salva tutti i file. Pulire le soluzione (Compilare>Pulire Clipboard) e quindi compilarla. Dopo aver seguito tutte le modifiche finora, esattamente come scritto, la compilazione dovrebbe avere esito positivo.

Implementa i membri di MainPage dichiarati nel file IDL

Costruttore Current e FEATURE_NAME

Ecco il codice pertinente (dal progetto C#) da convertire.

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

A breve riutilizzerai interamente MainPage.xaml (copiandolo). Per il momento (come sotto), si aggiungerà temporaneamente un elemento TextBlock, con il nome appropriato, nel file MainPage.xaml del progetto C++/WinRT.

FEATURE_NAME è un campo statico di MainPage (un campo const C# ha un comportamento essenzialmente statico), definito in SampleConfiguration.cs. Per C++/WinRT, invece che come campo (statico), lo imposterai come espressione C++/WinRT di una proprietà di sola lettura (statica). C++/WinRT esprime il getter di una proprietà come funzione che restituisce il valore di una proprietà e non accetta parametri (funzione di accesso). Il campo statico FEATURE_NAME C# diventa quindi la funzione di accesso statica FEATURE_NAME C++/WinRT (in questo caso, restituisce il valore letterale della stringa).

Per inciso, sarebbe possibile eseguire la stessa operazione se si convertisse una proprietà di sola lettura C#. Per una proprietà scrivibile C#, C++/WinRT esprime il setter di una proprietà come funzione void che accetta il valore della proprietà come parametro (mutatore). In entrambi i casi, se il campo o la proprietà C# è statica, lo sarà anche la funzione di accesso e/o il mutatore C++/WinRT.

Current è un campo statico (non costante) di MainPage. Anche in questo caso, verrà impostato come (espressione C++/WinRT di) una proprietà di sola lettura e, anche in questo caso, statica. Se FEATURE_NAME è costante, Current non lo è, perciò in C++/WinRT sarà necessario un campo sottostante che verrà restituito dalla funzione di accesso. Nel progetto C++/WinRT dichiariamo quindi in MainPage.h un campo privato statico denominato current, definiamo/inizializziamo current in MainPage.cpp (perché la durata di archiviazione è statica) e vi accediamo tramite una funzione di accesso statica pubblica denominata Current.

Il costruttore stesso esegue un paio di assegnazioni, che sono semplici da convertire.

Nel progetto C++/WinRT aggiungi un nuovo elemento Visual C++>Codice>File di C++ (.cpp) con il nome SampleConfiguration.cpp.

Modifica MainPage.xaml, MainPage.h, MainPage.cpp e SampleConfiguration.cpp in modo che corrispondano ai listati seguenti.

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

Assicurati anche di eliminare i corpi delle funzioni esistenti da MainPage.cpp per MainPage::Current() e MainPage::FEATURE_NAME(), perché ora stiamo definendo tali metodi altrove.

Come puoi vedere, MainPage::current viene dichiarato come di tipo SDKTemplate::MainPage, ovvero il tipo proiettato. Non è di tipo SDKTemplate::implementation::MainPage, che è il tipo di implementazione. Il tipo proiettato è pensato per essere utilizzato all'interno del progetto per l'interoperatività XAML o nei file binari. Il tipo di implementazione è quello che usi per implementare le strutture che hai esposto nel tipo proiettato. Poiché la dichiarazione di MainPage::current (in MainPage.h) viene visualizzata nello spazio dei nomi di implementazione (winrt::SDKTemplate::implementation), un elemento MainPage non qualificato avrebbe fatto riferimento al tipo di implementazione. Eseguiamo quindi la qualifica con SDKTemplate:: perché risulti chiaro che MainPage::current deve essere un'istanza del tipo proiettato winrt::SDKTemplate::MainPage.

Nel costruttore sono presenti alcuni punti correlati a MainPage::current = *this; che richiedono una spiegazione.

  • Quando usi il puntatore this all'interno di un membro del tipo di implementazione, il puntatore this è ovviamente un puntatore al tipo di implementazione.
  • Per convertire il puntatore this nel tipo proiettato corrispondente, dereferenzialo. Se generi il tipo di implementazione dal file IDL (come in questo caso), il tipo di implementazione ha un operatore di conversione che esegue la conversione nel tipo proiettato. Ecco perché in questo caso l'assegnazione funziona.

Per altre informazioni su tali dettagli, vedi Creazione di istanze e restituzione di interfacce e tipi di implementazione.

Nel costruttore è presente anche SampleTitle().Text(FEATURE_NAME());. La parte SampleTitle() è una chiamata a una funzione di accesso semplice denominata SampleTitle, che restituisce l'oggetto TextBlock aggiunto al file XAML. Ogni volta che si applica x:Name a un elemento XAML, il compilatore XAML genera automaticamente una funzione di accesso denominata per l'elemento. La parte .Text(...) chiama la funzione del mutatore Text sull'oggetto TextBlock restituito dalla funzione di accesso SampleTitle. FEATURE_NAME() chiama la funzione di accesso MainPage::FEATURE_NAME statica per restituire il valore letterale della stringa. Nel complesso, tale riga di codice imposta la proprietà Text dell'oggetto TextBlock denominato SampleTitle.

Tieni presente che, poiché le stringhe in Windows Runtime sono di tipo wide, per convertire un valore letterale di una stringa, aggiungiamo il prefisso di codifica in caratteri wide L. Sostituiamo quindi (ad esempio) "a string literal" con L"a string literal". Vedi anche Valori letterali di stringa di tipo wide.

Scenari

Ecco il codice C# pertinente da convertire.

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

Dall'analisi precedente, sappiamo che questa raccolta di oggetti Scenario viene visualizzata in un elemento ListBox. In C++/WinRT sono previsti limiti per il tipo di raccolta che è possibile assegnare alla proprietà ItemsSource di un controllo di elementi. La raccolta deve essere un vettore o un vettore osservabile e i suoi elementi devono essere uno dei seguenti:

Nel caso di IInspectable, se gli elementi non sono classi di runtime, tali elementi devono essere di tipo boxed o unboxed in e da IInspectable. Devono quindi essere tipi di Windows Runtime. Vedere Boxing e unboxing dei valori in IInspectable.

Anche se per questo case study non abbiamo impostato Scenario come classe di runtime, si tratta comunque di un'opzione ragionevole. In alcuni casi, durante la procedura di conversione reale, una classe di runtime sarà sicuramente il modo più corretto di procedere. Se ad esempio devi impostare il tipo di elemento come observable (vedi Controlli XAML, binding a una proprietà C++/WinRT) o se per qualche motivo l'elemento deve avere dei metodi, si tratta di qualcosa di più di un semplice set di membri dei dati.

Poiché in questa procedura dettagliata non useremo una classe di runtime per il tipo Scenario, è necessario prendere in considerazione il boxing. Se Scenario fosse una normale struct C++, non sarebbe possibile eseguirne il boxing. Poiché tuttavia Scenario è stato dichiarato come struct nel file IDL, è possibile eseguirne il boxing.

È possibile scegliere di eseguire il boxing di Scenario in anticipo o attendere il momento dell'assegnazione a ItemsSource ed eseguire il boxing in modalità JIT. Di seguito sono riportate alcune considerazioni relative a queste due opzioni.

  • Boxing in anticipo. Per questa opzione, il membro dati è una raccolta di IInspectable pronto per l'assegnazione all'interfaccia utente. Durante l'inizializzazione eseguiamo il boxing degli oggetti Scenario nel membro dati. Serve una sola copia della raccolta, ma per leggere i campi di un elemento è necessario eseguirne ogni volta l'unboxing.
  • Boxing JIT. Per questa opzione, il membro dati è una raccolta di Scenario. Quando è necessario eseguire l'assegnazione all'interfaccia utente, si esegue il boxing degli oggetti Scenario dal membro dati in una nuova raccolta di IInspectable. È possibile leggere i campi degli elementi del membro dati senza unboxing, ma sono necessarie due copie della raccolta.

Come puoi notare, per una raccolta di piccole dimensioni come questa, i pro e i contro si equivalgono, quindi, per questo case study, si userà l'opzione JIT.

Il membro scenarios è un campo di MainPage, definito e inizializzato in SampleConfiguration.cs, e Scenarios è una proprietà di sola lettura di MainPage, definita in MainPage.xaml.cs (e implementata per restituire semplicemente il campo scenarios). Nel progetto C++/WinRT si procederà in modo simile, ma i due membri saranno statici (dal momento che è necessaria una sola istanza nell'applicazione e quindi è possibile accedervi senza bisogno di un'istanza della classe). Li chiameremo rispettivamente scenariosInner e scenarios. Dichiariamo scenariosInner in MainPage.h e, poiché ha una durata di archiviazione statica, lo definiamo/inizializziamo in un file .cpp (in questo caso, SampleConfiguration.cpp).

Modifica MainPage.h e SampleConfiguration.cpp in modo che corrispondano ai listati seguenti.

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

Assicurati anche di eliminare il corpo della funzione esistente da MainPage.cpp per MainPage::scenarios(), perché tale metodo ora viene definito nel file di intestazione.

Come puoi vedere, in SampleConfiguration.cpp inizializziamo il membro dati statico scenariosInner chiamando una funzione helper C++/WinRT denominata winrt::single_threaded_observable_vector. Tale funzione crea automaticamente un nuovo oggetto raccolta di Windows Runtime e restituisce un'interfaccia IObservableVector. Poiché in questo esempio la raccolta non è observable (non è necessario che lo sia perché non aggiunge né rimuove elementi dopo l'inizializzazione), sarebbe stato possibile chiamare invece winrt::single_threaded_vector. Tale funzione restituisce la raccolta come interfaccia IVector.

Per altre informazioni sulle raccolte e sull'associazione, vedi Controlli di elementi XAML, binding a una raccolta C++/WinRT e Raccolte con C++/WinRT.

Il codice di inizializzazione appena aggiunto fa riferimento a tipi che non sono ancora nel progetto, ad esempio winrt::SDKTemplate::CopyText. Per risolvere questo problema, prosegui aggiungendo cinque nuove pagine XAML vuote al progetto.

Aggiungere cinque nuove pagine XAML vuote

Aggiungere un nuovo elemento Visual C++>Blank Page (C++/WinRT) al progetto. Assicurarsi che si tratti del modello di elemento Blank Page (C++/WinRT) e non di Pagina vuota. Assegna il nomeCopyText. La nuova pagina XAML è definita, come previsto, nello spazio dei nomi SDKTemplate.

Ripeti il processo precedente altre quattro volte e assegna alla pagine XAML i nomi CopyImage, CopyFiles, HistoryAndRoaming e OtherScenarios.

Ora puoi compilare di nuovo l'app se vuoi.

NotifyUser

Nel progetto C# troverai l'implementazione del metodo MainPage.NotifyUser in MainPage.xaml.cs. MainPage.NotifyUser ha una dipendenza da MainPage.UpdateStatus e tale metodo a sua volta ha dipendenze da elementi XAML non ancora convertiti. Per il momento ci limiteremo quindi a impostare come stub nel progetto C++/WinRT un metodo UpdateStatus che verrà convertito più avanti.

Ecco il codice C# pertinente da convertire.

// 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 usa l'enumerazione Windows.UI.Core.CoreDispatcherPriority. In C++/WinRT, ogni volta che vuoi usare un tipo da uno spazio dei nomi Windows, devi includere il file di intestazione dello spazio dei nomi Windows C++/WinRT corrispondente. Per altre informazioni, vedi Introduzione a C++/WinRT. In questo caso, come è possibile vedere nel listato di codice seguente, l'intestazione è winrt/Windows.UI.Core.h, che verrà inclusa in pch.h.

UpdateStatus è privato, quindi verrà impostato come metodo privato nel tipo di implementazione MainPage. Poiché UpdateStatus non deve essere chiamato sulla classe di runtime, non verrà dichiarato nel file IDL.

Dopo la conversione di MainPage.NotifyUser e l'impostazione come stub di MainPage.UpdateStatus, ecco che cosa si ottiene nel progetto C++/WinRT. Dopo questo listato di codice verranno esaminati alcuni dettagli.

// pch.h
...
#include <winrt/Windows.UI.Core.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 (Dispatcher().HasThreadAccess())
    {
        UpdateStatus(strMessage, type);
    }
    else
    {
        Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [strMessage, type, this]()
            {
                UpdateStatus(strMessage, type);
            });
    }
}
void MainPage::UpdateStatus(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
    throw hresult_not_implemented();
}
...

In C# puoi usare la notazione del punto per accedere alle proprietà annidate. Il tipo MainPage C# può quindi accedere alla proprietà Dispatcher con la sintassi Dispatcher. In C# si possono inserire altri punti in tale valore, ad esempio con la sintassi Dispatcher.HasThreadAccess. In C++/WinRT le proprietà vengono implementate come funzioni di accesso, quindi la sintassi differisce solo per il fatto che aggiungi le parentesi per ogni chiamata di funzione.

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

Quando la versione C# di NotifyUser chiama CoreDispatcher.RunAsync, implementa il delegato di callback asincrono come funzione lambda. La versione C++/WinRT si comporta nello stesso modo, ma la sintassi è leggermente diversa. In C++/WinRT si acquisiscono i due parametri da usare, oltre al puntatore this (perché chiameremo una funzione membro). Per altre informazioni sull'implementazione dei delegati come funzioni lambda e per esempi di codice, vedi l'argomento Gestire eventi mediante i delegati in C++/WinRT. In questo caso specifico, si può anche ignorare la parte var task =. Non è attesa la restituzione dell'oggetto asincrono, quindi non è necessario archiviarlo.

Implementare i membri rimanenti di MainPage

Creiamo un elenco completo dei membri di MainPage (implementati in MainPage.xaml.cs e in SampleConfiguration.cs) per verificare quali sono stati convertiti fino a questo momento e quali devono ancora essere convertiti.

Membro Accesso Stato
Costruttore MainPage public Convertito
Proprietà Current public Convertito
Proprietà FEATURE_NAME public Convertito
Proprietà IsClipboardContentChangedEnabled public Non avviato
Proprietà Scenarios public Convertito
Metodo BuildClipboardFormatsOutputString public Non avviato
Metodo DisplayToast public Non avviato
Metodo EnableClipboardContentChangedNotifications public Non avviato
Metodo NotifyUser public Convertito
Metodo OnNavigatedTo protected Non avviato
Campo isApplicationWindowActive private Non avviato
Campo needToPrintClipboardFormat private Non avviato
Campo scenarios private Convertito
Metodo Button_Click private Non avviato
Metodo DisplayChangedFormats private Non avviato
Metodo Footer_Click private Non avviato
Metodo HandleClipboardChanged private Non avviato
Metodo OnClipboardChanged private Non avviato
Metodo OnWindowActivated private Non avviato
Metodo ScenarioControl_SelectionChanged private Non avviato
Metodo UpdateStatus private Impostato come stub

Nelle sottosezioni successive si parlerà dei membri non ancora convertiti.

Nota

Di volta in volta si incontreranno nel codice sorgente riferimenti a elementi dell'interfaccia utente nel markup XAML (in MainPage.xaml). In questi casi, si aggiungeranno al codice XAML semplici elementi segnaposto come soluzione temporanea. In questo modo, il progetto continuerà a essere compilato dopo ogni sottosezione. L'alternativa è risolvere i riferimenti copiando ora l'intero contenuto di MainPage.xaml dal progetto C# al progetto C++/WinRT, ma in tal caso, poiché passerà molto tempo prima di potersi fermare ed eseguire una nuova compilazione, si rischierebbe di ignorare eventuali errori di digitazione o di altro tipo.

Solo dopo aver completato la conversione del codice imperativo per la classe MainPage saremo, in grado di copiare il contenuto del file XAML con la certezza che il progetto continuerà a essere compilato.

IsClipboardContentChangedEnabled

Si tratta di una proprietà C# get-set la cui impostazione predefinita è false. È un membro di MainPage ed è definita in SampleConfiguration.cs.

Per C++/WinRT sono necessari una funzione di accesso, una funzione mutatore e un membro dati sottostante come campo. Poiché IsClipboardContentChangedEnabled rappresenta lo stato di uno degli scenari dell'esempio e non lo stato di MainPage, creeremo i nuovi membri in un nuovo tipo di utilità denominato SampleState, che verrà implementata nel file di codice sorgente SampleConfiguration.cpp e i membri saranno static (dal momento che è necessaria una sola istanza nell'applicazione e quindi è possibile accedervi senza bisogno di un'istanza della classe).

Per trasferire SampleConfiguration.cpp nel progetto C++/WinRT, aggiungi un nuovo elemento Visual C++>Codice>File di intestazione (.h) con il nome SampleConfiguration.h. Modifica SampleConfiguration.h e SampleConfiguration.cpp in modo che corrispondano ai listati seguenti.

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

Ancora una volta è necessario definire un campo con risorsa di archiviazione static (ad esempio, SampleState::isClipboardContentChangedEnabled) nell'applicazione e un file .cpp è l'ideale (in questo caso, SampleConfiguration.cpp).

BuildClipboardFormatsOutputString

Questo metodo è un membro pubblico di MainPage ed è definito 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 BuildClipboardFormatsOutputString sarà un metodo statico pubblico di SampleState. È possibile impostarlo come static perché non accede a nessun membro dell'istanza.

Per usare i tipi Clipboard e DataPackageView in C++/WinRT, dovremo includere il file di intestazione dello spazio dei nomi Windows C++/WinRT winrt/Windows.ApplicationModel.DataTransfer.h.

In C# la proprietà DataPackageView.AvailableFormats è un elemento IReadOnlyList, quindi è possibile accedere alla sua proprietà Count. In C++/WinRT la funzione di accesso DataPackageView::AvailableFormats restituisce un elemento IVectorView, che ha una funzione di accesso Size che è possibile chiamare.

Per convertire l'uso del tipo System.Text.StringBuilder C#, useremo il tipo C++ standard std::wostringstream. Questo tipo è un flusso di output per le stringhe wide e per usarlo, è necessario includere il file di intestazione sstream. Invece di usare un metodo Append come con StringBuilder, usi l'operatore di inserimento (<<) con un flusso di output, ad esempio wostringstream. Per altre informazioni, vedi Programmazione di iostream e Formattazione di stringhe C++/WinRT.

Il codice C# costruisce un elemento StringBuilder con la parola chiave new. In C# gli oggetti sono, per impostazione predefinita, tipi di riferimento dichiarati nell'heap con new. Nel linguaggio C++ standard moderno gli oggetti sono, per impostazione predefinita, tipi di valori dichiarati nello stack (senza usare new). Convertiamo quindi StringBuilder output = new StringBuilder(); in C++/WinRT semplicemente come std::wostringstream output;.

La parola chiave var C# chiede al compilatore di dedurre un tipo. Converti var ad auto in C++/WinRT. Tuttavia, in C++/WinRT esistono casi in cui (per evitare copie) vuoi un riferimento a un tipo dedotto ed esprimi un riferimento lvalue a un tipo dedotto con auto&. Esistono anche casi in cui vuoi un tipo speciale di riferimento che esegua correttamente il binding, indipendentemente dal fatto che venga inizializzato con lvalue o con rvalue. Puoi esprimerlo con auto&&. Questa è la forma che vedi usata nel ciclo for nel codice convertito riportato di seguito. Per un'introduzione agli lvalue e rvalue, vedi Categorie di valori e riferimenti.

Modifica pch.h, SampleConfiguration.h e SampleConfiguration.cpp in modo che corrispondano ai listati seguenti.

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

Nota

La sintassi nella riga di codice DataPackageView clipboardContent{ Clipboard::GetContent() }; usa una funzionalità del linguaggio C++ standard moderno, denominata inizializzazione uniforme, con il suo uso caratteristico di parentesi graffe al posto di un segno =. Questa sintassi rende chiaro che sia in corso un'inizializzazione anziché un'assegnazione. Se preferisci la forma di sintassi che sembra un'assegnazione (anche se in realtà non lo è), puoi sostituire la sintassi precedente con il codice DataPackageView clipboardContent = Clipboard::GetContent(); equivalente. È tuttavia consigliabile acquisire familiarità con entrambe le modalità di espressione dell'inizializzazione, poiché è probabile che vengano spesso usate tutte e due nel codice che trovi.

DisplayToast

DisplayToast è un metodo statico pubblico della classe MainPage C#, che viene definito in SampleConfiguration.cs. In C++/WinRT si creerà un metodo statico pubblico di SampleState.

Abbiamo già visto la maggior parte dei dettagli e delle tecniche rilevanti per la conversione di questo metodo. Un nuovo elemento da osservare è la conversione di un valore letterale stringa verbatim C# (@) in un valore letterale stringa non elaborato C++ standard (LR).

Inoltre quando fai riferimento ai tipi ToastNotification e XmlDocument in C++/WinRT, puoi qualificarli in base al nome dello spazio dei nomi oppure modificare SampleConfiguration.cpp e aggiungere le direttive using namespace, come nell'esempio seguente.

using namespace Windows::UI::Notifications;

Hai la stessa possibilità quando fai riferimento al tipo XmlDocument o a qualsiasi altro tipo di Windows Runtime.

A eccezione di questi elementi, è sufficiente seguire le stesse indicazioni di prima anche per i passaggi seguenti.

  • Dichiara il metodo in SampleConfiguration.h e definiscilo in SampleConfiguration.cpp.
  • Modifica pch.h per includere i file di intestazione dello spazio dei nomi Windows C++/WinRT necessari.
  • Costruisci gli oggetti C++/WinRT nello stack, non nell'heap.
  • Sostituisci le chiamate alle funzioni di accesso get delle proprietà con la sintassi di chiamata alle funzioni (()).

Dimenticare di includere i file di intestazione dello spazio dei nomi Windows C++/WinRT necessari è molto spesso causa di errori del compilatore/linker. Per ulteriori informazioni su un possibile errore, vedere C3779: Perché il compilatore restituisce un errore "consume_Something: funzione che restituisce "auto" non può essere usata prima di essere definita?.

Per continuare con la procedura dettagliata e convertire da solo DisplayToast, è possibile confrontare i risultati con il codice nella versione C++/WinRT nel file ZIP del codice sorgente dell'esempio Clipboard scaricato.

EnableClipboardContentChangedNotifications

EnableClipboardContentChangedNotifications è un metodo statico pubblico della classe MainPage C# ed è definito 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 si creerà un metodo statico pubblico di SampleState.

In C# usi la sintassi degli operatori += e -= per registrare e revocare i delegati di gestione degli eventi. In C++/WinRT puoi scegliere tra più opzioni sintattiche per registrare/revocare un delegato, come illustrato in Gestire eventi mediante i delegati in C++/WinRT. Tuttavia, in genere esegui la registrazione e la revoca con chiamate a una coppia di funzioni specificate per l'evento. Per la registrazione, passi il delegato alla funzione di registrazione e in cambio recuperi un token di revoca (winrt::event_token). Per la revoca, passi questo token alla funzione di revoca. In questo caso, il gestore è statico e (come puoi vedere nel listato di codice seguente) la sintassi della chiamata alla funzione è semplice.

Token simili vengono effettivamente usati, in background, in C#. Il linguaggio, tuttavia, rende implicito il dettaglio. C++/WinRT lo rende esplicito.

Il tipo object appare nelle firme dei gestori di eventi C#. Nel linguaggio C# object è un alias per il tipo System.Object .NET. L'equivalente in C++/WinRT è winrt::Windows::Foundation::IInspectable. Vedrai quindi IInspectable nei gestori di eventi C++/WinRT.

Modifica SampleConfiguration.h e SampleConfiguration.cpp in modo che corrispondano ai listati seguenti.

// 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, Windows::UI::Core::WindowActivatedEventArgs const& e);
...

// SampleConfiguration.cpp
...
using namespace Windows::Foundation;
using namespace Windows::UI::Core;
using namespace Windows::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){}

Per il momento lascia i delegati di gestione degli eventi (OnClipboardChanged e OnWindowActivated) come stub. Sono già presenti nell'elenco dei membri da convertire, quindi ce ne occuperemo nelle sottosezioni successive.

OnNavigatedTo

OnNavigatedTo è un metodo protetto della classe C# MainPage ed è definito in MainPage.xaml.cs. Eccolo, con l'elemento ListBox XAML a cui fa riferimento.

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

È un metodo importante e interessante, perché consente di assegnare la raccolta di oggetti Scenario all'interfaccia utente. Il codice C# compila una classe System.Collections.Generic.List di oggetti Scenario e la assegna alla proprietà ItemsSource di ListBox (un controllo di elementi). In C# usiamo l'interpolazione di stringhe per compilare il titolo per ogni oggetto Scenario. Nota l'uso del carattere speciale $.

In C++/WinRT imposteremo OnNavigatedTo come metodo pubblico di MainPage e aggiungeremo un elemento ListBox al codice XAML per eseguire correttamente la compilazione. Dopo il listato di codice, verranno esaminati alcuni dettagli.

<!-- MainPage.xaml -->
...
<StackPanel ...>
    ...
    <ListBox x:Name="ScenarioControl" />
</StackPanel>
...
// MainPage.h
...
void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
...

// MainPage.cpp
...
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::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);
    }
}
...

Chiamiamo ancora la funzione winrt::single_threaded_observable_vector, ma questa volta per creare una raccolta di IInspectable, che abbiamo visto quando è stata presa la decisione di eseguire il boxing degli oggetti Scenario in modalità JIT.

Inoltre, invece dell'uso dell'interpolazione di stringhe da parte di C# qui, viene usata una combinazione della funzione to_hstring e dell'operatore di concatenazione di winrt::hstring.

isApplicationWindowActive

In C# isApplicationWindowActive è un semplice campo bool privato appartenente alla classe MainPage, definito in SampleConfiguration.cs. Il valore predefinito è false. In C++/WinRT verrà impostato come campo statico privato di SampleState (per i motivi già visti) nei file SampleConfiguration.h e SampleConfiguration.cpp, con la stessa impostazione predefinita.

Abbiamo già visto come dichiarare, definire e inizializzare un campo statico. Se necessario, ricontrolla la procedura seguita prima per il campo isClipboardContentChangedEnabled e ripetila per isApplicationWindowActive.

needToPrintClipboardFormat

Il modello è uguale a quello di isApplicationWindowActive. Vedi l'intestazione subito prima di questa.

Button_Click

Button_Click è un metodo privato (di gestione degli eventi) della classe MainPage C#, definito in MainPage.xaml.cs. Di seguito è illustrato questo metodo, insieme a SplitView XAML a cui fa riferimento e a ToggleButton che lo registra.

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

Questo è il codice equivalente, convertito in C++/WinRT. Nota che nella versione C++/WinRT il gestore dell'evento è public. Come puoi vedere, lo dichiari prima delle dichiarazioni private:. Infatti, un gestore eventi registrato nel markup XAML come questo, deve essere public in C++/WinRT per consentire al markup XAML di accedervi. D'altra parte, se si registra un gestore eventi nel codice imperativo, come è stato fatto in precedenza in MainPage::EnableClipboardContentChangedNotifications, non è necessario che il gestore eventi sia public.

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

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

DisplayChangedFormats

In C# DisplayChangedFormats è un metodo privato appartenente alla classe MainPage, definito in SampleConfiguration.cs.

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

In C++/WinRT verrà impostato come campo statico privato di SampleState (non accede a nessun membro dell'istanza), nei file SampleConfiguration.h e SampleConfiguration.cpp. Il codice C# per questo metodo non usa System.Text.StringBuilder, ma la formattazione delle stringhe è tale che, per la versione C++/WinRT, è consigliabile usare std::wostringstream.

Invece della proprietà statica System.Environment.NewLine, usata nel codice C#, inseriremo il carattere di nuova riga C++ standard std::endl nel flusso di output.

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

La progettazione della versione C++/WinRT precedente presenta una piccola inefficienza. Prima creiamo un elemento std::wostringstream, ma chiamiamo anche il metodo BuildClipboardFormatsOutputString, che abbiamo convertito in precedenza. Tale metodo crea il proprio std::wostringstream, quindi trasforma il flusso in winrt::hstring e lo restituisce. Chiamiamo la funzione hstring::c_str per trasformare nuovamente tale hstring restituita in una stringa di tipo C e quindi la inseriamo nel flusso. Sarebbe più efficiente creare un solo std::wostringstream e passarlo (o passare un riferimento) in modo che i metodi possano inserirvi direttamente le stringhe.

Ed è proprio ciò che accade nella versione C++/WinRT del codice sorgente dell'esempio Clipboard (nel file ZIP scaricato). In tale codice sorgente è presente un nuovo metodo statico privato denominato SampleState::AddClipboardFormatsOutputString, che usa un riferimento a un flusso di output. Viene quindi effettuato il refactoring dei metodi SampleState::DisplayChangedFormats e SampleState::BuildClipboardFormatsOutputString per chiamare il nuovo metodo. Dal punto di vista funzionale equivale ai listati di codice di questo argomento, ma è più efficiente.

Footer_Click è un gestore di eventi asincrono appartenente alla classe MainPage C#, definito in MainPage.xaml.cs. Il listato di codice riportato di seguito dal punto di vista funzionale equivale al metodo del codice sorgente scaricato, ma qui è stato decompresso in quattro righe, per capire meglio come funziona e quindi decidere come convertirlo.

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

Anche se tecnicamente il metodo è asincrono, non esegue operazioni dopo await, quindi non necessita di await (né della parola chiave async). È probabile che li usi per evitare il messaggio di IntelliSense in Visual Studio.

Anche il metodo C++/WinRT equivalente sarà asincrono (perché chiama Launcher.LaunchUriAsync), ma non necessita dell'istruzione co_await né deve restituire un oggetto asincrono. Per informazioni su co_await e sugli oggetti asincroni, vedi Concorrenza e operazioni asincrone con C++/WinRT.

A questo punto si parlerà del funzionamento del metodo. Poiché si tratta di un gestore per l'evento Click di un HyperlinkButton, l'oggetto denominato sender è effettivamente un HyperlinkButton. La conversione dei tipi è quindi sicura. In alternativa, potremmo esprimere questa conversione come sender as HyperlinkButton. Ora si recupererà il valore della proprietà Tag. Se osservi il markup XAML nel progetto C#, vedrai che è impostata su una stringa che rappresenta un URL Web. Anche se la proprietà FrameworkElement.Tag (HyperlinkButton è un FrameworkElement) è di tipo object, in C# è possibile convertirla in stringa con Object.ToString. Dalla stringa risultante si costruisce un oggetto Uri. Infine, con l'aiuto della shell, si avvia un browser e si passa all'URL.

Ecco il metodo convertito in C++/WinRT (anche in questo caso, espanso per maggiore chiarezza), a cui segue una descrizione dei dettagli.

// pch.h
...
#include "winrt/Windows.System.h"
...

// MainPage.h
...
    void Footer_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
private:
...

// MainPage.cpp
...
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml::Controls;
...
void MainPage::Footer_Click(Windows::Foundation::IInspectable const& sender, Windows::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);
}

Come sempre, impostiamo come public il gestore dell'evento. Usiamo la funzione as sull'oggetto sender per convertirlo in HyperlinkButton. In C++/WinRT la proprietà Tag è un'interfaccia IInspectable (l'equivalente di Object), ma non esiste alcuna Tostring in IInspectable. È invece necessario eseguire l'unboxing di IInspectable in un valore scalare (in questo caso, una stringa). Per altre informazioni su boxing e unboxing, vedere ancora una volta Boxing e unboxing dei valori in IInspectable.

Le ultime due righe ripetono i modelli di conversione visti prima e sono molto simili a quelle della versione C#.

HandleClipboardChanged

Non c'è niente di nuovo per la conversione di questo metodo. È possibile confrontare le versioni C# e C++ /WinRT nel file ZIP del codice sorgente dell'esempio Clipboard scaricato.

OnClipboardChanged e OnWindowActivated

Fino a questo punto abbiamo solo stub vuoti per questi due gestori di eventi, ma convertirli è semplice e non serve aggiungere nulla.

ScenarioControl_SelectionChanged

È un altro gestore di eventi privato che appartiene alla classe MainPage C#, definito in MainPage.xaml.cs. In C++/WinRT lo imposteremo come pubblico e lo implementeremo in MainPage.h e MainPage.cpp.

Per questo metodo, saranno necessari MainPage::navigating, un campo booleano privato, inizializzato su false, e anche un Frame in MainPage.xaml, denominato ScenarioFrame. Tuttavia, oltre a questi dettagli, la conversione di questo metodo non rivela nuove tecniche.

Se, invece di eseguire la conversione manuale, si sta copiando il codice dalla versione C++/WinRT nel file ZIP del codice sorgente di esempio Clipboard scaricato, si vedrà un MainPage::NavigateTo in uso lì. Per il momento, è sufficiente eseguire il refactoring del contenuto di NavigateTo in ScenarioControl_SelectionChanged.

UpdateStatus

Fino a questo punto abbiamo solo uno stub per MainPage.UpdateStatus. La conversione dell'implementazione ancora una volta non prevede nulla di nuovo, se non che, mentre in C# possiamo confrontare string con String.Empty, in C++/WinRT chiamiamo invece la funzione winrt::hstring::empty. Un'altra differenza è che nullptr è l'equivalente C++ standard di null in C#.

Puoi eseguire il resto della conversione con le tecniche già analizzate. Ecco un elenco dei tipi di operazioni che è necessario eseguire prima che venga compilata la versione convertita di questo metodo.

  • In MainPage.xaml aggiungi un Border denominato StatusBorder.
  • In MainPage.xaml aggiungi un TextBlock denominato StatusBlock.
  • In MainPage.xaml aggiungi uno StackPanel denominato StatusPanel.
  • In pch.h aggiungi #include "winrt/Windows.UI.Xaml.Media.h".
  • In pch.h aggiungi #include "winrt/Windows.UI.Xaml.Automation.Peers.h".
  • In MainPage.cpp aggiungi using namespace winrt::Windows::UI::Xaml::Media;.
  • In MainPage.cpp aggiungi using namespace winrt::Windows::UI::Xaml::Automation::Peers;.

Copia il codice XAML e gli stili necessari per completare la conversione di MainPage

Per XAML, l'ideale è poter usare lo stesso markup XAML in un progetto C# e in uno C++/WinRT. L'esempio Clipboard è uno di questi casi.

Nel file Styles.xaml l'esempio Clipboard ha un ResourceDictionary XAML di stili, che vengono applicati ai pulsanti, ai menu e agli altri elementi dell'interfaccia utente dell'applicazione. La pagina Styles.xaml viene unita ad App.xaml. Si ottiene così il punto di partenza standard MainPage.xaml per l'interfaccia utente, di cui si è già parlato brevemente. Ora possiamo riutilizzare questi tre file .xaml, senza modifiche, nella versione C++/WinRT del progetto.

Come per i file di asset, puoi scegliere di fare riferimento agli stessi file XAML condivisi da più versioni dell'applicazione. In questa procedura dettagliata, solo per semplicità, copieremo i file nel progetto C++/WinRT e li aggiungeremo in questo modo.

Passa alla cartella \Clipboard_sample\SharedContent\xaml, seleziona e copia App.xaml e MainPage.xaml e quindi incolla i due file nella cartella \Clipboard\Clipboard nel progetto C++/WinRT, scegliendo di sostituire i file quando ti viene chiesto.

Nel progetto C++/WinRT in Visual Studio, fare clic su Mostra tutti i file per attivarlo. Aggiungere ora una nuova cartella, immediatamente sotto il nodo del progetto, denominata Styles. In Esplora file, passare alla cartella \Clipboard_sample\SharedContent\xaml, selezionare e copiare Styles.xaml e incollarlo nella cartella \Clipboard\Clipboard\Styles appena creata. Tornando a Esplora soluzioni nel progetto C++/WinRT, fare clic con il pulsante destro del mouse sulla cartella Styles>Aggiungi>Elemento esistente... e passa a \Clipboard\Clipboard\Styles. In selezione file seleziona Styles e fai clic su Aggiungi.

Aggiungi una nuova cartella al progetto C++/WinRT, immediatamente sotto il nodo del progetto, denominata Styles. Passa alla cartella \Clipboard_sample\SharedContent\xaml, seleziona e copia Styles.xaml e incollalo nella cartella \Clipboard\Clipboard\Styles nel progetto C++/WinRT. Fai clic con il pulsante destro del mouse sulla cartella Styles (in Esplora soluzioni nel progetto C++/WinRT) >Aggiungi>Elemento esistente... e passa a \Clipboard\Clipboard\Styles. In selezione file seleziona Styles e fai clic su Aggiungi.

Fare di nuovo clic su Mostra tutti i file per disattivarlo.

La conversione di MainPage a questo punto è terminata e, se hai seguito tutti i passaggi, il tuo progetto C++/WinRT ora verrà compilato ed eseguito.

Consolidare i file .idl

Oltre al punto di partenza MainPage.xaml standard per l'interfaccia utente, l'esempio Clipboard presenta altre cinque pagine XAML specifiche dello scenario, insieme ai relativi file code-behind corrispondenti. Verrà riutilizzato il markup XAML effettivo di tutte queste pagine, senza modifiche, nella versione C++/WinRT del progetto. Nelle successive sezioni principali verrà inoltre esaminato come eseguire la conversione del code-behind. Ma prima è opportuno parlare di IDL.

È possibile consolidare il file IDL per le classi di runtime in un singolo file IDL. Per informazioni su questo valore, vedere Factoring delle classi di runtime nei file Midl (.idl). Pertanto, di seguito il contenuto di CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idl, e OtherScenarios.idl verrà consolidato spostando tale codice IDL in un unico file denominato Project.idl (e quindi eliminando i file originari).

Contemporaneamente, sarà anche necessario rimuovere la proprietà fittizia generata automaticamente (Int32 MyProperty; e la relativa implementazione) da ognuno di questi cinque tipi di pagine XAML.

Per prima cosa, aggiungi un nuovo elemento File Midl (.idl) al progetto C++/WinRT. Assegna il nomeProject.idl. Sostituisci l'intero contenuto di Project.idl con il codice seguente.

// Project.idl
namespace SDKTemplate
{
    [default_interface]
    runtimeclass CopyFiles : Windows.UI.Xaml.Controls.Page
    {
        CopyFiles();
    }

    [default_interface]
    runtimeclass CopyImage : Windows.UI.Xaml.Controls.Page
    {
        CopyImage();
    }

    [default_interface]
    runtimeclass CopyText : Windows.UI.Xaml.Controls.Page
    {
        CopyText();
    }

    [default_interface]
    runtimeclass HistoryAndRoaming : Windows.UI.Xaml.Controls.Page
    {
        HistoryAndRoaming();
    }

    [default_interface]
    runtimeclass OtherScenarios : Windows.UI.Xaml.Controls.Page
    {
        OtherScenarios();
    }
}

Come si può notare, si tratta semplicemente di una copia del contenuto dei singoli file .idl, tutti all'interno di un unico spazio dei nomi e con MyProperty rimossa da ogni classe di runtime.

In Esplora soluzioni di Visual Studio seleziona tutti i file IDL originari (CopyFiles.idl, CopyImage.idl, CopyText.idl, HistoryAndRoaming.idl e OtherScenarios.idl) e quindi fai clic su Modifica>Rimuovi per rimuoverli (scegli Elimina nella finestra di dialogo).

Infine, anche per completare la rimozione di MyProperty nei file .h e .cpp per ciascuno degli stessi cinque tipi di pagine XAML, eliminare le dichiarazioni e le definizioni delle funzioni di accesso int32_t MyProperty() e mutatore void MyProperty(int32_t).

Per inciso, è sempre consigliabile che il nome dei file XAML corrisponda al nome della classe che tali file rappresentano. Se, ad esempio, un file di markup XAML contiene x:Class="MyNamespace.MyPage", il file deve essere denominato MyPage.xaml. Benché non si tratti di un requisito tecnico, il fatto di non doversi destreggiare tra nomi diversi per lo stesso artefatto renderà il progetto più comprensibile e gestibile e più facile da usare.

CopyFiles

Nel progetto C# il tipo di pagina XAML CopyFiles viene implementato nei file di codice sorgente CopyFiles.xaml e CopyFiles.xaml.cs. Ora è possibile esaminare i singoli membri di CopyFiles.

rootPage

Questo è un campo privato.

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

In C++/WinRT può essere definito e inizializzato come segue.

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

Anche in questo caso (come con MainPage::current), CopyFiles::rootPage viene dichiarato come se fosse di tipo SDKTemplate::MainPage, che è il tipo previsto, e non il tipo di implementazione.

CopyFiles (il costruttore)

Nel progetto C++/WinRT il tipo CopyFiles dispone già di un costruttore contenente il codice desiderato (semplicemente chiama InitializeComponent).

CopyButton_Click

Il metodo CopyButton_Click C# è un gestore eventi e dalla parola chiave async nella relativa firma è possibile comprendere che il metodo esegue operazioni asincrone. In C++/WinRT un metodo asincrono viene implementato come coroutine. Per un'introduzione alla concorrenza in C++/WinRT e per una descrizione di cosa sia una coroutine, vedi Concorrenza e operazioni asincrone con C++/WinRT.

È comune voler pianificare altre operazioni da eseguire dopo il completamento di una coroutine e, per questi casi, la coroutine restituisce un tipo di oggetto asincrono che può essere atteso e che facoltativamente segnala lo stato di avanzamento. Queste considerazioni, tuttavia, non si applicano in genere a un gestore eventi. Pertanto, quando disponi di un gestore eventi che esegue operazioni asincrone, puoi implementarlo come una coroutine che restituisce winrt::fire_and_forget. Per altre informazioni, vedi Attivare e poi dimenticare.

Anche se alla base di una coroutine di tipo fire-and-forget c'è il concetto che non è importante quando verrà completata, il lavoro prosegue comunque (o viene sospeso, in attesa della ripresa) in background. Puoi vedere dall'implementazione C# che CopyButton_Click dipende dal puntatore this (accede al membro dati di istanza rootPage). È pertanto necessario assicurarsi che il puntatore this (un puntatore a un oggetto CopyFiles) abbia una durata superiore a quella della coroutine CopyButton_Click. In una situazione analoga a questa applicazione di esempio, in cui l'utente si sposta tra le pagine dell'interfaccia utente, non è possibile controllare direttamente la durata di tali pagine. Se la pagina CopyFiles viene distrutta (spostandosi al di fuori di essa) mentre CopyButton_Click è ancora in fase di esecuzione in un thread in background, non sarà possibile accedere a rootPage in modo sicuro. Per rendere corretta la coroutine, è necessario ottenere un riferimento sicuro al puntatore this e mantenerlo per la durata della coroutine. Per altre informazioni, vedi Riferimenti sicuri e deboli in C++/WinRT.

Se osservi la versione C++/WinRT dell'esempio, in corrispondenza di CopyFiles::CopyButton_Click, noterai che ciò avviene con una semplice dichiarazione nello stack.

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

Verranno ora esaminati gli altri aspetti del codice convertito che sono degni di nota.

Nel codice viene creata un'istanza di un oggetto FileOpenPicker e due righe più avanti viene eseguito l'accesso alla proprietà FileTypeFilter di tale oggetto. Il tipo restituito della proprietà implementa un IVector di stringhe. Per quanto riguarda IVector, viene chiamato il metodo IVector<T>.ReplaceAll(T[]). L'aspetto interessante è il valore che viene passato al metodo, in cui è prevista una matrice. Di seguito è riportata la riga di codice.

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

Il valore che viene passato ({ L"*" }) è un elenco di inizializzatori C++ standard. In questo caso contiene un solo oggetto, ma un elenco di inizializzatori può contenere un numero qualsiasi di oggetti separati da virgole. Le parti di C++/WinRT che ti consentono di passare facilmente un elenco di inizializzatori a un metodo come questo sono illustrate in Elenchi di inizializzatori standard.

La parola chiave await C# viene convertita a co_await in C++/WinRT. Di seguito è riportato l'esempio tratto dal codice.

auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };

Considera quindi questa riga di codice C#.

dataPackage.SetStorageItems(storageItems);

C# è in grado di convertire in modo implicito l'elemento IReadOnlyList<StorageFile> rappresentato da storageItems nell'elemento IEnumerable<StorageItems> previsto da DataPackage.SetStorageItems. Tuttavia, in C++/WinRT è necessario convertire in modo esplicito da IVectorView<StorageFile> a IIterable<IStorageItem>. È quindi disponibile un altro esempio della funzione as in azione.

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

Dove viene usata la parola chiave null in C# (ad esempio, Clipboard.SetContentWithOptions(dataPackage, null)), viene usata nullptr in C++/WinRT (ad esempio, Clipboard::SetContentWithOptions(dataPackage, nullptr)).

PasteButton_Click

Si tratta di un altro gestore eventi sotto forma di coroutine di tipo fire-and-forget. Verranno ora esaminati gli aspetti del codice convertito che sono degni di nota.

Nella versione C# dell'esempio vengono intercettate le eccezioni con catch (Exception ex). Nel codice C++/WinRT convertito vedrai l'espressione catch (winrt::hresult_error const& ex). Per altre informazioni su winrt::hresult_error e su come gestirlo, vedi Gestione degli errori con C++/WinRT.

Un esempio di test che indica se un oggetto C# è null o no è if (storageItems != null). In C++/WinRT è possibile usare un operatore di conversione a bool, che esegue il test internamente a fronte di nullptr.

Di seguito è riportata una versione leggermente semplificata di un frammento di codice della versione C++/WinRT convertita dell'esempio.

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

La costruzione di un elemento std::wstring_view da un elemento winrt::hstring come questo illustra un'alternativa alla chiamata della funzione hstring::c_str (per trasformare winrt::hstring in una stringa di tipo C). Questa alternativa funziona grazie all'operatore di conversione a std::wstring_viewdi hstring.

Considera questo frammento di codice C#.

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

Per convertire la parola chiave as C# a C++/WinRT, finora è stata usata un paio di volte la funzione as. Se la conversione dei tipi ha esito negativo, questa funzione genera un'eccezione. Se vuoi però che la conversione restituisca nullptr quando l'esito è negativo (in modo da poter gestire tale condizione nel codice), possiamo usare la funzione try_as.

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

Copiare il codice XAML necessario per completare la conversione di CopyFiles

Ora è possibile selezionare l'intero contenuto del file CopyFiles.xaml dalla cartella shared del download del codice sorgente dell'esempio originale e incollarlo nel file CopyFiles.xaml nel progetto C++/WinRT (sostituendo il contenuto esistente di tale file nel progetto C++/WinRT).

Modifica infine CopyFiles.h e .cpp ed elimina la funzione ClickHandler fittizia, in quanto il markup XAML corrispondente è stato appena sovrascritto.

La conversione di CopyFiles a questo punto è terminata e, se hai seguito tutti i passaggi, il tuo progetto C++/WinRT ora verrà compilato ed eseguito e lo scenario CopyFiles sarà funzionante.

CopyImage

Per convertire il tipo di pagina XAML CopyImage, segui lo stesso processo illustrato per CopyFiles. Durante la conversione di CopyImage, vedrai l'uso dell'istruzione using C#, che assicura una corretta eliminazione degli oggetti che implementano l'interfaccia IDisposable.

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

L'interfaccia equivalente in C++/WinRT è IClosable, con il suo unico metodo Close. Di seguito è riportato l'equivalente C++/WinRT del codice C# precedente.

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

Gli oggetti C++/WinRT implementano IClosable principalmente a vantaggio dei linguaggi privi di finalizzazione deterministica. C++/WinRT prevede tale finalizzazione, pertanto spesso non è necessario chiamare IClosable::Close quando viene scritto codice C++/WinRT. Tuttavia, in alcuni casi è opportuno chiamarlo e questa è una di tali circostanze. Qui l'identificatore imageStream è un wrapper con conteggio dei riferimenti intorno a un oggetto Windows Runtime sottostante (in questo caso, un oggetto che implementa IRandomAccessStreamWithContentType). Benché sia possibile stabilire che il finalizzatore di imageStream (il relativo distruttore) venga eseguito alla fine dell'ambito che lo contiene (le parentesi graffe), non è certo che il finalizzatore chiami Close. Ecco perché imageStream è stato passato ad altre API, che potrebbero contribuire comunque al conteggio dei riferimenti dell'oggetto Windows Runtime sottostante. Questo è quindi un caso in cui è consigliabile chiamare Close in modo esplicito. Per altre informazioni, vedi Devo chiamare IClosable::Close sulle classi di runtime utilizzate?.

Considera quindi l'espressione C# (uint)(imageDecoder.OrientedPixelWidth * 0.5), che troverai nel gestore eventi OnDeferredImageRequestedHandler. Tale espressione moltiplica un uint per un double, dando come risultato un double. Ne esegue quindi il cast in un uint. In C++/WinRT si potrebbe usare un cast di tipo C simile ((uint32_t)(imageDecoder.OrientedPixelWidth() * 0.5)), ma è preferibile specificare in modo chiaro quale tipo di cast si intenda esattamente e in questo caso viene usato static_cast<uint32_t>(imageDecoder.OrientedPixelWidth() * 0.5).

La versione C# di CopyImage.OnDeferredImageRequestedHandler dispone di una clausola finally, ma non di una clausola catch. Nella versione C++/WinRT è stato possibile procedere ulteriormente ed è stata implementata una clausola catch, in modo da poter segnalare se l'esito del rendering ritardato era positivo o negativo.

La conversione della parte restante di questa pagina XAML non fornisce nuovi spunti di cui discutere. Ricorda di eliminare la funzione fittizia ClickHandler. Analogamente a quanto avviene con CopyFiles, l'ultimo passaggio della conversione consiste nel selezionare l'intero contenuto di CopyImage.xaml e incollarlo nello stesso file nel progetto C++/WinRT.

CopyText

Puoi convertire CopyText.xaml e CopyText.xaml.cs usando le tecniche già descritte.

HistoryAndRoaming

Durante la conversione del tipo di pagina XAML HistoryAndRoaming emergono alcuni spunti interessanti.

Prima di tutto, esamina il codice sorgente C# e segui il flusso di controllo da OnNavigatedTo al gestore eventi OnHistoryEnabledChanged e infine alla funzione asincrona CheckHistoryAndRoaming (che non è attesa, quindi essenzialmente è di tipo fire-and-forget). Poiché CheckHistoryAndRoaming è asincrona, sarà necessario prestare attenzione in C++/WinRT riguardo alla durata del puntatore this. Puoi vedere il risultato guardando l'implementazione nel file di codice sorgente HistoryAndRoaming.cpp. In primo luogo, quando vengono associati delegati agli eventi Clipboard::HistoryEnabledChanged e Clipboard::RoamingEnabledChanged, viene usato solo un riferimento debole all'oggetto della pagina HistoryAndRoaming. A tale scopo, viene creato il delegato con una dipendenza dal valore restituito da winrt::get_weak invece che con una dipendenza dal puntatore this. Ciò significa che il delegato stesso, che alla fine esegue chiamate nel codice asincrono, non mantiene attiva la pagina HistoryAndRoaming quando ci si sposta al di fuori di essa.

In secondo luogo, quando alla fine si giunge alla coroutine CheckHistoryAndRoaming di tipo fire-and-forget, la prima operazione da eseguire è usare un riferimento sicuro a this per garantire che la pagina HistoryAndRoaming resti attiva almeno fino al completamento della coroutine. Per altre informazioni su entrambi gli aspetti appena illustrati, vedi Riferimenti sicuri e deboli in C++/WinRT.

Durante la conversione di CheckHistoryAndRoaming emerge un altro spunto interessante. Contiene il codice per aggiornare l'interfaccia utente, quindi si deve avere la certezza di eseguire questa operazione sul thread principale dell'interfaccia utente. Il thread che inizialmente chiama un gestore eventi è il thread di UI principale. In genere, tuttavia, un metodo asincrono può essere eseguito e/o ripreso su qualsiasi thread arbitrario. In C# la soluzione consiste nel chiamare CoreDispatcher.RunAsync e nell'aggiornare l'interfaccia utente dall'interno della funzione lambda. In C++/WinRT è possibile usare la funzione winrt::resume_foreground insieme al this Dispatcher del puntatore per sospendere la coroutine e riprendere immediatamente sul thread principale dell'interfaccia utente.

L'espressione pertinente è co_await winrt::resume_foreground(Dispatcher());. In alternativa, sebbene con meno chiarezza, si potrebbe esprimere questa funzione semplicemente come co_await Dispatcher();. La versione più breve viene ottenuta grazie a un operatore di conversione fornito da C++/WinRT.

La conversione della parte restante di questa pagina XAML non fornisce nuovi spunti di cui discutere. Ricorda di eliminare la funzione fittizia ClickHandler e sovrascrivere il markup XAML.

OtherScenarios

Puoi convertire OtherScenarios.xaml e OtherScenarios.xaml.cs usando le tecniche già descritte.

Conclusione

Avendo seguito le informazioni e le tecniche di conversione illustrate in questa procedura dettagliata, ora puoi provare a eseguire da solo la conversione delle tue applicazioni C# a C++/WinRT. Tramite un aggiornamento, è possibile continuare a fare riferimento alla versione precedente (C#) e a quella successiva (C++/WinRT) del codice sorgente nell'esempio Clipboard e confrontarle affiancate per vedere la corrispondenza.