Compartir a través de


Tutorial para crear un componente de Windows Runtime de C# o Visual Basic y llamarlo desde JavaScript

En este tutorial se muestra cómo puedes usar .NET con Visual Basic o C# para crear tus propios tipos de Windows Runtime, empaquetados en un componente de Windows Runtime y cómo llamar a ese componente desde una aplicación de JavaScript Plataforma universal de Windows (UWP).

Visual Studio facilita la creación e implementación de sus propios tipos personalizados de Windows Runtime dentro de un proyecto de componente de Windows Runtime (CMR) escrito con C# o Visual Basic y, a continuación, para hacer referencia a ese CMR desde un proyecto de aplicación de JavaScript y para consumir esos tipos personalizados desde esa aplicación.

Internamente, los tipos de Windows Runtime pueden usar cualquier funcionalidad de .NET permitida en una aplicación para UWP.

Externamente, los miembros de los tipos pueden exponer solo los tipos de Windows Runtime para sus parámetros y valores devueltos. Cuando se compila una solución, Visual Studio compila el proyecto de WRC de .NET y, después, ejecuta un paso de compilación que crea un archivo de metadatos de Windows (.winmd). Este es el componente de Windows Runtime, que Visual Studio incluye en la aplicación.

Nota:

.NET asigna automáticamente algunos de los tipos de .NET más usados (como tipos de datos primitivos y tipos de colección) a sus equivalentes de Windows Runtime. Estos tipos de .NET pueden usarse en la interfaz pública de un componente de Windows Runtime y se mostrarán a los usuarios del componente como los tipos correspondientes de Windows Runtime. Consulte Componentes de Windows Runtime con C# y Visual Basic.

Requisitos previos:

Nota:

los proyectos de Plataforma universal de Windows (UWP) que usan JavaScript no se admiten en Visual Studio 2019. Consulte JavaScript y TypeScript en Visual Studio 2019. Para seguir este tema, se recomienda usar Visual Studio 2017. Consulte JavaScript en Visual Studio 2017.

Creación de una clase sencilla de Windows Runtime

En esta sección se crea una aplicación para UWP de JavaScript y se agrega a la solución un proyecto de componente de Windows Runtime de Visual Basic o C#. Muestra cómo definir un tipo de Windows Runtime, crear una instancia del tipo a partir de JavaScript y llamar a miembros estáticos e de instancia. La presentación visual de la aplicación de ejemplo es deliberadamente de baja clave para mantener el foco en el componente.

  1. En Visual Studio, cree un nuevo proyecto de JavaScript: en la barra de menús, elija Archivo, Nuevo, Proyecto. En la sección Plantillas instaladas del cuadro de diálogo Nuevo proyecto, elija JavaScript y, después, Windows y, a continuación, Universal. (Si Windows no está disponible, asegúrese de que usa Windows 8 o posterior). Elija la plantilla Aplicación en blanco y escriba SampleApp como nombre del proyecto.

  2. Cree el proyecto de componente: en Explorador de soluciones, abra el menú contextual de la solución SampleApp y elija Agregar y, a continuación, elija Nuevo proyecto para agregar un nuevo proyecto de C# o Visual Basic a la solución. En la sección Plantillas instaladas del cuadro de diálogo Agregar nuevo proyecto , elija Visual Basic o Visual C#, y, a continuación, elija Windows y, a continuación , Universal. Elija la plantilla Componente de Windows Runtime y escriba SampleComponent como nombre del proyecto.

  3. Cambie el nombre de la clase a Example. Tenga en cuenta que, de forma predeterminada, la clase está marcada como public sealed (Public NotInheritable en Visual Basic). Todas las clases de Windows Runtime que exponga desde el componente deben ser sealed.

  4. Agregue dos miembros simples a la clase , un método estático (método Compartido en Visual Basic) y una propiedad de instancia:

    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. Opcional: para habilitar IntelliSense para los miembros recién agregados, en Explorador de soluciones, abra el menú contextual del proyecto SampleComponent y, a continuación, elija Compilar.

  6. En Explorador de soluciones, en el proyecto de JavaScript, abra el menú contextual de Referencias y, a continuación, elija Agregar referencia para abrir el Administrador de referencias. Elige Proyectos y, a continuación, Solución. Active la casilla del proyecto SampleComponent y elija Aceptar para agregar una referencia.

Llamada al componente desde JavaScript

Para usar el tipo de Windows Runtime desde JavaScript, agregue el código siguiente en la función anónima en el archivo default.js (en la carpeta js del proyecto) que proporciona la plantilla de Visual Studio. Debe ir después del controlador de eventos app.oncheckpoint y antes de la llamada 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;
}

Observe que la primera letra de cada nombre de miembro cambia de mayúsculas a minúsculas. Esta transformación forma parte de la compatibilidad que proporciona JavaScript para habilitar el uso natural de Windows Runtime. Los espacios de nombres y los nombres de clase tienen mayúsculas y minúsculas Pascal. Los nombres de miembro tienen mayúsculas y minúsculas, excepto los nombres de evento, que están en minúsculas. Consulte Uso de Windows Runtime en JavaScript. Las reglas para el uso de mayúsculas y minúsculas pueden resultar confusas. Normalmente, una serie de letras mayúsculas iniciales aparece como minúsculas, pero si tres letras mayúsculas van seguidas de una letra minúscula, solo las dos primeras letras aparecen en minúsculas: por ejemplo, un miembro denominado IDStringKind aparece como idStringKind. En Visual Studio, puedes compilar el proyecto de componente de Windows Runtime y, a continuación, usar IntelliSense en el proyecto de JavaScript para ver el uso correcto de mayúsculas y minúsculas.

De forma similar, .NET proporciona compatibilidad para habilitar el uso natural de Windows Runtime en código administrado. Esto se describe en las secciones posteriores de este artículo y en los artículos Componentes de Windows Runtime con C# y Visual Basic y compatibilidad de .NET con aplicaciones para UWP y Windows Runtime.

Creación de una interfaz de usuario sencilla

En el proyecto de JavaScript, abra el archivo default.html y actualice el cuerpo como se muestra en el código siguiente. Este código incluye el conjunto completo de controles de la aplicación de ejemplo y especifica los nombres de función para los eventos click.

Nota Cuando se ejecuta por primera vez la aplicación, solo se admiten el botón Basics1 y 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>

En el proyecto de JavaScript, en la carpeta css, abra default.css. Modifique la sección body como se muestra y agregue estilos para controlar el diseño de botones y la colocación del texto de salida.

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

Ahora agregue el código de registro del agente de escucha de eventos agregando una cláusula then a la llamada processAll en app.onactivated en default.js. Reemplace la línea de código existente que llama a setPromise y cámbiela por el código siguiente:

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

Esta es una manera mejor de agregar eventos a controles HTML que agregar un controlador de eventos click directamente en HTML. Consulta Crear una aplicación "Hola mundo" (JS).

Compilar y ejecutar la aplicación

Antes de compilar, cambie la plataforma de destino de todos los proyectos a Arm, x64 o x86, según corresponda para el equipo.

Para compilar y ejecutar la solución, elija la tecla F5. (Si recibe un mensaje de error en tiempo de ejecución que indica que SampleComponent no está definido, falta la referencia al proyecto de biblioteca de clases).

Visual Studio compila primero la biblioteca de clases y, a continuación, ejecuta una tarea de MSBuild que ejecuta Winmdexp.exe (Herramienta de exportación de metadatos de Windows Runtime) para crear el componente de Windows Runtime. El componente se incluye en un archivo .winmd que contiene el código administrado y los metadatos de Windows que describen el código. WinMdExp.exe genera mensajes de error de compilación al escribir código que no es válido en un componente de Windows Runtime y los mensajes de error se muestran en el IDE de Visual Studio. Visual Studio agrega el componente al paquete de aplicación (archivo .appx) para la aplicación para UWP y genera el manifiesto adecuado.

Elija el botón Aspectos básicos 1 para asignar el valor devuelto del método GetAnswer estático al área de salida, cree una instancia de la clase Example y muestre el valor de su propiedad SampleProperty en el área de salida. La salida se muestra aquí:

"The answer is 42."
0

Elija el botón Aspectos básicos 2 para incrementar el valor de la propiedad SampleProperty y mostrar el nuevo valor en el área de salida. Los tipos primitivos, como cadenas y números, se pueden usar como tipos de parámetro y tipos devueltos, y se pueden pasar entre código administrado y JavaScript. Dado que los números de JavaScript se almacenan en formato de punto flotante de precisión doble, se convierten en tipos numéricos de .NET Framework.

Nota De forma predeterminada, solo puede establecer puntos de interrupción en el código JavaScript. Para depurar el código de Visual Basic o C#, consulte Creación de componentes de Windows Runtime en C# y Visual Basic.

Para detener la depuración y cerrar la aplicación, cambie de la aplicación a Visual Studio y elija Mayús+F5.

Uso de Windows Runtime desde JavaScript y código administrado

Se puede llamar a Windows Runtime desde JavaScript o código administrado. Los objetos de Windows Runtime se pueden pasar entre los dos y los eventos se pueden controlar desde cualquier lado. Sin embargo, las formas en que usas los tipos de Windows Runtime en los dos entornos difieren en algunos detalles, ya que JavaScript y .NET admiten Windows Runtime de forma diferente. En el ejemplo siguiente se muestran estas diferencias mediante la clase Windows.Foundation.Collections.PropertySet . En este ejemplo, creará una instancia de la colección PropertySet en código administrado y registrará un controlador de eventos para realizar un seguimiento de los cambios de la colección. A continuación, agregue código JavaScript que obtenga la colección, registre su propio controlador de eventos y use la colección. Por último, agregue un método que realice cambios en la colección desde código administrado y muestre JavaScript que controla una excepción administrada.

Importante En este ejemplo, el evento se desencadena en el subproceso de la interfaz de usuario. Si desencadena el evento desde un subproceso en segundo plano, por ejemplo, en una llamada asincrónica, deberá realizar algún trabajo adicional para que JavaScript controle el evento. Para obtener más información, consulta Generar eventos en componentes de Windows Runtime.

En el proyecto SampleComponent, agregue una nueva clase sellada pública (clase Public NotInheritable en Visual Basic) denominada PropertySetStats. La clase encapsula una colección PropertySet y controla su evento MapChanged. El controlador de eventos realiza un seguimiento del número de cambios de cada tipo que se producen y el método DisplayStats genera un informe con formato HTML. Observe la instrucción using adicional (instrucción Imports en Visual Basic); tenga cuidado de agregarlo a las instrucciones using existentes en lugar de sobrescribirlas.

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

El controlador de eventos sigue el conocido patrón de eventos de .NET Framework, excepto que el remitente del evento (en este caso, el objeto PropertySet) se convierte en la cadena IObservableMap, interfaz de objeto> (IObservableMap(Of String, Object) en Visual Basic), que es una creación de instancias de la interfaz de Windows Runtime IObservableMap<<K, V>. (Puede convertir el remitente a su tipo si es necesario). Además, los argumentos de evento se presentan como una interfaz en lugar de como un objeto .

En el archivo default.js, agregue la función Runtime1 como se muestra. Este código crea un objeto PropertySetStats, obtiene su colección PropertySet y agrega su propio controlador de eventos, la función onMapChanged, para controlar el evento MapChanged. Después de realizar cambios en la colección, runtime1 llama al método DisplayStats para mostrar un resumen de los tipos de cambio.

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

La forma en que controla los eventos de Windows Runtime en JavaScript es muy diferente de la forma en que los controla en el código de .NET Framework. El controlador de eventos de JavaScript solo toma un argumento. Al ver este objeto en el depurador de Visual Studio, la primera propiedad es el remitente. Los miembros de la interfaz del argumento de evento también aparecen directamente en este objeto.

Para ejecutar la aplicación, elija la tecla F5. Si la clase no está sellada, recibirá el mensaje de error "Exporting unsealed type "SampleComponent.Example" (Exportar el tipo no sellado "SampleComponent.Example" no se admite actualmente). Por favor, marcalo como sellado".

Elija el botón Runtime 1 (Tiempo de ejecución 1 ). El controlador de eventos muestra los cambios a medida que se agregan o cambian los elementos, y al final se llama al método DisplayStats para generar un resumen de los recuentos. Para detener la depuración y cerrar la aplicación, vuelva a Visual Studio y elija Mayús+F5.

Para agregar dos elementos más a la colección PropertySet desde código administrado, agregue el código siguiente a la clase 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

Este código resalta otra diferencia en la forma de usar los tipos de Windows Runtime en los dos entornos. Si escribe este código usted mismo, observará que IntelliSense no muestra el método de inserción que usó en el código JavaScript. En su lugar, muestra el método Add que se suele ver en las colecciones de .NET. Esto se debe a que algunas interfaces de colección usadas normalmente tienen nombres diferentes pero funcionalidades similares en Windows Runtime y .NET. Cuando se usan estas interfaces en código administrado, aparecen como equivalentes de .NET Framework. Esto se describe en componentes de Windows Runtime con C# y Visual Basic. Cuando usas las mismas interfaces en JavaScript, el único cambio de Windows Runtime es que las letras mayúsculas al principio de los nombres de miembro se vuelven minúsculas.

Por último, para llamar al método AddMore con control de excepciones, agregue la función 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();
}

Agregue el código de registro del controlador de eventos de la misma manera que hizo anteriormente.

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

Para ejecutar la aplicación, elija la tecla F5. Elija Runtime 1 y, a continuación, Runtime 2. El controlador de eventos de JavaScript informa del primer cambio en la colección. Sin embargo, el segundo cambio tiene una clave duplicada. Los usuarios de diccionarios de .NET Framework esperan que el método Add produzca una excepción y eso es lo que sucede. JavaScript controla la excepción de .NET.

Nota No puede mostrar el mensaje de la excepción desde código JavaScript. El texto del mensaje se reemplaza por un seguimiento de pila. Para obtener más información, vea "Iniciar excepciones" en Creación de componentes de Windows Runtime en C# y Visual Basic.

Por el contrario, cuando JavaScript llamó al método insert con una clave duplicada, se cambió el valor del elemento. Esta diferencia en el comportamiento se debe a las distintas formas en que JavaScript y .NET admiten Windows Runtime, como se explica en Componentes de Windows Runtime con C# y Visual Basic.

Devolver tipos administrados desde el componente

Como se ha explicado anteriormente, puede pasar tipos nativos de Windows Runtime de vuelta y adelante libremente entre el código de JavaScript y el código de C# o Visual Basic. La mayoría de las veces, los nombres de tipo y los nombres de miembro serán los mismos en ambos casos (excepto que los nombres de miembro comienzan con letras minúsculas en JavaScript). Sin embargo, en la sección anterior, la clase PropertySet parecía tener miembros diferentes en código administrado. (Por ejemplo, en JavaScript llamó al método insert y en el código .NET que llamó al método Add). En esta sección se explora la forma en que esas diferencias afectan a los tipos de .NET Framework pasados a JavaScript.

Además de devolver tipos de Windows Runtime creados en el componente o pasados al componente desde JavaScript, puedes devolver un tipo administrado, creado en código administrado, a JavaScript como si fuera el tipo de Windows Runtime correspondiente. Incluso en el primer ejemplo sencillo de una clase en tiempo de ejecución, los parámetros y los tipos de valor devuelto de los miembros eran tipos primitivos de Visual Basic o C#, que son tipos de .NET Framework. Para mostrar esto para las colecciones, agregue el código siguiente a la clase Example para crear un método que devuelva un diccionario genérico de cadenas, indizado por enteros:

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

Tenga en cuenta que el diccionario debe devolverse como una interfaz implementada por Dictionary<TKey, TValue> y que se asigna a una interfaz de Windows Runtime. En este caso, la interfaz es IDictionary<int, string> (IDictionary(Of Integer, String) en Visual Basic). Cuando el tipo de Windows Runtime IMap<int, la cadena> se pasa al código administrado, aparece como IDictionary<int, string> y la inversa es true cuando el tipo administrado se pasa a JavaScript.

Importante Cuando un tipo administrado implementa varias interfaces, JavaScript usa la interfaz que aparece primero en la lista. Por ejemplo, si devuelve Dictionary<int, string> a código JavaScript, aparece como IDictionary<int, cadena> independientemente de la interfaz que especifique como tipo de valor devuelto. Esto significa que si la primera interfaz no incluye un miembro que aparece en interfaces posteriores, ese miembro no es visible para JavaScript.

 

Para probar el nuevo método y usar el diccionario, agregue las funciones returns1 y 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>";
}

Agregue el código de registro de eventos al mismo bloque que el otro código de registro de eventos:

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

Hay algunas cosas interesantes que observar sobre este código JavaScript. En primer lugar, incluye una función showMap para mostrar el contenido del diccionario en HTML. En el código de showMap, observe el patrón de iteración. En .NET, no hay ningún método First en la interfaz IDictionary genérica y un método Size devuelve el tamaño mediante una propiedad Count en lugar de mediante un método Size. Para JavaScript, IDictionary<int, la cadena> parece ser el tipo de Windows Runtime IMap<int, string>. (Consulte el IMap<K,V> interface.)

En la función returns2, como en los ejemplos anteriores, JavaScript llama al método Insert (insert en JavaScript) para agregar elementos al diccionario.

Para ejecutar la aplicación, elija la tecla F5. Para crear y mostrar el contenido inicial del diccionario, elija el botón Devuelve 1 . Para agregar dos entradas más al diccionario, elija el botón Devuelve 2 . Tenga en cuenta que las entradas se muestran en orden de inserción, como cabría esperar de Dictionary<TKey, TValue>. Si quiere que se ordenen, puede devolver un int SortedDictionary<, cadena> de GetMapOfNames. (La clase PropertySet usada en ejemplos anteriores tiene una organización interna diferente de Dictionary<TKey, TValue>.)

Por supuesto, JavaScript no es un lenguaje fuertemente tipado, por lo que el uso de colecciones genéricas fuertemente tipadas puede dar lugar a algunos resultados sorprendentes. Vuelva a elegir el botón Devuelve 2 . JavaScript obliga a coerce los "7" a un valor numérico 7 y el número 7 almacenado en ct en una cadena. Y coerce la cadena "cuarenta" a cero. Pero eso es sólo el principio. Elija el botón Devuelve 2 varias veces más. En el código administrado, el método Add generaría excepciones de clave duplicadas, incluso si los valores se convierten en los tipos correctos. En cambio, el método Insert actualiza el valor asociado a una clave existente y devuelve un valor booleano que indica si se agregó una nueva clave al diccionario. Este es el motivo por el que el valor asociado a la clave 7 sigue cambiando.

Otro comportamiento inesperado: si pasa una variable de JavaScript sin asignar como argumento de cadena, lo que obtiene es la cadena "sin definir". En resumen, tenga cuidado al pasar tipos de colección de .NET Framework al código javaScript.

Nota Si tiene grandes cantidades de texto para concatenar, puede hacerlo de forma más eficaz moviendo el código a un método de .NET Framework y usando la clase StringBuilder, como se muestra en la función showMap.

Aunque no puedes exponer tus propios tipos genéricos desde un componente de Windows Runtime, puedes devolver colecciones genéricas de .NET Framework para clases de Windows Runtime mediante código como el siguiente:

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> implementa IList<T>, que aparece como el tipo de Windows Runtime IVector<T> en JavaScript.

Declaración de eventos

Puede declarar eventos mediante el patrón de eventos estándar de .NET Framework u otros patrones usados por Windows Runtime. .NET Framework admite la equivalencia entre el delegado TEventArgs System.EventHandler<y el delegado de T EventHandler<> de Windows Runtime, por lo que usar EventHandler<TEventArgs> es una buena manera de implementar el patrón estándar de .NET Framework.> Para ver cómo funciona esto, agregue el siguiente par de clases al proyecto 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

Cuando expones un evento en Windows Runtime, la clase de argumento de evento hereda de System.Object. No hereda de System.EventArgs, como lo haría en .NET, porque EventArgs no es un tipo de Windows Runtime.

Si declaras descriptores de acceso de eventos personalizados para el evento (palabra clave Custom en Visual Basic), debes usar el patrón de eventos de Windows Runtime. Consulta Eventos personalizados y descriptores de acceso de eventos en componentes de Windows Runtime.

Para controlar el evento Test, agregue la función events1 a default.js. La función events1 crea una función de controlador de eventos para el evento Test e invoca inmediatamente el método OnTest para generar el evento. Si coloca un punto de interrupción en el cuerpo del controlador de eventos, puede ver que el objeto pasado al parámetro único incluye el objeto de origen y ambos miembros de 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);
}

Agregue el código de registro de eventos al mismo bloque que el otro código de registro de eventos:

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

Exposición de operaciones asincrónicas

.NET Framework tiene un amplio conjunto de herramientas para el procesamiento asincrónico y el procesamiento paralelo, en función de las clases task y generic Task TResult>.< Para exponer el procesamiento asincrónico basado en tareas en un componente de Windows Runtime, usa las interfaces de Windows Runtime IAsyncAction, IAsyncActionWithProgress TProgress<>, IAsyncOperation<TResult> e IAsyncOperationWithProgress TResult, TProgress.<> (En Windows Runtime, las operaciones devuelven resultados, pero las acciones no).

En esta sección se muestra una operación asincrónica cancelable que notifica el progreso y devuelve los resultados. El método GetPrimesInRangeAsync usa la clase AsyncInfo para generar una tarea y conectar sus características de cancelación y informes de progreso a un objeto WinJS.Promise. Comience agregando el método GetPrimesInRangeAsync a la clase de ejemplo:

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 es un buscador de números primos muy simple y eso es por diseño. El enfoque aquí es implementar una operación asincrónica, por lo que la simplicidad es importante y una implementación lenta es una ventaja cuando se muestra la cancelación. GetPrimesInRangeAsync encuentra primos por fuerza bruta: divide un candidato por todos los enteros que son menores o iguales que su raíz cuadrada, en lugar de usar solo los números primos. Paso a paso por este código:

  • Antes de iniciar una operación asincrónica, realice actividades de limpieza, como validar parámetros e iniciar excepciones para la entrada no válida.

  • La clave de esta implementación es el método AsyncInfo.Run<TResult, TProgress>(Func<CancellationToken, IProgress TProgress<>, Task<TResult>>) y el delegado que es el único parámetro del método. El delegado debe aceptar un token de cancelación y una interfaz para notificar el progreso y debe devolver una tarea iniciada que use esos parámetros. Cuando JavaScript llama al método GetPrimesInRangeAsync, se producen los pasos siguientes (no necesariamente en el orden indicado aquí):

    • El objeto WinJS.Promise proporciona funciones para procesar los resultados devueltos, reaccionar a la cancelación y controlar los informes de progreso.

    • El método AsyncInfo.Run crea un origen de cancelación y un objeto que implementa la interfaz T> de IProgress<. Para el delegado, pasa un token CancellationToken desde el origen de cancelación y la interfaz IProgress<T> .

      Nota Si el objeto Promise no proporciona una función para reaccionar a la cancelación, AsyncInfo.Run sigue pasando un token cancelable y la cancelación todavía puede producirse. Si el objeto Promise no proporciona una función para controlar las actualizaciones de progreso, AsyncInfo.Run sigue suministrando un objeto que implementa IProgress<T>, pero se omiten sus informes.

    • El delegado usa el método Task.Run<TResult>(Func<TResult>, CancellationToken) para crear una tarea iniciada que use el token y la interfaz de progreso. La función lambda proporciona el delegado para la tarea iniciada que calcula el resultado deseado. Se va a ofrecer más información sobre esto en un momento.

    • El método AsyncInfo.Run crea un objeto que implementa la interfaz IAsyncOperationWithProgress<TResult, TProgress>, conecta el mecanismo de cancelación de Windows Runtime con el origen del token y conecta la función de informes de progreso del objeto Promise con la interfaz T> de IProgress<.

    • La interfaz IAsyncOperationWithProgress<TResult, TProgress> se devuelve a JavaScript.

  • La función lambda representada por la tarea iniciada no toma ningún argumento. Dado que es una función lambda, tiene acceso al token y a la interfaz IProgress. Cada vez que se evalúa un número candidato, la función lambda:

    • Comprueba si se ha alcanzado el siguiente punto porcentual de progreso. Si lo tiene, la función lambda llama a IProgress<T>. Método de informe y el porcentaje se pasa a la función que el objeto Promise especificó para notificar el progreso.
    • Usa el token de cancelación para producir una excepción si se ha cancelado la operación. Si se ha llamado al método IAsyncInfo.Cancel (que el método IAsyncOperationWithProgress<TResult, la interfaz TProgress> hereda), se ha llamado a la conexión que el método AsyncInfo.Run configuró garantiza que se notifique el token de cancelación.
  • Cuando la función lambda devuelve la lista de números primos, la lista se pasa a la función que el objeto WinJS.Promise especificó para procesar los resultados.

Para crear la promesa de JavaScript y configurar el mecanismo de cancelación, agregue las funciones asyncRun y 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();
}

No olvide que el código de registro de eventos es el mismo que antes.

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

Al llamar al método asincrónico GetPrimesInRangeAsync, la función asyncRun crea un objeto WinJS.Promise. El método then del objeto toma tres funciones que procesan los resultados devueltos, reaccionan a los errores (incluida la cancelación) y controlan los informes de progreso. En este ejemplo, los resultados devueltos se imprimen en el área de salida. Cancelación o finalización restablece los botones que inician y cancelan la operación. Los informes de progreso actualizan el control de progreso.

La función asyncCancel simplemente llama al método cancel del objeto WinJS.Promise.

Para ejecutar la aplicación, elija la tecla F5. Para iniciar la operación asincrónica, elija el botón Async . Lo que sucede a continuación depende de la rapidez con la que se encuentra el equipo. Si la barra de progreso se comprime hasta la finalización antes de que tenga tiempo de parpadear, aumente el tamaño del número inicial que se pasa a GetPrimesInRangeAsync por uno o más factores de diez. Puede ajustar la duración de la operación aumentando o disminuyendo el recuento de números que se van a probar, pero agregar ceros en medio del número inicial tendrá un impacto mayor. Para cancelar la operación, elija el botón Cancelar asincrónico .