Condividi tramite


Procedura dettagliata per la creazione di un componente Windows Runtime C# o Visual Basic e chiamata del componente da JavaScript

Questa procedura dettagliata descrive come usare .NET con Visual Basic o C# per creare tipi Windows Runtime personalizzati, inseriti in un pacchetto di un componente Windows Runtime, e quindi come richiamare quel componente da un'app Universal Windows Platform (UWP) JavaScript.

Visual Studio semplifica la creazione e la distribuzione di tipi Windows Runtime personalizzati all'interno di un progetto WRC (Windows Runtime Component) scritto con C# o Visual Basic e quindi di fare riferimento a tale WRC da un progetto di applicazione JavaScript e di utilizzare tali tipi personalizzati da tale applicazione.

Internamente, i tipi di Windows Runtime possono utilizzare qualsiasi funzionalità .NET consentita in un'applicazione UWP.

Esternamente, i membri del tipo possono esporre solo tipi di per i relativi parametri e valori restituiti. Quando si costruisce la soluzione, Visual Studio costruisce il progetto .NET WRC e poi esegue una fase di compilazione che crea un file di metadati di Windows (.winmd). Si tratta del componente di Windows Runtime incluso nell'app Visual Studio.

Nota

.NET mappa automaticamente alcuni tipi .NET comunemente usati, come i tipi di dati primitivi e i tipi di collezione, ai loro equivalenti di Windows Runtime. Questi tipi .NET possono essere utilizzati nell'interfaccia pubblica di un componente Windows Runtime e appariranno agli utenti del componente come i corrispondenti tipi Windows Runtime. Componenti Windows Runtime con C# e Visual Basic.

Prerequisiti:

Nota

In Visual Studio 2019 i progetti Universal Windows Platform (UWP) usando JavaScript non sono supportati. Vedere JavaScript and TypeScript in Visual Studio 2019. Per seguire questo argomento, è consigliabile usare Visual Studio 2017. Vedere JavaScript in Visual Studio 2017.

Creazione di una semplice classe di Windows Runtime

Questa sezione crea un'applicazione UWP JavaScript e aggiunge alla soluzione un progetto di componente Windows Runtime di Visual Basic o C#. Illustra come definire un tipo di Windows Runtime, creare un'istanza del tipo da JavaScript e chiamare membri statici e dell'istanza. La visualizzazione visiva dell'app di esempio è deliberatamente bassa per mantenere lo stato attivo sul componente.

  1. In Visual Studio creare un nuovo progetto JavaScript: nella barra dei menu selezionare File, Nuovo, Progetto. Nella sezione Installed Templates della finestra di dialogo New Project, scegliere JavaScript, quindi scegliere Windows, quindi Universal. (Se Windows non è disponibile, assicurarsi di usare Windows 8 o versione successiva). Selezionare il modello Applicazione vuota e immettere SampleApp come nome del progetto.

  2. creare il progetto del componente: In Solution Explorer, aprite il menu di scelta rapida della soluzione SampleApp e selezionare Add, quindi selezionare New Project per aggiungere un nuovo progetto C# o Visual Basic alla soluzione. Nella sezione Installed Templates della finestra di dialogo Add New Project, selezionare Visual Basic o Visual C#, quindi selezionare Windows e poi Universal. Selezionare il modello Windows Runtime Component e inserire SampleComponent come nome del progetto.

  3. Modificare il nome della classe in Esempio. Si noti che per impostazione predefinita, la classe è contrassegnata come public sealed (Public NotInheritable in Visual Basic). Tutte le classi di esposte dal componente devono essere sealed.

  4. Aggiungere due membri semplici alla classe, un metodo statico (metodo Shared in Visual Basic) e una proprietà dell'istanza:

    namespace SampleComponent
    {
        public sealed class Example
        {
            public static string GetAnswer()
            {
                return "The answer is 42.";
            }
    
            public int SampleProperty { get; set; }
        }
    }
    
    Public NotInheritable Class Example
        Public Shared Function GetAnswer() As String
            Return "The answer is 42."
        End Function
    
        Public Property SampleProperty As Integer
    End Class
    
  5. Facoltativo: per abilitare IntelliSense per i membri appena aggiunti, in Esplora soluzioni apri il menu di scelta rapida del progetto SampleComponent e selezionare Build.

  6. In Esplora soluzioni, nel progetto JavaScript aprire il menu di scelta rapida per References, dunque selezionare Add Reference per aprire Reference Manager. Scegli Progetti, dunque scegli Soluzione. Selezionare la casella di controllo per il progetto SampleComponent e selezionare OK per aggiungere un riferimento.

Chiamare il componente da JavaScript

Per usare il tipo Windows Runtime da JavaScript, aggiungere il codice seguente nella funzione anonima nel file default.js (nella cartella js del progetto) fornito dal modello di Visual Studio. Deve andare dopo il gestore eventi app.oncheckpoint e prima della chiamata a app.start.

var ex;

function basics1() {
   document.getElementById('output').innerHTML =
        SampleComponent.Example.getAnswer();

    ex = new SampleComponent.Example();

   document.getElementById('output').innerHTML += "<br/>" +
       ex.sampleProperty;

}

function basics2() {
    ex.sampleProperty += 1;
    document.getElementById('output').innerHTML += "<br/>" +
        ex.sampleProperty;
}

Si noti che la prima lettera di ogni nome membro viene modificata da maiuscola a minuscola. Questa trasformazione fa parte del supporto fornito da JavaScript per abilitare l'uso naturale di Windows Runtime. Gli spazi dei nomi e i nomi delle classi sono maiuscole e minuscole Pascal. I nomi dei membri sono maiuscole e minuscole, ad eccezione dei nomi degli eventi, che sono tutti minuscoli. Vedere Uso di Windows Runtime in JavaScript. Le regole per la maiuscole e minuscole camel possono confondere. Una serie di lettere maiuscole iniziali viene normalmente visualizzata come minuscola, ma se tre lettere maiuscole sono seguite da una lettera minuscola, vengono visualizzate solo le prime due lettere in lettere minuscole: ad esempio, un membro denominato IDStringKind viene visualizzato come idStringKind. In Visual Studio è possibile compilare il progetto del componente Windows Runtime e quindi usare IntelliSense nel progetto JavaScript per visualizzare le maiuscole e minuscole corrette.

In modo analogo, .NET offre il supporto per abilitare l'uso naturale di Windows Runtime nel codice gestito. Questo argomento viene descritto nelle sezioni successive di questo articolo e negli articoli Componenti Windows Runtime con C# e Visual Basic e supporto .NET per le app UWP e Windows Runtime.

Creare un'interfaccia utente semplice

Nel progetto JavaScript aprire il file default.html e aggiornare il corpo come illustrato nel codice seguente. Questo codice include il set completo di controlli per l'app di esempio e specifica i nomi delle funzioni per gli eventi click.

Nota Quando si esegue l'app per la prima volta, sono supportati solo i pulsanti Basics1 e Basics2.

<body>
            <div id="buttons">
            <button id="button1" >Basics 1</button>
            <button id="button2" >Basics 2</button>

            <button id="runtimeButton1">Runtime 1</button>
            <button id="runtimeButton2">Runtime 2</button>

            <button id="returnsButton1">Returns 1</button>
            <button id="returnsButton2">Returns 2</button>

            <button id="events1Button">Events 1</button>

            <button id="btnAsync">Async</button>
            <button id="btnCancel" disabled="disabled">Cancel Async</button>
            <progress id="primeProg" value="25" max="100" style="color: yellow;"></progress>
        </div>
        <div id="output">
        </div>
</body>

Nel progetto JavaScript aprire default.css nella cartella css. Modificare la sezione del corpo come illustrato e aggiungere stili per controllare il layout dei pulsanti e il posizionamento del testo di output.

body
{
    -ms-grid-columns: 1fr;
    -ms-grid-rows: 1fr 14fr;
    display: -ms-grid;
}

#buttons {
    -ms-grid-rows: 1fr;
    -ms-grid-columns: auto;
    -ms-grid-row-align: start;
}
#output {
    -ms-grid-row: 2;
    -ms-grid-column: 1;
}

Aggiungere ora il codice di registrazione del listener di eventi aggiungendo una clausola then alla chiamata processAll in app.onactivated in default.js. Sostituire la riga di codice esistente che chiama setPromise e modificarla nel codice seguente:

args.setPromise(WinJS.UI.processAll().then(function () {
    var button1 = document.getElementById("button1");
    button1.addEventListener("click", basics1, false);
    var button2 = document.getElementById("button2");
    button2.addEventListener("click", basics2, false);
}));

Si tratta di un modo migliore per aggiungere eventi ai controlli HTML rispetto all'aggiunta di un gestore eventi click direttamente in HTML. Vedere Creare un'app console "Hello World" (JS).

Compilare ed eseguire l'app

Prima di compilare, modificare la piattaforma di destinazione per tutti i progetti in Arm, x64 o x86, in base alle esigenze del computer.

Per compilare ed eseguire la soluzione, selezionare il tasto F5. (Se viene visualizzato un messaggio di errore di run-time che indica che SampleComponent non è definito, il riferimento al progetto di libreria di classi è mancante).

Visual Studio compila prima la libreria di classi e quindi esegue un'attività MSBuild che esegue Winmdexp.exe (Strumento di esportazione metadati di Windows Runtime) per creare il componente Windows Runtime. Il componente è incluso in un file con estensione .winmd che contiene sia il codice gestito che i metadati di Windows che descrivono il codice. WinMdExp.exe genera messaggi di errore di compilazione quando si scrive codice non valido in un componente Windows Runtime e i messaggi di errore vengono visualizzati nell'IDE di Visual Studio. Visual Studio aggiunge il componente al pacchetto dell'app (file .appx) per l'applicazione UWP e genera il manifesto appropriato.

Selezionare il pulsante Informazioni di base 1 per assegnare il valore restituito dal metodo GetAnswer statico all'area di output, creare un'istanza della classe Example e visualizzare il valore della relativa proprietà SampleProperty nell'area di output. L'output è visualizzato qui:

"The answer is 42."
0

Selezionare il pulsante Informazioni di base 2 per incrementare il valore della proprietà SampleProperty e visualizzare il nuovo valore nell'area di output. I tipi primitivi, ad esempio stringhe e numeri, possono essere usati come tipi di parametro e tipi restituiti e possono essere passati tra codice gestito e JavaScript. Poiché i numeri in JavaScript vengono archiviati in formato a virgola mobile e precisione doppia, vengono convertiti in tipi numerici .NET Framework.

Nota Per impostazione predefinita, è possibile impostare punti di interruzione solo nel codice JavaScript. Per eseguire il debug del codice Visual Basic o C#, vedere Creazione di componenti Windows Runtime in C# e Visual Basic.

Per arrestare il debug e chiudere l'app, passare dall'app a Visual Studio e selezionare Shift+F5.

Uso di Windows Runtime da JavaScript e codice gestito

Windows Runtime può essere chiamato da Codice JavaScript o gestito. Gli oggetti Windows Runtime possono essere passati tra i due oggetti e gli eventi possono essere gestiti da entrambi i lati. Tuttavia, i modi in cui si usano i tipi Windows Runtime nei due ambienti differiscono in alcuni dettagli, perché JavaScript e .NET supportano Windows Runtime in modo diverso. Nell'esempio seguente vengono illustrate queste differenze, usando la classe Windows.Foundation.Collections.PropertySet. In questo esempio si crea un'istanza dell'insieme PropertySet nel codice gestito e si registra un gestore eventi per tenere traccia delle modifiche nella raccolta. Aggiungere quindi codice JavaScript che ottiene la raccolta, registra il proprio gestore eventi e usa la raccolta. Infine, si aggiunge un metodo che apporta modifiche alla raccolta dal codice gestito e mostra JavaScript che gestisce un'eccezione gestita.

Importante In questo esempio, l'evento viene generato nel thread dell'interfaccia utente. Se si attiva l'evento da un thread in background, ad esempio in una chiamata asincrona, è necessario eseguire alcune operazioni aggiuntive per consentire a JavaScript di gestire l'evento. Per altre informazioni, vedere Creazione di eventi in componenti Windows Runtime.

Nel progetto SampleComponent aggiungere una nuova classe sealed pubblica (classe Public NotInheritable in Visual Basic) denominata PropertySetStats. La classe esegue il wrapping di un insieme PropertySet e gestisce il relativo evento MapChanged. Il gestore eventi tiene traccia del numero di modifiche di ogni tipo e il metodo DisplayStats produce un report formattato in HTML. Si noti l'istruzione using aggiuntiva (Imports in Visual Basic). Prestare attenzione ad aggiungerla alle istruzioni using esistenti invece di sovrascriverle.

using Windows.Foundation.Collections;

namespace SampleComponent
{
    public sealed class PropertySetStats
    {
        private PropertySet _ps;
        public PropertySetStats()
        {
            _ps = new PropertySet();
            _ps.MapChanged += this.MapChangedHandler;
        }

        public PropertySet PropertySet { get { return _ps; } }

        int[] counts = { 0, 0, 0, 0 };
        private void MapChangedHandler(IObservableMap<string, object> sender,
            IMapChangedEventArgs<string> args)
        {
            counts[(int)args.CollectionChange] += 1;
        }

        public string DisplayStats()
        {
            StringBuilder report = new StringBuilder("<br/>Number of changes:<ul>");
            for (int i = 0; i < counts.Length; i++)
            {
                report.Append("<li>" + (CollectionChange)i + ": " + counts[i] + "</li>");
            }
            return report.ToString() + "</ul>";
        }
    }
}
Imports System.Text

Public NotInheritable Class PropertySetStats
    Private _ps As PropertySet
    Public Sub New()
        _ps = New PropertySet()
        AddHandler _ps.MapChanged, AddressOf Me.MapChangedHandler
    End Sub

    Public ReadOnly Property PropertySet As PropertySet
        Get
            Return _ps
        End Get
    End Property

    Dim counts() As Integer = {0, 0, 0, 0}
    Private Sub MapChangedHandler(ByVal sender As IObservableMap(Of String, Object),
        ByVal args As IMapChangedEventArgs(Of String))

        counts(CInt(args.CollectionChange)) += 1
    End Sub

    Public Function DisplayStats() As String
        Dim report As New StringBuilder("<br/>Number of changes:<ul>")
        For i As Integer = 0 To counts.Length - 1
            report.Append("<li>" & CType(i, CollectionChange).ToString() &
                          ": " & counts(i) & "</li>")
        Next
        Return report.ToString() & "</ul>"
    End Function
End Class

Il gestore eventi segue il modello di evento .NET Framework familiare, ad eccezione del fatto che il mittente dell'evento (in questo caso, l'oggetto PropertySet) viene eseguito il cast alla stringa IObservableMap<, all'interfaccia oggetto> (IObservableMap(Of String, Object) in Visual Basic), che è un'istanza dell'interfaccia di Windows Runtime IObservableMap<K, V>. (Se necessario, è possibile eseguire il cast del mittente al relativo tipo). Inoltre, gli argomenti dell'evento vengono presentati come interfaccia anziché come oggetto .

Nel file default.js aggiungere la funzione Runtime1 come illustrato. Questo codice crea un oggetto PropertySetStats, ottiene il relativo insieme PropertySet e aggiunge il proprio gestore eventi, la funzione onMapChanged, per gestire l'evento MapChanged. Dopo aver apportato modifiche alla raccolta, runtime1 chiama il metodo DisplayStats per visualizzare un riepilogo dei tipi di modifica.

var propertysetstats;

function runtime1() {
    document.getElementById('output').innerHTML = "";

    propertysetstats = new SampleComponent.PropertySetStats();
    var propertyset = propertysetstats.propertySet;

    propertyset.addEventListener("mapchanged", onMapChanged);

    propertyset.insert("FirstProperty", "First property value");
    propertyset.insert("SuperfluousProperty", "Unnecessary property value");
    propertyset.insert("AnotherProperty", "A property value");

    propertyset.insert("SuperfluousProperty", "Altered property value")
    propertyset.remove("SuperfluousProperty");

    document.getElementById('output').innerHTML +=
        propertysetstats.displayStats();
}

function onMapChanged(change) {
    var result
    switch (change.collectionChange) {
        case Windows.Foundation.Collections.CollectionChange.reset:
            result = "All properties cleared";
            break;
        case Windows.Foundation.Collections.CollectionChange.itemInserted:
            result = "Inserted " + change.key + ": '" +
                change.target.lookup(change.key) + "'";
            break;
        case Windows.Foundation.Collections.CollectionChange.itemRemoved:
            result = "Removed " + change.key;
            break;
        case Windows.Foundation.Collections.CollectionChange.itemChanged:
            result = "Changed " + change.key + " to '" +
                change.target.lookup(change.key) + "'";
            break;
        default:
            break;
     }

     document.getElementById('output').innerHTML +=
         "<br/>" + result;
}

Il modo in cui si gestiscono gli eventi di Windows Runtime in JavaScript è molto diverso dal modo in cui vengono gestiti nel codice .NET Framework. Il gestore eventi JavaScript accetta un solo argomento. Quando si visualizza questo oggetto nel debugger di Visual Studio, la prima proprietà è il mittente. Anche i membri dell'interfaccia dell'argomento evento vengono visualizzati direttamente su questo oggetto.

Eseguire l'app, selezionare il tasto F5. Se la classe non è sealed, viene visualizzato il messaggio di errore "L'esportazione del tipo non bloccato "SampleComponent.Example" non è attualmente supportata. Contrassegnarlo come sealed."

Selezionare il pulsante Runtime 1. Il gestore eventi visualizza le modifiche man mano che gli elementi vengono aggiunti o modificati e alla fine viene chiamato il metodo DisplayStats per produrre un riepilogo dei conteggi. Per interrompere il debug e chiudere l'app, tornare a Visual Studio e selezionare Shift+F5.

Per aggiungere altri due elementi all'insieme PropertySet dal codice gestito, aggiungere il codice seguente alla classe PropertySetStats:

public void AddMore()
{
    _ps.Add("NewProperty", "New property value");
    _ps.Add("AnotherProperty", "A property value");
}
Public Sub AddMore()
    _ps.Add("NewProperty", "New property value")
    _ps.Add("AnotherProperty", "A property value")
End Sub

Questo codice evidenzia un'altra differenza nel modo in cui si usano i tipi di Windows Runtime nei due ambienti. Se si digita questo codice manualmente, si noterà che IntelliSense non mostra il metodo di inserimento usato nel codice JavaScript. Mostra invece il metodo Add comunemente visualizzato nelle raccolte in .NET. Ciò è dovuto al fatto che alcune interfacce di raccolta comunemente usate hanno nomi diversi, ma funzionalità simili in Windows Runtime e .NET. Quando si usano queste interfacce nel codice gestito, vengono visualizzate come equivalenti di .NET Framework. Questo argomento viene descritto nei componenti Windows Runtime con C# e Visual Basic. Quando si usano le stesse interfacce in JavaScript, l'unica modifica rispetto a Windows Runtime è che le lettere maiuscole all'inizio dei nomi dei membri diventano minuscole.

Infine, per chiamare il metodo AddMore con la gestione delle eccezioni, aggiungere la funzione runtime2 a default.js.

function runtime2() {
   try {
      propertysetstats.addMore();
    }
   catch(ex) {
       document.getElementById('output').innerHTML +=
          "<br/><b>" + ex + "<br/>";
   }

   document.getElementById('output').innerHTML +=
       propertysetstats.displayStats();
}

Aggiungere il codice di registrazione del gestore eventi nello stesso modo in cui è stato fatto in precedenza.

var runtimeButton1 = document.getElementById("runtimeButton1");
runtimeButton1.addEventListener("click", runtime1, false);
var runtimeButton2 = document.getElementById("runtimeButton2");
runtimeButton2.addEventListener("click", runtime2, false);

Eseguire l'app, selezionare il tasto F5. Selezionare Runtime 1 e quindi Runtime 2. Il gestore eventi JavaScript segnala la prima modifica alla raccolta. La seconda modifica, tuttavia, ha una chiave duplicata. Gli utenti dei dizionari .NET Framework si aspettano che il metodo Add generi un'eccezione ed è ciò che accade. JavaScript gestisce l'eccezione .NET.

Nota Non è possibile visualizzare il messaggio dell'eccezione dal codice JavaScript. Il testo del messaggio viene sostituito da un'analisi dello stack. Per altre informazioni, vedere "Lanciare eccezioni" in Creazione di componenti Windows Runtime in C# e Visual Basic.

Al contrario, quando JavaScript ha chiamato il metodo insert con una chiave duplicata, il valore dell'elemento è stato modificato. Questa differenza di comportamento è dovuta ai diversi modi in cui JavaScript e .NET supportano Windows Runtime, come illustrato in Componenti Windows Runtime con C# e Visual Basic.

Restituzione di tipi gestiti dal componente

Come illustrato in precedenza, è possibile passare liberamente i tipi nativi di Windows Runtime tra il codice JavaScript e il codice C# o Visual Basic. Nella maggior parte dei casi, i nomi dei tipi e dei membri saranno gli stessi in entrambi i casi (ad eccezione del fatto che i nomi dei membri iniziano con lettere minuscole in JavaScript). Tuttavia, nella sezione precedente, la classe PropertySet sembrava avere membri diversi nel codice gestito. Ad esempio, in JavaScript è stato chiamato il metodo insert e nel codice .NET è stato chiamato il metodo Add. Questa sezione illustra il modo in cui tali differenze influiscono sui tipi di .NET Framework passati a JavaScript.

Oltre a restituire i tipi di Windows Runtime creati nel componente o passati al componente da JavaScript, è possibile restituire un tipo gestito, creato nel codice gestito, a JavaScript come se fosse il tipo Windows Runtime corrispondente. Anche nel primo esempio semplice di una classe di runtime, i parametri e i tipi restituiti dei membri erano tipi primitivi Visual Basic o C#, che sono tipi .NET Framework. Per illustrare questa operazione per le raccolte, aggiungere il codice seguente alla classe Example per creare un metodo che restituisce un dizionario generico di stringhe indicizzate da numeri interi:

public static IDictionary<int, string> GetMapOfNames()
{
    Dictionary<int, string> retval = new Dictionary<int, string>();
    retval.Add(1, "one");
    retval.Add(2, "two");
    retval.Add(3, "three");
    retval.Add(42, "forty-two");
    retval.Add(100, "one hundred");
    return retval;
}
Public Shared Function GetMapOfNames() As IDictionary(Of Integer, String)
    Dim retval As New Dictionary(Of Integer, String)
    retval.Add(1, "one")
    retval.Add(2, "two")
    retval.Add(3, "three")
    retval.Add(42, "forty-two")
    retval.Add(100, "one hundred")
    Return retval
End Function

Si noti che il dizionario deve essere restituito come interfaccia implementata da Dictionary<TKey, TValue> e che esegue il mapping a un'interfaccia di Windows Runtime. In questo caso, l'interfaccia è IDictionary<int, stringa> (IDictionary(Of Integer, String) in Visual Basic). Quando IMap tipo Windows Runtime<int, la stringa> viene passata al codice gestito, viene visualizzata come IDictionary<int, string> e il contrario è true quando il tipo gestito viene passato a JavaScript.

Importante Quando un tipo gestito implementa più interfacce, JavaScript usa l'interfaccia visualizzata per prima nell'elenco. Ad esempio, se si restituisce Dictionary<int, stringa> al codice JavaScript, viene visualizzata come IDictionary<int, stringa> indipendentemente dall'interfaccia specificata come tipo restituito. Ciò significa che se la prima interfaccia non include un membro visualizzato nelle interfacce successive, tale membro non è visibile a JavaScript.

 

Per testare il nuovo metodo e usare il dizionario, aggiungere le funzioni returns1 e returns2 a default.js:

var names;

function returns1() {
    names = SampleComponent.Example.getMapOfNames();
    document.getElementById('output').innerHTML = showMap(names);
}

var ct = 7;

function returns2() {
    if (!names.hasKey(17)) {
        names.insert(43, "forty-three");
        names.insert(17, "seventeen");
    }
    else {
        var err = names.insert("7", ct++);
        names.insert("forty", "forty");
    }
    document.getElementById('output').innerHTML = showMap(names);
}

function showMap(map) {
    var item = map.first();
    var retval = "<ul>";

    for (var i = 0, len = map.size; i < len; i++) {
        retval += "<li>" + item.current.key + ": " + item.current.value + "</li>";
        item.moveNext();
    }
    return retval + "</ul>";
}

Aggiungere il codice di registrazione dell'evento allo stesso blocco dell'altro codice di registrazione eventi:

var returnsButton1 = document.getElementById("returnsButton1");
returnsButton1.addEventListener("click", returns1, false);
var returnsButton2 = document.getElementById("returnsButton2");
returnsButton2.addEventListener("click", returns2, false);

Esistono alcuni aspetti interessanti da osservare su questo codice JavaScript. Prima di tutto, include una funzione showMap per visualizzare il contenuto del dizionario in HTML. Nel codice per showMap notare il modello di iterazione. In .NET non esiste alcun metodo First nell'interfaccia IDictionary generica e le dimensioni vengono restituite da una proprietà Count anziché da un metodo Size. Per JavaScript, IDictionary<int, stringa> sembra essere il tipo IMap di Windows Runtime<int, stringa>. (Vedere Interfaccia IMap<K,V>).

Nella funzione returns2, come negli esempi precedenti, JavaScript chiama il metodo Insert (insert in JavaScript) per aggiungere elementi al dizionario.

Eseguire l'app, selezionare il tasto F5. Per creare e visualizzare il contenuto iniziale del dizionario, selezionare il pulsante Returns 1. Per aggiungere altre due voci al dizionario, selezionare il pulsante Returns 2 . Si noti che le voci vengono visualizzate in ordine di inserimento, come ci si aspetterebbe da Dictionary<TKey, TValue>. Se si desidera ordinarli, è possibile restituire una stringa SortedDictionary<int,> da GetMapOfNames. La classe PropertySet usata negli esempi precedenti ha un'organizzazione interna diversa da Dictionary<TKey, TValue>.

Naturalmente, JavaScript non è un linguaggio fortemente tipizzato, quindi l'uso di raccolte generiche fortemente tipizzate può causare risultati sorprendenti. Selezionare di nuovo il pulsante Returns 2. JavaScript impone in modo oscuro il valore "7" a un valore numerico 7 e il numero 7 archiviato in ct in una stringa. E costringe la stringa "quaranta" a zero. Ma si tratta solo dell'inizio. Selezionare il pulsante Returns 2 alcune altre volte. Nel codice gestito, il metodo Add genera eccezioni di chiave duplicate, anche se i valori sono stati cast ai tipi corretti. Al contrario, il metodo Insert aggiorna il valore associato a una chiave esistente e restituisce un valore booleano che indica se è stata aggiunta una nuova chiave al dizionario. Ecco perché il valore associato al tasto 7 continua a cambiare.

Un altro comportamento imprevisto: se si passa una variabile JavaScript non assegnata come argomento stringa, ciò che si ottiene è la stringa "undefined". In breve, prestare attenzione quando si passano tipi di raccolta .NET Framework al codice JavaScript.

Nota Se si dispone di grandi quantità di testo da concatenare, è possibile farlo in modo più efficiente spostando il codice in un metodo .NET Framework e usando la classe StringBuilder, come illustrato nella funzione showMap.

Sebbene non sia possibile esporre i propri tipi generici da un componente Windows Runtime, è possibile restituire raccolte generiche di .NET Framework per le classi Windows Runtime usando codice come il seguente:

public static object GetListOfThis(object obj)
{
    Type target = obj.GetType();
    return Activator.CreateInstance(typeof(List<>).MakeGenericType(target));
}
Public Shared Function GetListOfThis(obj As Object) As Object
    Dim target As Type = obj.GetType()
    Return Activator.CreateInstance(GetType(List(Of )).MakeGenericType(target))
End Function

L'elenco<T> implementa IList<T>, che viene visualizzato come IVector tipo Windows Runtime<T> in JavaScript.

Dichiarazione di eventi

È possibile dichiarare eventi usando il modello di eventi standard di .NET Framework o altri modelli usati da Windows Runtime. .NET Framework supporta l'equivalenza tra il delegato System.EventHandler<TEventArgs> e il delegato Windows Runtime EventHandler<T>, pertanto usare EventHandler<TEventArgs> è un buon modo per implementare il modello .NET Framework standard. Per informazioni sul funzionamento, aggiungere la coppia di classi seguente al progetto SampleComponent:

namespace SampleComponent
{
    public sealed class Eventful
    {
        public event EventHandler<TestEventArgs> Test;
        public void OnTest(string msg, long number)
        {
            EventHandler<TestEventArgs> temp = Test;
            if (temp != null)
            {
                temp(this, new TestEventArgs()
                {
                    Value1 = msg,
                    Value2 = number
                });
            }
        }
    }

    public sealed class TestEventArgs
    {
        public string Value1 { get; set; }
        public long Value2 { get; set; }
    }
}
Public NotInheritable Class Eventful
    Public Event Test As EventHandler(Of TestEventArgs)
    Public Sub OnTest(ByVal msg As String, ByVal number As Long)
        RaiseEvent Test(Me, New TestEventArgs() With {
                            .Value1 = msg,
                            .Value2 = number
                            })
    End Sub
End Class

Public NotInheritable Class TestEventArgs
    Public Property Value1 As String
    Public Property Value2 As Long
End Class

Quando si espone un evento in Windows Runtime, la classe dell'argomento evento eredita da System.Object. Non eredita da System.EventArgs, come in .NET, perché EventArgs non è un tipo Windows Runtime.

Se si dichiarano funzioni di accesso agli eventi personalizzate per l'evento (parola chiave personalizzata in Visual Basic), si deve usare il modello di evento di Windows Runtime. Vedere Eventi personalizzati e funzioni di accesso agli eventi nei componenti di Windows Runtime.

Per gestire l'evento Test, aggiungere la funzione events1 a default.js. La funzione events1 crea una funzione del gestore eventi per l'evento Test e richiama immediatamente il metodo OnTest per generare l'evento. Se si inserisce un punto di interruzione nel corpo del gestore eventi, è possibile notare che l'oggetto passato al singolo parametro include l'oggetto di origine e entrambi i membri di TestEventArgs.

var ev;

function events1() {
   ev = new SampleComponent.Eventful();
   ev.addEventListener("test", function (e) {
       document.getElementById('output').innerHTML = e.value1;
       document.getElementById('output').innerHTML += "<br/>" + e.value2;
   });
   ev.onTest("Number of feet in a mile:", 5280);
}

Aggiungere il codice di registrazione dell'evento allo stesso blocco dell'altro codice di registrazione eventi:

var events1Button = document.getElementById("events1Button");
events1Button.addEventListener("click", events1, false);

Esposizione di operazioni asincrone

.NET Framework include un set completo di strumenti per l'elaborazione asincrona e l'elaborazione parallela, in base alle classi Task Task<TResult> Task e generiche. Per esporre l'elaborazione asincrona basata su attività in un componente Windows Runtime, usare le interfacce IAsyncAction Windows Runtime, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> e IAsyncOperationWithProgress<TResult, TProgress>. (In Windows Runtime le operazioni restituiscono risultati, ma non le azioni).

In questa sezione viene illustrata un'operazione asincrona annullabile che segnala lo stato di avanzamento e restituisce i risultati. Il metodo GetPrimesInRangeAsync usa la classe AsyncInfo per generare un'attività e per connettere le funzionalità di annullamento e creazione di report dello stato a un oggetto WinJS.Promise. Per iniziare, aggiungere il metodo GetPrimesInRangeAsync alla classe di esempio:

using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;

public static IAsyncOperationWithProgress<IList<long>, double>
GetPrimesInRangeAsync(long start, long count)
{
    if (start < 2 || count < 1) throw new ArgumentException();

    return AsyncInfo.Run<IList<long>, double>((token, progress) =>

        Task.Run<IList<long>>(() =>
        {
            List<long> primes = new List<long>();
            double onePercent = count / 100;
            long ctProgress = 0;
            double nextProgress = onePercent;

            for (long candidate = start; candidate < start + count; candidate++)
            {
                ctProgress += 1;
                if (ctProgress >= nextProgress)
                {
                    progress.Report(ctProgress / onePercent);
                    nextProgress += onePercent;
                }
                bool isPrime = true;
                for (long i = 2, limit = (long)Math.Sqrt(candidate); i <= limit; i++)
                {
                    if (candidate % i == 0)
                    {
                        isPrime = false;
                        break;
                    }
                }
                if (isPrime) primes.Add(candidate);

                token.ThrowIfCancellationRequested();
            }
            progress.Report(100.0);
            return primes;
        }, token)
    );
}
Imports System.Runtime.InteropServices.WindowsRuntime

Public Shared Function GetPrimesInRangeAsync(ByVal start As Long, ByVal count As Long)
As IAsyncOperationWithProgress(Of IList(Of Long), Double)

    If (start < 2 Or count < 1) Then Throw New ArgumentException()

    Return AsyncInfo.Run(Of IList(Of Long), Double)( _
        Function(token, prog)
            Return Task.Run(Of IList(Of Long))( _
                Function()
                    Dim primes As New List(Of Long)
                    Dim onePercent As Long = count / 100
                    Dim ctProgress As Long = 0
                    Dim nextProgress As Long = onePercent

                    For candidate As Long = start To start + count - 1
                        ctProgress += 1

                        If ctProgress >= nextProgress Then
                            prog.Report(ctProgress / onePercent)
                            nextProgress += onePercent
                        End If

                        Dim isPrime As Boolean = True
                        For i As Long = 2 To CLng(Math.Sqrt(candidate))
                            If (candidate Mod i) = 0 Then
                                isPrime = False
                                Exit For
                            End If
                        Next

                        If isPrime Then primes.Add(candidate)

                        token.ThrowIfCancellationRequested()
                    Next
                    prog.Report(100.0)
                    Return primes
                End Function, token)
        End Function)
End Function

GetPrimesInRangeAsync è una ricerca di un numero primo molto semplice, ed è per progettazione. L'obiettivo è l'implementazione di un'operazione asincrona, quindi la semplicità è importante e un'implementazione lenta è un vantaggio quando si dimostra l'annullamento. GetPrimesInRangeAsync trova i primi per forza bruta: divide un candidato per tutti i numeri interi minori o uguali alla radice quadrata, anziché usare solo i numeri primi. Esecuzione di questo codice:

  • Prima di avviare un'operazione asincrona, eseguire attività di manutenzione come la convalida dei parametri e la generazione di eccezioni per l'input non valido.

  • La chiave di questa implementazione è il metodo AsyncInfo.Run<TResult, TProgress>(Func<CancellationToken, IProgress<TProgress>, Task<TResult>>), e il delegato che è l'unico parametro del metodo. Il delegato deve accettare un token di annullamento e un'interfaccia per segnalare lo stato di avanzamento e deve restituire un'attività avviata che usa tali parametri. Quando JavaScript chiama il metodo GetPrimesInRangeAsync, vengono eseguiti i passaggi seguenti (non necessariamente nell'ordine indicato qui):

    • L'oggetto WinJS.Promise fornisce funzioni per elaborare i risultati restituiti, reagire all'annullamento e gestire i report di stato.

    • Il metodo AsyncInfo.Run crea un'origine di annullamento e un oggetto che implementa l'interfaccia IProgress<T>. Al delegato passa sia un token CancellationToken dalla sorgente di annullamento che l'interfaccia IProgress<T>.

      Nota Se l'oggetto Promise non fornisce una funzione per reagire all'annullamento, AsyncInfo.Run passa comunque un token annullabile e l'annullamento può comunque verificarsi. Se l'oggetto Promise non fornisce una funzione per gestire gli aggiornamenti dello stato di avanzamento, AsyncInfo.Run fornisce comunque un oggetto che implementa IProgress<T>, ma i relativi report vengono ignorati.

    • Il delegato usa il metodo Task.Run<TResult>(Func<TResult>, CancellationToken) per creare un'attività avviata che usa il token e l'interfaccia di stato. Il delegato per l'attività avviata viene fornito da una funzione lambda che calcola il risultato desiderato. Fra poco verranno fornite altre informazioni su questo argomento.

    • Il metodo AsyncInfo.Run crea un oggetto che implementa l'interfaccia IAsyncOperationWithProgress<TResult, TProgress>, collega il meccanismo di annullamento di Windows Runtime all'origine del token e collega la funzione di creazione di report dello stato dell'oggetto Promise con l'interfaccia IProgress<T>.

    • L'interfaccia IAsyncOperationWithProgress<TResult, TProgress> viene restituita a JavaScript.

  • La funzione lambda rappresentata dall'attività avviata non accetta argomenti. Poiché si tratta di una funzione lambda, ha accesso al token e all'interfaccia IProgress. Ogni volta che viene valutato un numero candidato, la funzione lambda:

    • Verifica se è stato raggiunto il punto percentuale di avanzamento successivo. In caso affermativo, la funzione lambda chiama IProgress<T>. Il metodo Report e la percentuale viene passata alla funzione specificata dall'oggetto Promise per segnalare lo stato di avanzamento.
    • Usa il token di annullamento per generare un'eccezione se l'operazione è stata annullata. Se è stato chiamato il metodo IAsyncInfo.Cancel (che l'interfaccia IAsyncOperationWithProgress<TResult, TProgress> eredita), la connessione configurata dal metodo AsyncInfo.Run garantisce che il token di annullamento venga informato.
  • Quando la funzione lambda restituisce l'elenco di numeri primi, l'elenco viene passato alla funzione specificata dall'oggetto WinJS.Promise per l'elaborazione dei risultati.

Per creare la promessa JavaScript e configurare il meccanismo di annullamento, aggiungere le funzioni asyncRun e asyncCancel a default.js.

var resultAsync;
function asyncRun() {
    document.getElementById('output').innerHTML = "Retrieving prime numbers.";
    btnAsync.disabled = "disabled";
    btnCancel.disabled = "";

    resultAsync = SampleComponent.Example.getPrimesInRangeAsync(10000000000001, 2500).then(
        function (primes) {
            for (i = 0; i < primes.length; i++)
                document.getElementById('output').innerHTML += " " + primes[i];

            btnCancel.disabled = "disabled";
            btnAsync.disabled = "";
        },
        function () {
            document.getElementById('output').innerHTML += " -- getPrimesInRangeAsync was canceled. -- ";

            btnCancel.disabled = "disabled";
            btnAsync.disabled = "";
        },
        function (prog) {
            document.getElementById('primeProg').value = prog;
        }
    );
}

function asyncCancel() {    
    resultAsync.cancel();
}

Non dimenticare il codice di registrazione dell'evento come è stato fatto in precedenza.

var btnAsync = document.getElementById("btnAsync");
btnAsync.addEventListener("click", asyncRun, false);
var btnCancel = document.getElementById("btnCancel");
btnCancel.addEventListener("click", asyncCancel, false);

Chiamando il metodo Asincrono GetPrimesInRangeAsync, la funzione asyncRun crea un oggetto WinJS.Promise. Il metodo dell'oggetto accetta quindi tre funzioni che elaborano i risultati restituiti, reagiscono agli errori (incluso l'annullamento) e gestiscono i report di stato. In questo esempio i risultati restituiti vengono stampati nell'area di output. L'annullamento o il completamento reimposta i pulsanti che avviano e annullano l'operazione. La creazione di report sullo stato di avanzamento aggiorna il controllo di stato.

La funzione asyncCancel chiama semplicemente il metodo cancel dell'oggetto WinJS.Promise.

Eseguire l'app, selezionare il tasto F5. Per avviare l'operazione asincrona, selezionare il pulsante Async. Ciò che accade di seguito dipende dalla velocità del computer. Se la barra di stato si blocca fino al completamento prima di avere tempo per lampeggiare, aumentare le dimensioni del numero iniziale passato a GetPrimesInRangeAsync di uno o più fattori di dieci. È possibile ottimizzare la durata dell'operazione aumentando o riducendo il numero di numeri da testare, ma l'aggiunta di zeri al centro del numero iniziale avrà un impatto maggiore. Per annullare l'operazione, selezionare il pulsante Annulla asincrona.