Tutorial: Crear un componente simple en C# o Visual Basic y llamarlo desde JavaScript

Este tutorial muestra cómo puedes usar .NET Framework con Visual Basic o C# para crear tus propios tipos de Windows en tiempo de ejecución empaquetados en un componente de Windows en tiempo de ejecución y cómo llamar al componente desde la aplicación de la Tienda Windows que se compiló para Windows que usa JavaScript.

Con Visual Studio resulta muy sencillo agregar un componente de Windows en tiempo de ejecución escrito con C# o Visual Basic a la aplicación, así como crear los tipos de Windows en tiempo de ejecución que se pueden llamar desde JavaScript. Internamente, los tipos de Windows en tiempo de ejecución pueden usar cualquier funcionalidad de .NET Framework que se permita en una aplicación de la Tienda Windows. (Para obtener más información, consulta Crear componentes de Windows en tiempo de ejecución en C# y Visual Basic y Información general de .NET para aplicaciones de la Tienda Windows). Externamente, los miembros de los tipos pueden exponer solo los tipos de Windows en tiempo de ejecución para sus parámetros y valores devueltos. Cuando se compila una solución, Visual Studio compila el proyecto del Componente Windows en tiempo de ejecución de .NET Framework y después ejecuta un paso de compilación que crea un archivo de metadatos de Windows (.winmd). Este es el componente de Windows en tiempo de ejecución, que Visual Studio va a incluir en tu aplicación.

NotaNota

.NET Framework asigna automáticamente algunos tipos de .NET Framework de uso habitual, como tipos de datos primitivos y tipos de colección, para los equivalentes de Windows en tiempo de ejecución.Estos tipos de .NET Framework pueden usarse en la interfaz pública de un componente de Windows en tiempo de ejecución y se mostrarán a los usuarios del componente como los tipos de Windows en tiempo de ejecución correspondientes.Consulta Crear componentes de Windows en tiempo de ejecución en C# y Visual Basic.

En este tutorial se muestran las tareas siguientes. Después de haber completado la primera sección, donde se configura la aplicación de la Tienda Windows con JavaScript, puedes completar las secciones restantes en cualquier orden.

  • Crear una clase simple de Windows en tiempo de ejecución

  • Usar Windows en tiempo de ejecución desde JavaScript y código administrado

  • Devolver tipos administrados desde el componente

  • Declarar eventos

  • Exponer operaciones asincrónicas

Requisitos previos:

Necesitas lo siguiente para poder llevar a cabo este tutorial:

  • Windows 8 (o posterior)

  • Microsoft Visual Studio 2012 o Microsoft Visual Studio Express 2012 para Windows 8 (o posterior)

Crear una clase simple de Windows en tiempo de ejecución

En esta sección se crea una aplicación de la Tienda Windows compilada para Windows que usa JavaScript y se agrega un proyecto de Componente de Windows en tiempo de ejecución de Visual Basic o C#. Se muestra cómo definir un tipo administrado de Windows en tiempo de ejecución, crear una instancia del tipo desde JavaScript y llamar a los miembros estáticos y de instancia. La representación visual de la aplicación de ejemplo tiene un aspecto deliberadamente sencillo para no desviar la atención del componente. No dudes en adornarla.

  1. En Visual Studio, crea un nuevo proyecto de JavaScript: en la barra de menús, elige Archivo, Nuevo, Proyecto (en Visual Studio Express, elige Archivo, Nuevo proyecto). En la sección Plantillas instaladas del cuadro de diálogo Nuevo proyecto, elige JavaScript y Tienda Windows. (Si la Tienda Windows no está disponible, asegúrate de que estés usando Windows 8 o posterior). Elige la plantilla Aplicación vacía y escribe SampleApp como nombre del proyecto.

  2. Crea el proyecto de componente: en el Explorador de soluciones, abre el menú contextual para la solución SampleApp, elige Agregar y, a continuación, Nuevo proyecto para agregar un nuevo proyecto de C# o Visual Basic a la solución. En la sección de Plantillas instaladas del cuadro de diálogo Agregar nuevo proyecto, elige Visual Basic o Visual C# y, después, elige Tienda Windows. Elija la plantilla de Componente Windows en tiempo de ejecución y escriba SampleComponent como nombre del proyecto.

  3. Cambia el nombre de la clase a Ejemplo. Ten en cuenta que, de forma predeterminada, la clase se marca como public sealed (Public NotInheritable en Visual Basic). Todas las clases de Windows en tiempo de ejecución que expongas desde tu componente deben estar selladas.

  4. Agrega dos miembros simples a la clase, a un método static (método Shared 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 el Explorador de soluciones, abre el menú contextual para el proyecto SampleComponent y, a continuación, elige Compilación.

  6. En el Explorador de soluciones, en el proyecto de JavaScript, abre el menú contextual para Referencias y, a continuación, elige Agregar referencia para abrir el Administrador de referencias. Elige Solución y, después, Proyectos. Activa la casilla para el proyecto SampleComponent y elige Aceptar para agregar una referencia.

Hh779077.collapse_all(es-es,VS.120).gifLlamar al componente desde JavaScript

Para usar el tipo de Windows en tiempo de ejecución desde JavaScript, agregue el código siguiente tras la función anónima al final del archivo default.js (en la carpeta para js del proyecto) que proporciona la plantilla de Visual Studio:

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

Ten en cuenta que la primera letra de cada nombre de miembro cambia de mayúsculas a minúsculas. Esta transformación es parte de la compatibilidad que ofrece JavaScript para habilitar el uso natural de Windows en tiempo de ejecución. Los espacios de nombres y los nombres de las clases usan la grafía Pascal. Los nombres de miembro usan la grafía Camel, salvo para los nombres de evento que están en minúsculas. Consulte Utilizar Windows en tiempo de ejecución en JavaScript. Las reglas para la grafía Camel pueden resultar confusas. Una serie de letras mayúsculas iniciales aparece normalmente en minúscula, pero si hay tres letras en mayúsculas seguidas de una minúscula, solo las dos primeras letras estarán en minúsculas: por ejemplo, un miembro denominado IDStringKind aparece como idStringKind. En Visual Studio, puedes compilar el proyecto de Windows en tiempo de ejecución y después usar IntelliSense en el proyecto de JavaScript de ver la grafía correcta.

De forma similar, .NET Framework proporciona compatibilidad para habilitar el uso natural de Windows en tiempo de ejecución en código administrado. Esto se explica en secciones posteriores de este artículo, y en los artículos Crear componentes de Windows en tiempo de ejecución en C# y Visual Basic y .NET Framework Support for Windows Store Apps and Windows Runtime.

Hh779077.collapse_all(es-es,VS.120).gifCrear una interfaz de usuario sencilla

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

Nota de precauciónPrecaución

Al ejecutar la aplicación por primera vez, solo se admiten los botones Basics1 y Basics2.

<body>
    <div id="buttons">
        <button onclick="basics1();">Basics 1</button>
        <button onclick="basics2();">Basics 2</button>

        <button onclick="runtime1();">Runtime 1</button>
        <button onclick="runtime2();">Runtime 2</button>

        <button onclick="returns1();">Returns 1</button>
        <button onclick="returns2();">Returns 2</button>

        <button onclick="events1();">Events 1</button>

        <button id="btnAsync" onclick="asyncRun();">Async</button>
        <button id="btnCancel" onclick="asyncCancel();" 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 de CSS, abre default.css. Modifica la sección de body tal como se muestra y agrega 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;
}

Hh779077.collapse_all(es-es,VS.120).gifCompilar y ejecutar la aplicación

Para compilar y ejecutar la solución, elige la tecla F5. (Si obtienes 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).

En primer lugar, Visual Studio compila la biblioteca de clases y después ejecuta una tarea de MSBuild que ejecute Winmdexp.exe (Herramienta de exportación de metadatos de Windows en tiempo de ejecución) para crear el componente de Windows en tiempo de ejecución. El componente se incluye en un archivo .winmd que contiene código administrado y metadatos de Windows que describen el código. WinMdExp.exe genera mensajes de error de compilación cuando escribes código que no es válido en un componente de Windows en tiempo de ejecución y los mensajes de error se muestran en el IDE de Visual Studio. Visual Studio agrega el componente al paquete de la aplicación (archivo de .appx) para la aplicación de la Tienda Windows y genera el manifiesto adecuado.

Elige el botón Basics 1 para asignar el valor de devolución desde el método estático GetAnswer al área de resultados, crear una instancia de la clase Example y mostrar el valor de la propiedad SampleProperty en el área de resultados. El resultado se muestra aquí:

"The answer is 42."
0

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

NotaNota

De forma predeterminada, puedes establecer puntos de interrupción solo en el código de JavaScript.Para depurar el código de Visual Basic o C#, consulta Crear componentes de Windows en tiempo de ejecución en C# y Visual Basic.

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

Usar Windows en tiempo de ejecución desde JavaScript y código administrado

Windows en tiempo de ejecución se puede invocar desde JavaScript o código administrado. Los objetos Windows en tiempo de ejecución se pueden pasar entre ambos y se pueden controlar eventos desde cualquiera de los dos. Sin embargo, la forma de usar tipos de Windows en tiempo de ejecución en los dos entornos es distinta en algunos detalles, porque JavaScript y .NET Framework admiten Windows en tiempo de ejecución de manera diferente. En el ejemplo siguiente se muestran estas diferencias, mediante la clase Windows.Foundation.Collections.PropertySet. En este ejemplo, se crea una instancia de la colección de PropertySet en código administrado y se registra un controlador de eventos para realizar el seguimiento de los cambios en la colección. A continuación, puedes agregar código de JavaScript que obtiene la colección, registra su propio controlador de eventos y usa la colección. Por último, puedes agregar un método que realiza cambios en la colección desde el código administrado y muestra JavaScript controlando una excepción administrada.

Nota importanteImportante

En este ejemplo, el evento se desencadena en el subproceso de la interfaz de usuario.Si desencadenas el evento desde un subproceso en segundo plano, por ejemplo en una llamada asincrónica, necesitarás hacer alguna tarea adicional para que JavaScript controle el evento.Para obtener más información, consulta Generar eventos de componentes de Windows en tiempo de ejecución.

En el proyecto SampleComponent, agrega una nueva clase public sealed (clase Public NotInheritable en Visual Basic) denominada PropertySetStats. La clase contiene una colección PropertySet y controla el evento MapChanged. El controlador de eventos lleva el seguimiento del número de cambios que se producen en cada tipo y el método DisplayStats muestra un informe con formato HTML. Ten en cuenta la instrucción adicional using (instrucción Imports en Visual Basic); asegúrate de agregarla a las instrucciones existentes using 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 lleva un seguimiento del patrón de eventos de .NET Framework habitual, salvo que el remitente del evento (en este caso, el objeto PropertySet) se convierte en la interfaz IObservableMap<string, object> (IObservableMap(Of String, Object) en Visual Basic), que es una instancia de la interfaz de Windows en tiempo de ejecución IObservableMap<K, V>. (Puedes convertir el remitente a su tipo, en caso de que sea necesario). Además, los argumentos se muestran como una interfaz en lugar de como un objeto.

En el archivo default.js, agrega la función Runtime1 tal como se describe. Este código crea un objeto PropertySetStats, obtiene su colección de 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 cambios.

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

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

La forma de controlar los eventos de Windows en tiempo de ejecución en JavaScript es muy diferente la forma de controlarlos en código de .NET Framework. El controlador de eventos de JavaScript solo usa un argumento. Cuando ves 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, elige la tecla F5. Si la clase no está sellada, recibirás un mensaje de error donde se indica que en estos momentos no se admite exportar el tipo sin que 'SampleComponent.Example' esté sellada y que hay que marcarla como sellada.

Elige el botón Runtime 1. El controlador de eventos muestra cambios a medida que se agregan o se cambian los elementos y, al final, el método DisplayStats se llama para mostrar un resumen de recuentos. Para detener la depuración y cerrar la aplicación, vuelve a pasar a Visual Studio y elige Mayús+F5.

Para agregar dos elementos más a la colección de PropertySet desde código administrado, agrega 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 pone de manifiesto otra diferencia en la forma de usar los tipos de Windows en tiempo de ejecución en los dos entornos. Si escribes tú mismo este código, observarás que IntelliSense no muestra el método insert que usaste en el código de JavaScript. En su lugar, muestra el método Add que se observa habitualmente en colecciones de .NET Framework. Esto se debe a que algunas interfaces de colocación que se usan habitualmente tienen nombres diferentes pero funcionalidad similar en Windows en tiempo de ejecución y en .NET Framework. Cuando se usan estas interfaces en código administrado, se muestran como sus equivalentes en .NET Framework. Esto se describe en Crear componentes de Windows en tiempo de ejecución en C# y Visual Basic. Cuando se usan las mismas interfaces en JavaScript, el único cambio respecto a Windows en tiempo de ejecución es que las letras mayúsculas al principio de los nombres de miembro pasan a minúsculas.

Por último, para llamar al método AddMore con control de excepciones, agrega la función runtime2 a default.js.

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

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

Para ejecutar la aplicación, elige la tecla F5. Elige Runtime 1 y, después, Runtime 2. El controlador de eventos de JavaScript notifica el primer cambio en la colección. El segundo cambio, sin embargo, tiene una clave duplicada. Los usuarios de los diccionarios de .NET Framework esperan que el método Add produzca una excepción, que es lo que ocurre. JavaScript controla la excepción de .NET Framework.

NotaNota

No puedes mostrar el mensaje de la excepción desde código de JavaScript.El texto de mensaje se reemplaza por un seguimiento de la pila.Para obtener más información, consulta "Producir excepciones" en Crear componentes de Windows en tiempo de ejecución en C# y Visual Basic.

Por el contrario, cuando JavaScript llamó al método insert con una clave duplicada, cambió el valor del elemento. Esta diferencia de comportamiento se debe a las distintas maneras en que JavaScript y .NET Framework prestan compatibilidad a Windows en tiempo de ejecución, tal como se describe en Crear componentes de Windows en tiempo de ejecución en C# y Visual Basic.

Devolver tipos administrados desde el componente

Según lo descrito anteriormente, puedes pasar los tipos de Windows en tiempo de ejecución nativos libremente entre el código de JavaScript y el código de C# o Visual Basic. Por lo general, los nombres de tipos y los nombres de miembro serán los mismos en ambos casos (salvo que los nombres de miembro comienzan con minúscula en JavaScript). Sin embargo, en la sección anterior, la clase PropertySet parecía tener distintos miembros en código administrado. (Por ejemplo, en JavaScript se llamó al método insert y en el código de .NET Framework se llamó al método Add). En esta sección se explora la manera en que esas diferencias repercuten en los tipos de .NET Framework que se han pasado a JavaScript.

Además de devolver los tipos de Windows en tiempo de ejecución que se crearon en el componente o que se pasaron al componente desde JavaScript, puedes devolver un tipo administrado, que se haya creado en código administrado, a JavaScript como si fuera el tipo de Windows en tiempo de ejecución correspondiente. Incluso en el primer y sencillo ejemplo de una clase en tiempo de ejecución, los parámetros y tipos devueltos de los miembros eran tipos primitivos de Visual Basic o C#, que son tipos de .NET Framework. Para mostrar esto en las colecciones, agrega el código siguiente a la clase Example, con el fin de 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

Ten en cuenta que el diccionario debe devolverse como una interfaz implementada mediante Dictionary<TKey, TValue> y que se asigne a una interfaz de Windows en tiempo de ejecución. En este caso, la interfaz es IDictionary<int, string> (IDictionary(Of Integer, String) en Visual Basic). Cuando se pasa el tipo IMap<int, string> de Windows en tiempo de ejecución a código administrado, parece IDictionary<int, string>, y sucede a la inversa cuando se pasa el tipo administrado a JavaScript.

Nota importanteImportante

Cuando un tipo administrado implementa varias interfaces, JavaScript utiliza la primera interfaz de la lista.Por ejemplo, si devuelves Dictionary<int, string> al código de JavaScript, aparecerá como IDictionary<int, string> con independencia de la interfaz que especifiques como tipo de valor devuelto.Esto significa que si la primera interfaz no incluye un miembro que aparece en interfaces posteriores, dicho miembro no es visible para JavaScript.

Para probar el nuevo método y usar el diccionario, agrega las funciones returns1 y returns2 a default.js:

var names;

function returns1() {
    names = SampleComponent.Example.getMapOfNames();
    document.getElementById('output').innerHTML = showMap(names);
}
 
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>";
}

Hay diversos aspectos interesantes a tener en cuenta acerca de este código de JavaScript. En primer lugar, incluye una función de showMap para mostrar el contenido del diccionario en HTML. En el código para showMap, observa el patrón de iteración. En .NET Framework, no hay ningún método First en la interfaz genérica IDictionary y el tamaño lo devuelve una propiedad Count en lugar de un método Size. En JavaScript, IDictionary<int, string> parece ser IMap<int, string> del tipo de Windows en tiempo de ejecución. (Consulte la interfaz IMap<K,V>).

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

Para ejecutar la aplicación, elige la tecla F5. Para crear y mostrar el contenido inicial del diccionario, elige el botón Returns 1. Para agregar dos entradas más al diccionario, elige el botón Returns 2. Ten en cuenta que las entradas se muestran por orden de inserción, tal como se podría esperar de Dictionary<TKey, TValue>. Si quieres ordenarlas, puedes devolver SortedDictionary<int, string> desde GetMapOfNames. (La clase PropertySet usada en ejemplos anteriores tiene otra organización interna respecto a Dictionary<TKey, TValue>).

Evidentemente, JavaScript no es un lenguaje fuertemente tipado, por lo que utilizar colecciones genéricas fuertemente tipadas puede producir resultados incoherentes. Vuelve a elegir el botón Returns 2. JavaScript convierte “7 " en un 7 numérico y el 7 numérico, que se almacena en ct, en una cadena. Además, convierte la cadena “cuarenta” en cero. Pero esto es solo el principio. Elige el botón Returns 2 varias veces más. En código administrado, el método Add generaría excepciones de clave duplicada, incluso si los valores se convirtieran a los tipos correctos. En cambio, el método Insert actualiza el valor asociado a una clave existente y devuelve un valor de Boolean que indica si se agregó una nueva clave al diccionario. Esta es la razón por la que el valor asociado a la clave 7 continua cambiando.

Otro comportamiento inesperado es que cuando pasa una variable no asignada de JavaScript como un argumento de cadena, lo que va a obtener es la cadena “undefined”. En resumen, ten cuidado cuando pases tipos de la colección de .NET Framework a código de JavaScript.

NotaNota

Si quieres concatenar muchos textos, puedes hacerlo con más eficacia si mueves el código a un método de .NET Framework y usas la clase StringBuilder, tal como se muestra en la función de showMap.

Aunque no puedas exponer tus propios tipos genéricos desde un componente de Windows en tiempo de ejecución, puedes devolver las colecciones genéricas de .NET Framework para las clases de Windows en tiempo de ejecución 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 IVector<T> de Windows en tiempo de ejecución en JavaScript.

Declarar eventos

Puedes declarar eventos mediante el patrón de eventos estándar de .NET Framework o desde otros patrones que use Windows en tiempo de ejecución. .NET Framework admite la equivalencia entre el delegado System.EventHandler<TEventArgs> y el delegado de EventHandler<T> de Windows en tiempo de ejecución, por lo que usar EventHandler<TEventArgs> es una buena forma de implementar el patrón estándar de .NET Framework. Para ver cómo funciona esto, agrega los siguientes pares 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 expongas un evento en Windows en tiempo de ejecución, la clase de argumento de evento hereda de System.Object. No hereda de System.EventArgs, como haría en .NET Framework, porque EventArgs no es un tipo de Windows en tiempo de ejecución.

NotaNota

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

Para controlar el evento Test, agrega la función events1 a default.js. La función events1 crea una función del controlador de eventos para el evento Test e invoca inmediatamente el método OnTest para generar el evento. Si colocas un punto de interrupción en el cuerpo del controlador de eventos, podrás ver que el objeto que se pasa 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);
}

Exponer operaciones asincrónicas

.NET Framework cuenta con un amplio conjunto de herramientas para el procesamiento asincrónico y el procesamiento en paralelo, que se basa en Task y en las clases genéricas Task<TResult>. Para exponer un procesamiento asincrónico basado en tareas en un componente Windows en tiempo de ejecución, use las interfaces IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> e IAsyncOperationWithProgress<TResult, TProgress> de Windows en tiempo de ejecución. (En Windows en tiempo de ejecución, las operaciones devuelven resultados, pero las acciones no lo hacen).

En esta sección se muestra una operación asincrónica cancelable que notifica sobre el progreso y devuelve resultados. El método GetPrimesInRangeAsync usa la clase AsyncInfo con el fin de generar una tarea y conectar sus características de cancelación y notificación sobre el progreso con un objeto WinJS.Promise. Comienza agregando las siguientes instrucciones using (Imports en Visual Basic) a la clase de Example:

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

Ahora, agrega el método GetPrimesInRangeAsync a la clase Example:

        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)
            );
        }
    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 sencillo y se basa en el diseño. Aquí lo principal es implementar una operación asincrónica; por tanto, la sencillez es importante y, además, la implementación representa una ventaja cuando se quiere demostrar la capacidad de cancelación. GetPrimesInRangeAsync busca números primos mediante una técnica de fuerza bruta: divide un candidato por todos los enteros menores o iguales que su raíz cuadrada, en lugar de usar únicamente números primos. Ejecución paso a paso de este código:

  • Antes de iniciar una operación asincrónica, realiza actividades de mantenimiento como validar parámetros y producir excepciones para las entradas no válidas.

  • La clave para 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 y una interfaz de cancelación para notificar sobre el progreso y debe devolver una tarea iniciada que use esos parámetros. Cuando JavaScript llama al método GetPrimesInRangeAsync, se producen los siguientes pasos (no necesariamente en el orden aquí especificado):

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

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

      NotaNota

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

    • 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. Una función lambda proporciona el delegado para la tarea iniciada, que calcula el resultado deseado. Más información en unos momentos.

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

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

  • La función lambda que representa la tarea iniciada no toma argumentos. Dado que se trata de una función lambda, tiene acceso al token y a la interfaz IProgress. Cada vez que se evalúa un número de candidato, la función lambda:

    • Comprueba si se ha alcanzado el siguiente punto de porcentaje para el progreso. Si debe hacerlo, la función lambda llama al método IProgress<T>.Report y se pasa el porcentaje a la función que especificó el objeto Promise para notificar el progreso.

    • Utiliza 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 hereda la interfaz IAsyncOperationWithProgress<TResult, TProgress>), la conexión que establece el método AsyncInfo.Run 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 especificó el objeto WinJS.Promise para procesar los resultados.

Para crear el compromiso de JavaScript y configurar el mecanismo de cancelación, agrega 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();
}

Si llama al método asincrónico GetPrimesInRangeAsync, la función asyncRun creará un objeto WinJS.Promise. El método then del objeto toma tres funciones que procesan los resultados devueltos, reaccionan ante los errores (incluida la cancelación) y controlan los informes sobre el progreso. En este ejemplo, los resultados devueltos se imprimen en el área de resultados. Cuando se produce una cancelación o finalización, los botones que inician y cancelan la operación se restablecen. La notificación sobre el progreso actualiza el control del progreso.

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

Para ejecutar la aplicación, elige la tecla F5. Para iniciar la operación asincrónica, elige el botón Asincrónica. Lo que ocurra a continuación dependerá de la velocidad de tu equipo. Si la barra de progreso se completa antes de que te dé tiempo a pestañear, aumenta el tamaño del número inicial que se pasa a GetPrimesInRangeAsync multiplicándolo una o varias veces por diez. Puedes ajustar la duración de la operación si incrementas o disminuyes el contador de números para ir probando, pero tiene más efecto si agregas ceros en la mitad de la cifra de inicio. Para cancelar la operación, elige el botón Cancelar operación asincrónica.

Vea también

Conceptos

Información general de .NET para aplicaciones de la Tienda Windows

API de .NET para aplicaciones de la Tienda Windows

Crear componentes de Windows en tiempo de ejecución en C# y Visual Basic

Programación asincrónica con Async y Await (C# y Visual Basic)

Crear componentes de Windows en tiempo de ejecución en

Eventos personalizados y descriptores de acceso de eventos en componentes de Windows en tiempo de ejecución

Otros recursos

.NET Framework Support for Windows Store Apps and Windows Runtime