Freigeben über


Exemplarische Vorgehensweise zum Erstellen einer Komponente für Windows-Runtime in C# oder Visual Basic und Aufrufen der Komponente über JavaScript

In dieser exemplarischen Vorgehensweise wird gezeigt, wie Sie .NET mit Visual Basic oder C# verwenden können, um eigene Windows-Runtime-Typen zu erstellen, die in einer Windows-Runtime Komponente verpackt sind, und wie Sie diese Komponente dann aus einer JavaScript-Universelle Windows-Plattform -App (UWP) aufrufen.

Visual Studio erleichtert das Erstellen und Bereitstellen eigener benutzerdefinierter Windows-Runtime Typen in einem mit C# oder Visual Basic geschriebenen WRC-Projekt (Windows-Runtime Component), und verweist dann auf wrC aus einem JavaScript-Anwendungsprojekt und zum Verwenden dieser benutzerdefinierten Typen aus dieser Anwendung.

Intern können Ihre Windows-Runtime-Typen jede in einer UWP-Anwendung zulässige .NET-Funktion verwenden.

Extern können die Typelemente für ihre Parameter und Rückgabewerte nur Windows-Runtime-Typen verfügbar machen. Wenn Sie die Projektmappe erstellen, erstellt Visual Studio das .NET WRC-Projekt und führt dann einen Schritt zur Erstellung einer Datei mit Windows-Metadaten (.winmd) durch. Dies ist Ihre Windows-Runtime Komponente, die Visual Studio in Ihrer App enthält.

Hinweis

.NET ordnet einige häufig verwendete .NET-Typen, z. B. primitive Datentypen und Sammlungstypen, automatisch ihren Windows-Runtime-Entsprechungen zu. Diese .NET-Typen können in der öffentlichen Schnittstelle einer Komponente für Windows-Runtime verwendet werden und werden Benutzern der Komponente als die entsprechenden Windows-Runtime-Typen angezeigt. Siehe Komponenten für Windows-Runtime in C# und Visual Basic.

Voraussetzungen:

Hinweis

Universelle Windows-Plattform (UWP)-Projekte mit JavaScript werden in Visual Studio 2019 nicht unterstützt. Siehe JavaScript und TypeScript in Visual Studio 2019. Um diesem Thema zu folgen, empfehlen wir, Visual Studio 2017 zu verwenden. Siehe JavaScript in Visual Studio 2017.

Erstellen einer einfachen Windows-Runtime Klasse

In diesem Abschnitt wird eine JavaScript-UWP-Anwendung erstellt und der Projektmappe ein Visual Basic- oder C#-Windows-Runtime Komponentenprojekt hinzugefügt. Es zeigt, wie Sie einen Windows-Runtime Typ definieren, eine Instanz des Typs aus JavaScript erstellen und statische und Instanzmember aufrufen. Die visuelle Anzeige der Beispiel-App ist absichtlich niedrig, um den Fokus auf die Komponente zu behalten.

  1. Erstellen Sie in Visual Studio ein neues JavaScript-Projekt: Wählen Sie auf der Menüleiste "Datei", "Neu", "Projekt" aus. Wählen Sie im Abschnitt "Installierte Vorlagen" im Dialogfeld "Neues Projekt" JavaScript und dann "Windows" und dann "Universal" aus. (Wenn Windows nicht verfügbar ist, stellen Sie sicher, dass Sie Windows 8 oder höher verwenden.) Wählen Sie die Vorlage "Leere Anwendung" aus, und geben Sie "SampleApp" für den Projektnamen ein.

  2. Erstellen Sie das Komponentenprojekt: Öffnen Sie in Projektmappen-Explorer das Kontextmenü für die SampleApp-Projektmappe, und wählen Sie "Hinzufügen" und dann "Neues Projekt" aus, um der Projektmappe ein neues C#- oder Visual Basic-Projekt hinzuzufügen. Wählen Sie im Abschnitt "Installierte Vorlagen" im Dialogfeld "Neues Projekt hinzufügen" die Option "Visual Basic" oder "Visual C#" und dann "Windows" und dann "Universal" aus. Wählen Sie die Windows-Runtime Component-Vorlage aus, und geben Sie "SampleComponent" für den Projektnamen ein.

  3. Ändern Sie den Namen der Klasse in Beispiel. Beachten Sie, dass die Klasse standardmäßig als öffentlich versiegelt gekennzeichnet ist (Public NotInheritable in Visual Basic). Die Windows-Runtime-Klassen, die Sie von Ihrer Komponente aus bereitstellen, müssen versiegelt sein.

  4. Fügen Sie der Klasse zwei einfache Member hinzu, eine statische Methode (Shared-Methode in Visual Basic) und eine Instanzeigenschaft:

    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. Optional: Um IntelliSense für die neu hinzugefügten Member zu aktivieren, öffnen Sie in Projektmappen-Explorer das Kontextmenü für das SampleComponent-Projekt, und wählen Sie dann "Erstellen" aus.

  6. Öffnen Sie in Projektmappen-Explorer im JavaScript-Projekt das Kontextmenü für Verweise, und wählen Sie dann "Verweis hinzufügen" aus, um den Verweis-Manager zu öffnen. Wählen Sie Projekte und dann Projektmappe aus. Aktivieren Sie das Kontrollkästchen für das SampleComponent-Projekt, und wählen Sie "OK " aus, um einen Verweis hinzuzufügen.

Aufrufen der Komponente aus JavaScript

Um den Windows-Runtime Typ aus JavaScript zu verwenden, fügen Sie den folgenden Code in der anonymen Funktion in der datei default.js (im Js-Ordner des Projekts) hinzu, der von der Visual Studio-Vorlage bereitgestellt wird. Er sollte nach dem Ereignishandler "app.oncheckpoint" und vor dem Aufruf von "app.start" gehen.

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

Beachten Sie, dass der erste Buchstabe jedes Mitgliedsnamens von Großbuchstaben in Kleinbuchstaben geändert wird. Diese Transformation ist Teil der Unterstützung, die JavaScript bereitstellt, um die natürliche Verwendung des Windows-Runtime zu ermöglichen. Namespaces und Klassennamen sind Pascal-cased. Membernamen sind kamelbuchstaben, mit Ausnahme von Ereignisnamen, die alle Kleinbuchstaben sind. Siehe Verwenden des Windows-Runtime in JavaScript. Die Regeln für die Kamel-Groß-/Kleinschreibung können verwirrend sein. Eine Reihe von anfangs großgeschriebenen Buchstaben wird normalerweise als Kleinbuchstaben angezeigt, aber wenn drei Großbuchstaben gefolgt von einem Kleinbuchstaben sind, werden nur die ersten beiden Buchstaben in Kleinbuchstaben angezeigt: Beispielsweise wird ein Element namens "IDStringKind" als "idStringKind" angezeigt. In Visual Studio können Sie Ihr Windows-Runtime Komponentenprojekt erstellen und dann IntelliSense in Ihrem JavaScript-Projekt verwenden, um die richtige Groß-/Kleinschreibung anzuzeigen.

In ähnlicher Weise bietet .NET Unterstützung, um die natürliche Verwendung der Windows-Runtime in verwaltetem Code zu ermöglichen. Dies wird in den nachfolgenden Abschnitten dieses Artikels und in den Artikeln Windows-Runtime Komponenten mit C#- und Visual Basic- und .NET-Unterstützung für UWP-Apps und die Windows-Runtime erläutert.

Erstellen einer einfachen Benutzeroberfläche

Öffnen Sie in Ihrem JavaScript-Projekt die default.html Datei, und aktualisieren Sie den Text wie im folgenden Code dargestellt. Dieser Code enthält den vollständigen Satz von Steuerelementen für die Beispiel-App und gibt die Funktionsnamen für die Klickereignisse an.

Hinweis : Wenn Sie die App zum ersten Mal ausführen, werden nur die Schaltfläche "Basics1" und "Basics2" unterstützt.

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

Öffnen Sie in Ihrem JavaScript-Projekt im Ordner "css" default.css. Ändern Sie den Textabschnitt wie dargestellt, und fügen Sie Formatvorlagen hinzu, um das Layout von Schaltflächen und die Platzierung von Ausgabetext zu steuern.

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

Fügen Sie nun den Ereignislistenerregistrierungscode hinzu, indem Sie dem processAll-Aufruf in "app.onactivated" in default.js eine Dann-Klausel hinzufügen. Ersetzen Sie die vorhandene Codezeile, die setPromise aufruft, und ändern Sie sie in den folgenden Code:

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

Dies ist eine bessere Möglichkeit, Ereignisse zu HTML-Steuerelementen hinzuzufügen, als einen Klickereignishandler direkt in HTML hinzuzufügen. Siehe Erstellen einer "Hello, World"-App (JS).

Erstellen und Ausführen der App

Ändern Sie vor dem Erstellen die Zielplattform für alle Projekte in Arm, x64 oder x86, je nach Bedarf für Ihren Computer.

Um die Lösung zu erstellen und auszuführen, wählen Sie die F5-TASTE aus. (Wenn eine Laufzeitfehlermeldung angezeigt wird, die besagt, dass SampleComponent nicht definiert ist, fehlt der Verweis auf das Klassenbibliotheksprojekt.)

Visual Studio kompiliert zuerst die Klassenbibliothek und führt dann eine MSBuild-Aufgabe aus, die Winmdexp.exe (Windows-Runtime Metadatenexporttool) ausführt, um Ihre Windows-Runtime Komponente zu erstellen. Die Komponente ist in einer WINMD-Datei enthalten, die sowohl den verwalteten Code als auch die Windows-Metadaten enthält, die den Code beschreiben. WinMdExp.exe generiert Buildfehlermeldungen, wenn Sie Code schreiben, der in einer Windows-Runtime Komponente ungültig ist, und die Fehlermeldungen werden in der Visual Studio-IDE angezeigt. Visual Studio fügt ihre Komponente dem App-Paket (.appx Datei) für Ihre UWP-Anwendung hinzu und generiert das entsprechende Manifest.

Wählen Sie die Schaltfläche "Basics 1" aus, um den Rückgabewert aus der statischen GetAnswer-Methode dem Ausgabebereich zuzuweisen, eine Instanz der Example-Klasse zu erstellen und den Wert der SampleProperty-Eigenschaft im Ausgabebereich anzuzeigen. Die Ausgabe wird hier angezeigt:

"The answer is 42."
0

Wählen Sie die Schaltfläche "Basics 2" aus, um den Wert der SampleProperty-Eigenschaft zu erhöhen und den neuen Wert im Ausgabebereich anzuzeigen. Primitive Typen wie Zeichenfolgen und Zahlen können als Parametertypen und Rückgabetypen verwendet werden und zwischen verwaltetem Code und JavaScript übergeben werden. Da Zahlen in JavaScript im Gleitkommaformat mit doppelter Genauigkeit gespeichert werden, werden sie in numerische .NET Framework-Typen konvertiert.

Hinweis : Standardmäßig können Sie Haltepunkte nur in Ihrem JavaScript-Code festlegen. Informationen zum Debuggen von Visual Basic- oder C#-Code finden Sie unter Erstellen von Windows-Runtime Komponenten in C# und Visual Basic.

Um das Debuggen zu beenden und Ihre App zu schließen, wechseln Sie von der App zu Visual Studio, und wählen Sie UMSCHALT+F5 aus.

Verwenden des Windows-Runtime aus JavaScript und verwaltetem Code

Die Windows-Runtime kann entweder aus JavaScript oder verwaltetem Code aufgerufen werden. Windows-Runtime Objekte können zwischen den beiden übergeben werden, und Ereignisse können von beiden Seiten behandelt werden. Die Verwendung von Windows-Runtime Typen in den beiden Umgebungen unterscheidet sich jedoch in einigen Details, da JavaScript und .NET die Windows-Runtime unterschiedlich unterstützen. Im folgenden Beispiel werden diese Unterschiede mithilfe der Windows.Foundation.Collections.PropertySet-Klasse veranschaulicht. In diesem Beispiel erstellen Sie eine Instanz der PropertySet-Auflistung in verwaltetem Code und registrieren einen Ereignishandler zum Nachverfolgen von Änderungen in der Auflistung. Anschließend fügen Sie JavaScript-Code hinzu, der die Auflistung abruft, einen eigenen Ereignishandler registriert und die Auflistung verwendet. Schließlich fügen Sie eine Methode hinzu, die Änderungen an der Auflistung aus verwaltetem Code vorgibt und JavaScript mit einer verwalteten Ausnahme zeigt.

Wichtig in diesem Beispiel wird das Ereignis im UI-Thread ausgelöst. Wenn Sie das Ereignis aus einem Hintergrundthread auslösen, z. B. in einem asynchronen Aufruf, müssen Sie zusätzliche Arbeit ausführen, damit JavaScript das Ereignis behandelt. Weitere Informationen finden Sie unter Auslösen von Ereignissen in Windows-Runtime Komponenten.

Fügen Sie im SampleComponent-Projekt eine neue öffentliche versiegelte Klasse (Public NotInheritable-Klasse in Visual Basic) mit dem Namen PropertySetStats hinzu. Die Klasse umschließt eine PropertySet-Auflistung und behandelt das MapChanged-Ereignis. Der Ereignishandler verfolgt die Anzahl der Änderungen jeder Art, die auftreten, und die DisplayStats-Methode erzeugt einen Bericht, der in HTML formatiert ist. Beachten Sie die zusätzliche using-Anweisung (Imports-Anweisung in Visual Basic). Achten Sie darauf, dies den vorhandenen using-Anweisungen hinzuzufügen, anstatt sie zu überschreiben.

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

Der Ereignishandler folgt dem vertrauten .NET Framework-Ereignismuster, mit der Ausnahme, dass der Absender des Ereignisses (in diesem Fall das PropertySet-Objekt) in die IObservableMap-Zeichenfolge<, die Objektschnittstelle> (IObservableMap(Of String, Object) in Visual Basic umzurechnen ist, die eine Instanziierung der Windows-Runtime Schnittstelle IObservableMap<K, V> ist. (Sie können den Absender bei Bedarf in seinen Typ umwandeln.) Außerdem werden die Ereignisargumente als Schnittstelle und nicht als Objekt dargestellt.

Fügen Sie in der Datei default.js die Runtime1-Funktion wie dargestellt hinzu. Dieser Code erstellt ein PropertySetStats-Objekt, ruft seine PropertySet-Auflistung ab und fügt einen eigenen Ereignishandler, die onMapChanged-Funktion, zum Behandeln des MapChanged-Ereignisses hinzu. Nachdem Sie Änderungen an der Auflistung vorgenommen haben, ruft runtime1 die DisplayStats-Methode auf, um eine Zusammenfassung der Änderungstypen anzuzeigen.

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

Die Behandlung von Windows-Runtime Ereignissen in JavaScript unterscheidet sich sehr von der Behandlung in .NET Framework-Code. Der JavaScript-Ereignishandler verwendet nur ein Argument. Wenn Sie dieses Objekt im Visual Studio-Debugger anzeigen, ist die erste Eigenschaft der Absender. Die Member der Ereignisargumentschnittstelle werden auch direkt auf diesem Objekt angezeigt.

Um die App auszuführen, wählen Sie die F5-TASTE aus. Wenn die Klasse nicht versiegelt ist, wird die Fehlermeldung "Das Exportieren des nicht gesperrten Typs "SampleComponent.Example" wird derzeit nicht unterstützt. Bitte markieren Sie es als versiegelt."

Wählen Sie die Schaltfläche "Runtime 1 " aus. Der Ereignishandler zeigt Änderungen an, wenn Elemente hinzugefügt oder geändert werden, und am Ende wird die DisplayStats-Methode aufgerufen, um eine Zusammenfassung der Anzahlen zu erzeugen. Um das Debuggen zu beenden und die App zu schließen, wechseln Sie zurück zu Visual Studio, und wählen Sie UMSCHALT+F5 aus.

Um der PropertySet-Auflistung zwei weitere Elemente aus verwaltetem Code hinzuzufügen, fügen Sie der PropertySetStats-Klasse den folgenden Code hinzu:

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

Dieser Code hebt einen weiteren Unterschied in der Art und Weise hervor, wie Sie Windows-Runtime Typen in den beiden Umgebungen verwenden. Wenn Sie diesen Code selbst eingeben, werden Sie feststellen, dass IntelliSense die insert-Methode, die Sie im JavaScript-Code verwendet haben, nicht anzeigt. Stattdessen wird die Add-Methode angezeigt, die häufig in Auflistungen in .NET zu sehen ist. Dies liegt daran, dass einige häufig verwendete Sammlungsschnittstellen unterschiedliche Namen haben, aber ähnliche Funktionen in den Windows-Runtime und .NET. Wenn Sie diese Schnittstellen in verwaltetem Code verwenden, werden sie als .NET Framework-Entsprechungen angezeigt. Dies wird in Windows-Runtime Komponenten mit C# und Visual Basic erläutert. Wenn Sie die gleichen Schnittstellen in JavaScript verwenden, ändert sich nur die Windows-Runtime darin, dass Großbuchstaben am Anfang von Membernamen kleingeschrieben werden.

Fügen Sie schließlich die Runtime2-Funktion zum default.js hinzu, um die AddMore-Methode mit Ausnahmebehandlung aufzurufen.

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

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

Fügen Sie den Ereignishandlerregistrierungscode auf die gleiche Weise hinzu, wie Sie es zuvor getan haben.

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

Um die App auszuführen, wählen Sie die F5-TASTE aus. Wählen Sie Runtime 1 und dann Runtime 2 aus. Der JavaScript-Ereignishandler meldet die erste Änderung an der Auflistung. Die zweite Änderung weist jedoch einen doppelten Schlüssel auf. Benutzer von .NET Framework-Wörterbüchern erwarten, dass die Add-Methode eine Ausnahme auslöst, und das geschieht. JavaScript behandelt die .NET-Ausnahme.

Hinweis : Sie können die Ausnahmemeldung nicht aus JavaScript-Code anzeigen. Der Nachrichtentext wird durch eine Stapelablaufverfolgung ersetzt. Weitere Informationen finden Sie unter "Auslösen von Ausnahmen" in der Erstellung von Windows-Runtime Komponenten in C# und Visual Basic.

Wenn JavaScript dagegen die Insert-Methode mit einem doppelten Schlüssel aufgerufen hat, wurde der Wert des Elements geändert. Dieser Unterschied im Verhalten liegt auf den verschiedenen Arten, wie JavaScript und .NET die Windows-Runtime unterstützen, wie in Windows-Runtime Komponenten mit C# und Visual Basic erläutert.

Zurückgeben von verwalteten Typen aus Ihrer Komponente

Wie bereits erwähnt, können Sie systemeigene Windows-Runtime Typen frei zwischen Ihrem JavaScript-Code und Ihrem C#- oder Visual Basic-Code übergeben. In den meisten Fällen sind die Typnamen und Membernamen in beiden Fällen identisch (außer dass die Membernamen mit Kleinbuchstaben in JavaScript beginnen). Im vorherigen Abschnitt scheint die PropertySet-Klasse jedoch unterschiedliche Member im verwalteten Code zu haben. (In JavaScript haben Sie beispielsweise die Insert-Methode und im .NET-Code, den Sie als Add-Methode bezeichnet haben, aufgerufen.) In diesem Abschnitt wird erläutert, wie sich diese Unterschiede auf .NET Framework-Typen auswirken, die an JavaScript übergeben werden.

Zusätzlich zum Zurückgeben von Windows-Runtime Typen, die Sie in Ihrer Komponente erstellt oder von JavaScript an Ihre Komponente übergeben haben, können Sie einen verwalteten Typ, der in verwaltetem Code erstellt wurde, an JavaScript zurückgeben, als wäre es der entsprechende Windows-Runtime Typ. Selbst im ersten einfachen Beispiel einer Laufzeitklasse waren die Parameter und Rückgabetypen der Member Visual Basic oder C#-Grundtypen, die .NET Framework-Typen sind. Um dies für Auflistungen zu veranschaulichen, fügen Sie der Example-Klasse den folgenden Code hinzu, um eine Methode zu erstellen, die ein generisches Wörterbuch mit Zeichenfolgen zurückgibt, die nach ganzen Zahlen indiziert werden:

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

Beachten Sie, dass das Wörterbuch als Schnittstelle zurückgegeben werden muss, die von Dictionary<TKey, TValue> implementiert wird und einer Windows-Runtime-Schnittstelle zugeordnet ist. In diesem Fall ist die Schnittstelle IDictionary<int, string> (IDictionary(Of Integer, String) in Visual Basic). Wenn der Windows-Runtime Typ "IMap<" int übergeben wird, wird die Zeichenfolge> an verwalteten Code übergeben, wird sie als "IDictionary<int", "string>" und "reverse" angezeigt, wenn der verwaltete Typ an JavaScript übergeben wird.

Wichtig , wenn ein verwalteter Typ mehrere Schnittstellen implementiert, verwendet JavaScript die Schnittstelle, die zuerst in der Liste angezeigt wird. Wenn Sie beispielsweise Dictionary<int, string> to JavaScript code, it appears as IDictionary<int, string> no matter which interface you specify as the return type. Dies bedeutet, dass das Element, wenn die erste Schnittstelle kein Element enthält, das auf späteren Schnittstellen angezeigt wird, für JavaScript nicht sichtbar ist.

 

Um die neue Methode zu testen und das Wörterbuch zu verwenden, fügen Sie die Funktionen "returns1" und "returns2" zum default.js hinzu:

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

Fügen Sie den Ereignisregistrierungscode demselben code hinzu, und blockieren Sie dann den anderen Ereignisregistrierungscode:

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

Es gibt ein paar interessante Dinge, die Sie über diesen JavaScript-Code beobachten können. Zunächst enthält sie eine showMap-Funktion, um den Inhalt des Wörterbuchs in HTML anzuzeigen. Beachten Sie im Code für showMap das Iterationsmuster. In .NET gibt es keine First-Methode für die generische IDictionary-Schnittstelle, und die Größe wird von einer Count-Eigenschaft und nicht von einer Size-Methode zurückgegeben. Für JavaScript scheint IDictionary<int die Zeichenfolge die Windows-Runtime Typ IMap<int, Zeichenfolge>> zu sein. (Siehe IMap<K,V-Schnittstelle> .)

In der Returns2-Funktion ruft JavaScript wie in früheren Beispielen die Insert-Methode (einfügen in JavaScript) auf, um dem Wörterbuch Elemente hinzuzufügen.

Um die App auszuführen, wählen Sie die F5-TASTE aus. Um den anfänglichen Inhalt des Wörterbuchs zu erstellen und anzuzeigen, wählen Sie die Schaltfläche "1 zurückgeben" aus. Wenn Sie dem Wörterbuch zwei weitere Einträge hinzufügen möchten, wählen Sie die Schaltfläche "2 zurückgeben" aus. Beachten Sie, dass die Einträge in der Reihenfolge der Einfügung angezeigt werden, wie Sie von Dictionary<TKey, TValue> erwarten würden. Wenn sie sortiert werden sollen, können Sie eine SortedDictionary-Int-Zeichenfolge<> aus GetMapOfNames zurückgeben. (Die in früheren Beispielen verwendete PropertySet-Klasse weist eine andere interne Organisation als Wörterbuch< auf.TKey, TValue>.)

JavaScript ist natürlich keine stark typierte Sprache, sodass die Verwendung stark typierter generischer Auflistungen zu überraschenden Ergebnissen führen kann. Wählen Sie erneut die Schaltfläche "2 zurückgeben" aus. JavaScript wandelt die "7" zu einer numerischen 7 zusammen, und die numerische 7, die in ct in einer Zeichenfolge gespeichert ist. Und sie wandelt die Zeichenfolge "vierzig" auf Null um. Aber das ist nur der Anfang. Wählen Sie die Schaltfläche "2 zurückgeben" ein paar weitere Male aus. In verwaltetem Code generiert die Add-Methode doppelte Schlüssel ausnahmen, auch wenn die Werte in die richtigen Typen umgewandelt wurden. Im Gegensatz dazu aktualisiert die Insert-Methode den wert, der einem vorhandenen Schlüssel zugeordnet ist, und gibt einen booleschen Wert zurück, der angibt, ob dem Wörterbuch ein neuer Schlüssel hinzugefügt wurde. Aus diesem Grund ändert sich der wert, der dem Schlüssel 7 zugeordnet ist.

Ein weiteres unerwartetes Verhalten: Wenn Sie eine nicht zugewiesene JavaScript-Variable als Zeichenfolgenargument übergeben, wird die Zeichenfolge "undefined" abgerufen. Seien Sie kurz gesagt vorsichtig, wenn Sie .NET Framework-Sammlungstypen an Ihren JavaScript-Code übergeben.

Hinweis : Wenn Sie über große Textmengen verfügen, die verkettet werden sollen, können Sie ihn effizienter erledigen, indem Sie den Code in eine .NET Framework-Methode verschieben und die StringBuilder-Klasse verwenden, wie in der ShowMap-Funktion gezeigt.

Obwohl Sie ihre eigenen generischen Typen aus einer Windows-Runtime Komponente nicht verfügbar machen können, können Sie generische .NET Framework-Auflistungen für Windows-Runtime Klassen mithilfe von Code wie den folgenden zurückgeben:

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

List<T> implementiert IList<T>, das als Windows-Runtime Typ IVector<T> in JavaScript angezeigt wird.

Deklarieren von Ereignissen

Sie können Ereignisse mithilfe des standardmäßigen .NET Framework-Ereignismusters oder anderer Muster deklarieren, die vom Windows-Runtime verwendet werden. .NET Framework unterstützt die Äquivalenz zwischen dem "System.EventHandler<TEventArgs>"-Delegaten und dem Windows-Runtime EventHandler<T-Delegaten>. Daher ist die Verwendung von EventHandler<TEventArgs> eine gute Möglichkeit, das standardmäßige .NET Framework-Muster zu implementieren. Um zu sehen, wie dies funktioniert, fügen Sie dem SampleComponent-Projekt das folgende Klassenpaar hinzu:

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

Wenn Sie ein Ereignis im Windows-Runtime verfügbar machen, erbt die Ereignisargumentklasse von System.Object. Sie erbt nicht von System.EventArgs, wie in .NET, da EventArgs kein Windows-Runtime Typ ist.

Wenn Sie benutzerdefinierte Ereignisaccessoren für Ihr Ereignis deklarieren (benutzerdefiniertes Schlüsselwort in Visual Basic), müssen Sie das Windows-Runtime Ereignismuster verwenden. Siehe benutzerdefinierte Ereignisse und Ereignisaccessoren in Windows-Runtime Komponenten.

Um das Test-Ereignis zu behandeln, fügen Sie die Ereignis1-Funktion zu default.js hinzu. Die Ereignis1-Funktion erstellt eine Ereignishandlerfunktion für das Test-Ereignis und ruft sofort die OnTest-Methode auf, um das Ereignis auszuheben. Wenn Sie einen Haltepunkt im Textkörper des Ereignishandlers platzieren, können Sie sehen, dass das an den einzelnen Parameter übergebene Objekt das Quellobjekt und beide Member von TestEventArgs enthält.

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

Fügen Sie den Ereignisregistrierungscode demselben code hinzu, und blockieren Sie dann den anderen Ereignisregistrierungscode:

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

Verfügbarmachen asynchroner Vorgänge

Das .NET Framework verfügt über eine vielzahl von Tools für die asynchrone Verarbeitung und parallele Verarbeitung, basierend auf den Task- und generischen Task-TResult-Klassen><. Verwenden Sie zum Verfügbarmachen aufgabenbasierter asynchroner Verarbeitung in einer Windows-Runtime Komponente die Windows-Runtime Schnittstellen IAsyncAction, IAsyncActionWithProgress TProgress<>, IAsyncOperation<TResult> und IAsyncOperationWithProgress<TResult, TProgress.> (In der Windows-Runtime geben Vorgänge Ergebnisse zurück, aktionen jedoch nicht.)

In diesem Abschnitt wird ein abbruchbarer asynchroner Vorgang veranschaulicht, der den Fortschritt meldet und Ergebnisse zurückgibt. Die GetPrimesInRangeAsync-Methode verwendet die AsyncInfo-Klasse , um eine Aufgabe zu generieren und ihre Abbruch- und Statusberichtsfeatures mit einem WinJS.Promise-Objekt zu verbinden. Fügen Sie zunächst der Beispielklasse die GetPrimesInRangeAsync-Methode hinzu:

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 ist ein sehr einfacher Primzahlensucher, und das ist beabsichtigt. Der Fokus liegt hier auf der Implementierung eines asynchronen Vorgangs, daher ist die Einfachheit wichtig, und eine langsame Implementierung ist ein Vorteil, wenn wir den Abbruch demonstrieren. GetPrimesInRangeAsync findet Primes durch Brute force: Er dividiert einen Kandidaten durch alle ganzzahligen Zahlen, die kleiner oder gleich der Quadratwurzel sind, anstatt nur die Primzahlen zu verwenden. Schrittweises Durchlaufen dieses Codes:

  • Führen Sie vor dem Starten eines asynchronen Vorgangs Housekeeping-Aktivitäten aus, z. B. das Überprüfen von Parametern und das Auslösen von Ausnahmen für ungültige Eingaben.

  • Der Schlüssel zu dieser Implementierung ist die AsyncInfo.Run<TResult, TProgress>(Func<CancellationToken, IProgress<TProgress>, Task<TResult>>)-Methode und der Delegat, der nur der Parameter der Methode ist. Der Delegat muss ein Abbruchtoken und eine Schnittstelle zum Melden des Fortschritts akzeptieren und eine gestartete Aufgabe zurückgeben, die diese Parameter verwendet. Wenn JavaScript die GetPrimesInRangeAsync-Methode aufruft, treten die folgenden Schritte auf (nicht unbedingt in der hier angegebenen Reihenfolge):

    • Das WinJS.Promise-Objekt stellt Funktionen bereit, um die zurückgegebenen Ergebnisse zu verarbeiten, auf Abbruch zu reagieren und Statusberichte zu behandeln.

    • Die AsyncInfo.Run-Methode erstellt eine Abbruchquelle und ein Objekt, das die IProgress<T-Schnittstelle> implementiert. An den Delegaten übergibt es sowohl ein CancellationToken-Token von der Abbruchquelle als auch die IProgress<T-Schnittstelle> .

      Hinweis: Wenn das Promise-Objekt keine Funktion zum Reagieren auf den Abbruch liefert, übergibt AsyncInfo.Run weiterhin ein abbruchfähiges Token, und der Abbruch kann weiterhin auftreten. Wenn das Promise-Objekt keine Funktion zum Behandeln von Statusaktualisierungen bereitstellt, stellt AsyncInfo.Run weiterhin ein Objekt bereit, das IProgress<T> implementiert, aber seine Berichte werden ignoriert.

    • Der Delegat verwendet die Task.Run<TResult(Func<TResult>>, CancellationToken)-Methode, um eine gestartete Aufgabe zu erstellen, die das Token und die Fortschrittsschnittstelle verwendet. Der Delegat für die gestartete Aufgabe wird von einer Lambda-Funktion bereitgestellt, die das gewünschte Ergebnis berechnet. Weitere Informationen hierzu erhalten Sie in einem späteren Abschnitt.

    • Die AsyncInfo.Run-Methode erstellt ein Objekt, das die IAsyncOperationWithProgress<TResult, TProgress-Schnittstelle> implementiert, den Windows-Runtime Abbruchmechanismus mit der Tokenquelle verbindet und die Statusberichtsfunktion des Promise-Objekts mit der IProgress<T-Schnittstelle> verbindet.

    • Die IAsyncOperationWithProgress<TResult-, TProgress-Schnittstelle> wird an JavaScript zurückgegeben.

  • Die Lambda-Funktion, die durch die gestartete Aufgabe dargestellt wird, verwendet keine Argumente. Da es sich um eine Lambda-Funktion ist, hat sie Zugriff auf das Token und die IProgress-Schnittstelle. Jedes Mal, wenn eine Kandidatennummer ausgewertet wird, wird die Lambda-Funktion wie folgt ausgeführt:

    • Überprüft, ob der nächste Prozentpunkt des Fortschritts erreicht wurde. Wenn ja, ruft die Lambda-Funktion den IProgress<T> auf. Die Report-Methode und der Prozentsatz werden an die Funktion übergeben, die das Promise-Objekt für die Berichterstellung des Fortschritts angegeben hat.
    • Verwendet das Abbruchtoken, um eine Ausnahme auszuwerfen, wenn der Vorgang abgebrochen wurde. Wenn die IAsyncInfo.Cancel-Methode (die das IAsyncOperationWithProgress TResult, TProgress-Schnittstelle<> erbt) aufgerufen wurde, stellt die von der AsyncInfo.Run-Methode eingerichtete Verbindung sicher, dass das Abbruchtoken benachrichtigt wird.
  • Wenn die Lambda-Funktion die Liste der Primzahlen zurückgibt, wird die Liste an die Funktion übergeben, die das WinJS.Promise -Objekt für die Verarbeitung der Ergebnisse angegeben hat.

Um die JavaScript-Zusage zu erstellen und den Abbruchmechanismus einzurichten, fügen Sie die Funktionen "asyncRun" und "asyncCancel" zum default.js hinzu.

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

Vergessen Sie nicht den Ereignisregistrierungscode wie zuvor.

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

Durch Aufrufen der asynchronen GetPrimesInRangeAsync-Methode erstellt die asyncRun-Funktion ein WinJS.Promise-Objekt. Die anschließende Methode des Objekts verwendet drei Funktionen, die die zurückgegebenen Ergebnisse verarbeiten, auf Fehler (einschließlich Abbruch) reagieren und Statusberichte behandeln. In diesem Beispiel werden die zurückgegebenen Ergebnisse im Ausgabebereich gedruckt. Abbruch oder Abschluss setzt die Schaltflächen zurück, die den Vorgang starten und abbrechen. Die Statusberichterstattung aktualisiert das Statussteuerelement.

Die asyncCancel-Funktion ruft einfach die Cancel-Methode des WinJS.Promise-Objekts auf.

Um die App auszuführen, wählen Sie die F5-TASTE aus. Um den asynchronen Vorgang zu starten, wählen Sie die Asynchrone Schaltfläche aus. Was als Nächstes geschieht, hängt davon ab, wie schnell Ihr Computer ist. Wenn die Statusleiste nach Abschluss zippt, bevor Sie Zeit zum Blinken haben, erhöhen Sie die Größe der Startnummer, die an "GetPrimesInRangeAsync" übergeben wird, um einen oder mehrere Faktoren von zehn. Sie können die Dauer des Vorgangs optimieren, indem Sie die Anzahl der zu testenden Zahlen erhöhen oder verringern, aber das Hinzufügen von Nullen in der Mitte der Startnummer hat eine größere Auswirkung. Um den Vorgang abzubrechen, wählen Sie die Schaltfläche "Abbrechen Async " aus.