Passo a passo para criar um componente do Windows Runtime em C# ou Visual Basic e chamá-lo do JavaScript

Este passo a passo mostra como você pode usar o .NET com Visual Basic ou C# para criar seus próprios tipos do Tempo de Execução do Windows, empacotados em um componente do Tempo de Execução do Windows, e como chamar esse componente de um aplicativo UWP (Plataforma Universal do Windows) JavaScript.

O Visual Studio facilita a criação e a implantação de seus próprios tipos personalizados do Tempo de Execução do Windows dentro de um projeto WRC (componente do Tempo de Execução do Windows) escrito com C# ou Visual Basic e, em seguida, a referência a esse WRC de um projeto de aplicativo JavaScript e o consumo desses tipos personalizados desse aplicativo.

Internamente, seus tipos de Windows Runtime podem usar qualquer funcionalidade .NET que seja permitida em um aplicativo UWP.

Externamente, os membros do seu tipo podem expor somente tipos do Windows Runtime para seus parâmetros e valores retornados. Quando você compila sua solução, o Visual Studio compila seu projeto de Componente do .NET WRC e executa uma etapa de build que cria um arquivo de metadados do Windows (.winmd). Esse é o componente do Tempo de Execução do Windows, que o Visual Studio inclui em seu aplicativo.

Observação

O .NET mapeia automaticamente alguns tipos do .NET usados mais frequentemente, como tipos de dados primitivos e tipos de coleção, para os equivalentes do Windows Runtime. Esses tipos do .NET podem ser usados na interface pública de um componente do Windows Runtime e serão exibidos para usuários do componente como os tipos de Windows Runtime correspondentes. Confira Componentes do Windows Runtime com C# e Visual Basic.

Pré-requisitos:

Observação

Não há suporte para projetos da Plataforma Universal do Windows (UWP) em JavaScript no Visual Studio 2019. Consulte JavaScript e TypeScript no Visual Studio 2019. Para acompanhar este tópico, recomendamos que você use o Visual Studio 2017. Consulte JavaScript no Visual Studio 2017.

Criando uma classe simples do Tempo de Execução do Windows

Esta seção cria um aplicativo UWP JavaScript e adiciona à solução um projeto de componente do Tempo de Execução do Windows do Visual Basic ou C#. Ele mostra como definir um tipo do Tempo de Execução do Windows, criar uma instância do tipo do JavaScript e chamar membros estáticos e de instância. A exibição visual do aplicativo de exemplo é deliberadamente discreta para manter o foco no componente.

  1. No Visual Studio, crie um novo projeto JavaScript: na barra de menus, escolha Arquivo, Novo, Projeto. Na seção Modelos Instalados da caixa de diálogo Novo Projeto, escolha JavaScript, Windows e, em seguida, Universal. (Se o Windows não estiver disponível, verifique se você está usando o Windows 8 ou posterior.) Escolha o modelo Aplicativo em Branco e insira SampleApp para o nome do projeto.

  2. Crie o projeto de componente: no Gerenciador de Soluções, abra o menu de atalho da solução SampleApp e escolha Adicionar e, em seguida, escolha Novo Projeto para adicionar um novo projeto C# ou Visual Basic à solução. Na seção Modelos Instalados da caixa de diálogo Adicionar Novo Projeto, escolha Visual Basic ou Visual C# e, em seguida, escolha Windows e, em seguida, Universal. Escolha o modelo de componente do Tempo de Execução do Windows e insira SampleComponent para o nome do projeto.

  3. Altere o nome da classe para Exemplo. Observe que, por padrão, a classe é marcada como public sealed (Public NotInheritable no Visual Basic). Todas as classes do Windows Runtime que você expõe no componente precisam ser sealed.

  4. Adicione dois membros simples à classe, um método estático (método compartilhado no Visual Basic) e uma propriedade de instância:

    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 o IntelliSense para os membros recém-adicionados, no Gerenciador de Soluções, abra o menu de atalho do projeto SampleComponent e escolha Compilar.

  6. No Gerenciador de Soluções, no projeto JavaScript, abra o menu de atalho para Referências e escolha Adicionar Referência para abrir o Gerenciador de Referências. Escolha Projetos e Solução. Marque a caixa de seleção do projeto SampleComponent e escolha OK para adicionar uma referência.

Chamar o componente do JavaScript

Para usar o tipo Windows Runtime do JavaScript, adicione o código a seguir na função anônima no arquivo default.js (na pasta js do projeto) fornecido pelo modelo do Visual Studio. Ele deve ir após o manipulador de eventos app.oncheckpoint e antes da chamada para 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 a primeira letra de cada nome de membro é alterada de maiúsculas para minúsculas. Essa transformação faz parte do suporte que o JavaScript fornece para habilitar o uso natural do Tempo de Execução do Windows. Namespaces e nomes de classe são maiúsculas e minúsculas em Pascal. Os nomes dos membros são maiúsculas e minúsculas, exceto os nomes de eventos, que são todos minúsculos. Consulte Usando o Tempo de Execução do Windows em JavaScript. As regras para o invólucro de camelo podem ser confusas. Uma série de letras maiúsculas iniciais normalmente aparece como minúsculas, mas se três letras maiúsculas forem seguidas por uma letra minúscula, apenas as duas primeiras letras aparecerão em minúsculas: por exemplo, um membro chamado IDStringKind aparece como idStringKind. No Visual Studio, você pode criar seu projeto de componente do Tempo de Execução do Windows e, em seguida, usar o IntelliSense em seu projeto JavaScript para ver as maiúsculas e minúsculas corretas.

De maneira semelhante, o .NET fornece suporte para habilitar o uso natural do Tempo de Execução do Windows em código gerenciado. Isso é discutido nas seções subsequentes deste artigo e nos artigos Componentes do Tempo de Execução do Windows com suporte a C# e Visual Basic e .NET para aplicativos UWP e o Tempo de Execução do Windows.

Crie uma interface de usuário simples

Em seu projeto JavaScript, abra o arquivo default.html e atualize o corpo conforme mostrado no código a seguir. Esse código inclui o conjunto completo de controles para o aplicativo de exemplo e especifica os nomes de função para os eventos de clique.

Observação Quando você executa o aplicativo pela primeira vez, há suporte apenas para os botões Básico1 e Básico2.

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

Em seu projeto JavaScript, na pasta css, abra default.css. Modifique a seção do corpo conforme mostrado e adicione estilos para controlar o layout dos botões e o posicionamento do texto de saída.

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

Agora, adicione o código de registro do ouvinte de eventos adicionando uma cláusula then à chamada processAll em app.onactivated no default.js. Substitua a linha de código existente que chama setPromise e altere-a para o seguinte código:

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

Essa é uma maneira melhor de adicionar eventos a controles HTML do que adicionar um manipulador de eventos de clique diretamente em HTML. Consulte Criar um aplicativo "Hello, World" (JS).

Compilar e executar o aplicativo

Antes de compilar, altere a plataforma de destino de todos os projetos para Arm, x64 ou x86, conforme apropriado para o seu computador.

Para compilar e executar a solução, escolha a tecla F5. (Se você receber uma mensagem de erro em tempo de execução informando que SampleComponent é indefinido, a referência ao projeto da biblioteca de classes está ausente.)

O Visual Studio primeiro compila a biblioteca de classes e, em seguida, executa uma tarefa do MSBuild que executa Winmdexp.exe (Ferramenta de Exportação de Metadados do Tempo de Execução do Windows) para criar seu componente do Tempo de Execução do Windows. O componente está incluído em um arquivo .winmd que contém o código gerenciado e os metadados do Windows que descrevem o código. WinMdExp.exe gera mensagens de erro de build quando você escreve um código inválido em um componente do Tempo de Execução do Windows e as mensagens de erro são exibidas no IDE do Visual Studio. O Visual Studio adiciona seu componente ao pacote do aplicativo (arquivo .appx) para seu aplicativo UWP e gera o manifesto apropriado.

Escolha o botão Basics 1 para atribuir o valor retornado do método estático GetAnswer à área de saída, crie uma instância da classe Example e exiba o valor de sua propriedade SampleProperty na área de saída. A saída é mostrada aqui:

"The answer is 42."
0

Escolha o botão Basics 2 para incrementar o valor da propriedade SampleProperty e exibir o novo valor na área de saída. Tipos primitivos, como cadeias de caracteres e números, podem ser usados como tipos de parâmetro e tipos de retorno e podem ser passados entre código gerenciado e JavaScript. Como os números em JavaScript são armazenados no formato de ponto flutuante de precisão dupla, eles são convertidos em tipos numéricos do .NET Framework.

Observação Por padrão, você pode definir pontos de interrupção somente em seu código JavaScript. Para depurar seu código Visual Basic ou C#, consulte Criando componentes do Tempo de Execução do Windows em C# e Visual Basic.

Para interromper a depuração e fechar seu aplicativo, alterne do aplicativo para o Visual Studio e escolha Shift+F5.

Usando o Tempo de Execução do Windows a partir do JavaScript e do código gerenciado

O Tempo de Execução do Windows pode ser chamado de JavaScript ou código gerenciado. Os objetos do Tempo de Execução do Windows podem ser passados entre os dois, e os eventos podem ser manipulados de ambos os lados. No entanto, as maneiras como você usa os tipos do Tempo de Execução do Windows nos dois ambientes diferem em alguns detalhes, pois o JavaScript e o .NET dão suporte ao Tempo de Execução do Windows de maneira diferente. O exemplo a seguir demonstra essas diferenças, usando a classe Windows.Foundation.Collections.PropertySet . Neste exemplo, você cria uma instância da coleção PropertySet em código gerenciado e registra um manipulador de eventos para controlar as alterações na coleção. Em seguida, você adiciona o código JavaScript que obtém a coleção, registra seu próprio manipulador de eventos e usa a coleção. Por fim, você adiciona um método que faz alterações na coleção do código gerenciado e mostra o JavaScript manipulando uma exceção gerenciada.

Importante Neste exemplo, o evento está sendo disparado no thread da interface do usuário. Se você disparar o evento de um thread em segundo plano, por exemplo, em uma chamada assíncrona, precisará fazer algum trabalho extra para que o JavaScript manipule o evento. Para obter mais informações, consulte Gerando eventos em componentes do Tempo de Execução do Windows.

No projeto SampleComponent, adicione uma nova classe pública selada (classe Public NotInheritable no Visual Basic) chamada PropertySetStats. A classe encapsula uma coleção PropertySet e manipula seu evento MapChanged. O manipulador de eventos rastreia o número de alterações de cada tipo que ocorrem e o método DisplayStats produz um relatório formatado em HTML. Observe a instrução using adicional (instrução Imports no Visual Basic); tenha cuidado para adicioná-la às instruções using existentes em vez de substituí-las.

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

O manipulador de eventos segue o padrão de evento familiar do .NET Framework, exceto que o remetente do evento (nesse caso, o objeto PropertySet) é convertido na cadeia de caracteres IObservableMap<, interface de objeto> (IObservableMap(Of String, Object) no Visual Basic), que é uma instanciação da interface do Tempo de Execução do Windows IObservableMap<K, V>. (Você pode converter o remetente para seu tipo, se necessário.) Além disso, os argumentos do evento são apresentados como uma interface e não como um objeto.

No arquivo default.js, adicione a função Runtime1 conforme mostrado. Esse código cria um objeto PropertySetStats, obtém sua coleção PropertySet e adiciona seu próprio manipulador de eventos, a função onMapChanged, para manipular o evento MapChanged. Depois de fazer alterações na coleção, runtime1 chama o método DisplayStats para mostrar um resumo dos tipos de alteração.

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

A maneira como você lida com eventos do Tempo de Execução do Windows em JavaScript é muito diferente da maneira como você os manipula no código do .NET Framework. O manipulador de eventos JavaScript usa apenas um argumento. Quando você exibe esse objeto no depurador do Visual Studio, a primeira propriedade é o remetente. Os membros da interface de argumento de evento também aparecem diretamente nesse objeto.

Para executar o aplicativo, escolha a tecla F5. Se a classe não estiver lacrada, você receberá a mensagem de erro "Não há suporte para a exportação do tipo não lacrado 'SampleComponent.Example' no momento. Por favor, marque-o como selado."

Escolha o botão Tempo de execução 1 . O manipulador de eventos exibe as alterações à medida que os elementos são adicionados ou alterados e, no final, o método DisplayStats é chamado para produzir um resumo das contagens. Para interromper a depuração e fechar o aplicativo, volte para o Visual Studio e escolha Shift+F5.

Para adicionar mais dois itens à coleção PropertySet do código gerenciado, adicione o seguinte código à classe PropertySetStats:

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

Esse código destaca outra diferença na maneira como você usa os tipos do Tempo de Execução do Windows nos dois ambientes. Se você digitar esse código por conta própria, observará que o IntelliSense não mostra o método de inserção usado no código JavaScript. Em vez disso, ele mostra o método Add comumente visto em coleções no .NET. Isso ocorre porque algumas interfaces de coleção comumente usadas têm nomes diferentes, mas funcionalidade semelhante no Tempo de Execução do Windows e no .NET. Quando você usa essas interfaces em código gerenciado, elas aparecem como seus equivalentes do .NET Framework. Isso é discutido em Componentes do Tempo de Execução do Windows com C# e Visual Basic. Quando você usa as mesmas interfaces em JavaScript, a única alteração em relação ao Tempo de Execução do Windows é que as letras maiúsculas no início dos nomes dos membros se tornam minúsculas.

Por fim, para chamar o método AddMore com tratamento de exceção, adicione a função 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();
}

Adicione o código de registro do manipulador de eventos da mesma forma que você fez anteriormente.

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

Para executar o aplicativo, escolha a tecla F5. Escolha Runtime 1 e, em seguida, Runtime 2. O manipulador de eventos JavaScript relata a primeira alteração na coleção. A segunda mudança, no entanto, tem uma chave duplicada. Os usuários de dicionários do .NET Framework esperam que o método Add gere uma exceção, e é isso que acontece. O JavaScript lida com a exceção do .NET.

Observação Não é possível exibir a mensagem da exceção do código JavaScript. O texto da mensagem é substituído por um rastreamento de pilha. Para obter mais informações, consulte "Lançando exceções" em Criando componentes do Tempo de Execução do Windows em C# e Visual Basic.

Por outro lado, quando o JavaScript chamou o método insert com uma chave duplicada, o valor do item foi alterado. Essa diferença de comportamento se deve às diferentes maneiras pelas quais o JavaScript e o .NET dão suporte ao Tempo de Execução do Windows, conforme explicado em Componentes do Tempo de Execução do Windows com C# e Visual Basic.

Retornando tipos gerenciados do seu componente

Conforme discutido anteriormente, você pode passar tipos nativos do Tempo de Execução do Windows para frente e para trás livremente entre o código JavaScript e o código C# ou Visual Basic. Na maioria das vezes, os nomes de tipo e os nomes de membros serão os mesmos em ambos os casos (exceto que os nomes de membros começam com letras minúsculas em JavaScript). No entanto, na seção anterior, a classe PropertySet parecia ter membros diferentes no código gerenciado. (Por exemplo, em JavaScript, você chamou o método insert e, no código .NET, chamou o método Add.) Esta seção explora a maneira como essas diferenças afetam os tipos do .NET Framework passados para o JavaScript.

Além de retornar os tipos do Tempo de Execução do Windows que você criou em seu componente ou passou para o componente do JavaScript, você pode retornar um tipo gerenciado, criado em código gerenciado, para JavaScript como se fosse o tipo correspondente do Tempo de Execução do Windows. Mesmo no primeiro exemplo simples de uma classe de tempo de execução, os parâmetros e os tipos de retorno dos membros eram tipos primitivos do Visual Basic ou C#, que são tipos do .NET Framework. Para demonstrar isso para coleções, adicione o seguinte código à classe Example, para criar um método que retorna um dicionário genérico de cadeias de caracteres, indexado por inteiros:

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

Observe que o dicionário deve ser retornado como uma interface implementada pelo Dictionary<TKey, TValue> e que mapeia para uma interface do Tempo de Execução do Windows. Nesse caso, a interface é IDictionary<int, string> (IDictionary(Of Integer, String) no Visual Basic). Quando o tipo do Tempo de Execução do Windows IMap<int, cadeia de caracteres> é passado para o código gerenciado, ele aparece como IDictionary<int, cadeia de caracteres> e o inverso é true quando o tipo gerenciado é passado para JavaScript.

Importante Quando um tipo gerenciado implementa várias interfaces, o JavaScript usa a interface que aparece primeiro na lista. Por exemplo, se você retornar Dictionary<int, string> para o código JavaScript, ele aparecerá como IDictionary<int, string> , independentemente da interface especificada como o tipo de retorno. Isso significa que, se a primeira interface não incluir um membro que aparece em interfaces posteriores, esse membro não ficará visível para o JavaScript.

 

Para testar o novo método e usar o dicionário, adicione as funções returns1 e returns2 a default.js:

var names;

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

var ct = 7;

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

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

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

Adicione o código de registro do evento ao mesmo e bloqueie o outro código de registro do evento:

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

Há algumas coisas interessantes a serem observadas sobre esse código JavaScript. Em primeiro lugar, inclui uma função showMap para exibir o conteúdo do dicionário em HTML. No código de showMap, observe o padrão de iteração. No .NET, não há nenhum método First na interface IDictionary genérica e o tamanho é retornado por uma propriedade Count em vez de por um método Size. Para JavaScript, IDictionary<int, string> parece ser o tipo do Tempo de Execução do Windows IMap<int, string>. (Veja o Interface IMap<K,V> .)

Na função returns2, como nos exemplos anteriores, o JavaScript chama o método Insert (insert em JavaScript) para adicionar itens ao dicionário.

Para executar o aplicativo, escolha a tecla F5. Para criar e exibir o conteúdo inicial do dicionário, escolha o botão Returns 1 . Para adicionar mais duas entradas ao dicionário, escolha o botão Retorna 2 . Observe que as entradas são exibidas em ordem de inserção, como seria de esperar do Dictionary<TKey, TValue>. Se você quiser que eles sejam classificados, poderá retornar um SortedDictionary<int, string> de GetMapOfNames. (A classe PropertySet usada em exemplos anteriores tem uma organização interna diferente de Dictionary<TKey, TValue>.)

É claro que o JavaScript não é uma linguagem fortemente tipada, portanto, o uso de coleções genéricas fortemente tipadas pode levar a alguns resultados surpreendentes. Escolha o botão Devoluções 2 novamente. O JavaScript obrigatoriamente coage o "7" a um 7 numérico e o 7 numérico armazenado em ct a uma string. E coage a string "quarenta" a zero. Mas isso é apenas o começo. Escolha o botão Devoluções 2 mais algumas vezes. No código gerenciado, o método Add geraria exceções de chave duplicadas, mesmo que os valores fossem convertidos nos tipos corretos. Por outro lado, o método Insert atualiza o valor associado a uma chave existente e retorna um valor booliano que indica se uma nova chave foi adicionada ao dicionário. É por isso que o valor associado à chave 7 continua mudando.

Outro comportamento inesperado: se você passar uma variável JavaScript não atribuída como um argumento de string, o que você obtém é a string "undefined". Em resumo, tenha cuidado ao passar tipos de coleção do .NET Framework para o código JavaScript.

Observação Se você tiver grandes quantidades de texto para concatenar, poderá fazê-lo com mais eficiência movendo o código para um método .NET Framework e usando a classe StringBuilder, conforme mostrado na função showMap.

Embora você não possa expor seus próprios tipos genéricos de um componente do Tempo de Execução do Windows, você pode retornar coleções genéricas do .NET Framework para classes do Tempo de Execução do Windows usando um código como o seguinte:

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

A Lista<T> implementa IList<T>, que aparece como o tipo IVector<T> do Windows Runtime em JavaScript.

Declarando eventos

Você pode declarar eventos usando o padrão de evento padrão do .NET Framework ou outros padrões usados pelo Tempo de Execução do Windows. O .NET Framework dá suporte à equivalência entre o delegado System.EventHandler<TEventArgs> e o delegado EventHandler<T> do Windows Runtime, portanto, usar EventHandler<TEventArgs> é uma boa maneira de implementar o padrão padrão do .NET Framework. Para ver como isso funciona, adicione o seguinte par de classes ao projeto SampleComponent:

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

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

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

Quando você expõe um evento no Tempo de Execução do Windows, a classe de argumento de evento herda de System.Object. Ele não herda de System.EventArgs, como faria no .NET, porque EventArgs não é um tipo do Tempo de Execução do Windows.

Se você declarar acessadores de eventos personalizados para seu evento (palavra-chave personalizada no Visual Basic), deverá usar o padrão de evento do Tempo de Execução do Windows. Consulte Eventos personalizados e acessadores de eventos em componentes do Tempo de Execução do Windows.

Para manipular o evento Test, adicione a função events1 a default.js. A função events1 cria uma função de manipulador de eventos para o evento Test e invoca imediatamente o método OnTest para gerar o evento. Se você colocar um ponto de interrupção no corpo do manipulador de eventos, poderá ver que o objeto passado para o parâmetro único inclui o objeto de origem e ambos os membros 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);
}

Adicione o código de registro do evento ao mesmo e bloqueie o outro código de registro do evento:

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

Expondo operações assíncronas

O .NET Framework tem um conjunto avançado de ferramentas para processamento assíncrono e processamento paralelo, com base nas classes Task e Task<TResult> genéricas. Para expor o processamento assíncrono baseado em tarefas em um componente do Tempo de Execução do Windows, use as interfaces do Tempo de Execução do Windows IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> e IAsyncOperationWithProgress<TResult, TProgress.> (No Tempo de Execução do Windows, as operações retornam resultados, mas as ações não.)

Esta seção demonstra uma operação assíncrona cancelável que relata o progresso e retorna resultados. O método GetPrimesInRangeAsync usa a classe AsyncInfo para gerar uma tarefa e conectar seus recursos de cancelamento e relatório de progresso a um objeto WinJS.Promise. Comece adicionando o método GetPrimesInRangeAsync à classe de exemplo:

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 é um localizador de números primos muito simples, e isso é por design. O foco aqui está na implementação de uma operação assíncrona, portanto, a simplicidade é importante e uma implementação lenta é uma vantagem quando estamos demonstrando o cancelamento. GetPrimesInRangeAsync localiza primos por força bruta: ele divide um candidato por todos os inteiros que são menores ou iguais à sua raiz quadrada, em vez de usar apenas os números primos. Percorrendo este código:

  • Antes de iniciar uma operação assíncrona, execute atividades de manutenção, como validar parâmetros e lançar exceções para entrada inválida.

  • A chave para essa implementação é o método AsyncInfo.Run<TResult, TProgress>(Func<CancellationToken, IProgress<TProgress>, Task<TResult>>) e o delegado que é o único parâmetro do método. O representante deve aceitar um token de cancelamento e uma interface para relatar o progresso e deve retornar uma tarefa iniciada que usa esses parâmetros. Quando o JavaScript chama o método GetPrimesInRangeAsync, as seguintes etapas ocorrem (não necessariamente na ordem fornecida aqui):

    • O objeto WinJS.Promise fornece funções para processar os resultados retornados, reagir ao cancelamento e lidar com relatórios de progresso.

    • O método AsyncInfo.Run cria uma fonte de cancelamento e um objeto que implementa a interface IProgress<T> . Para o delegado, ele passa um token CancellationToken da origem do cancelamento e a interface IProgress<T> .

      Observação Se o objeto Promise não fornecer uma função para reagir ao cancelamento, AsyncInfo.Run ainda passará um token cancelável e o cancelamento ainda poderá ocorrer. Se o objeto Promise não fornecer uma função para lidar com atualizações de progresso, AsyncInfo.Run ainda fornecerá um objeto que implementa IProgress<T>, mas seus relatórios serão ignorados.

    • O delegado usa o método Task.Run<TResult>(Func<TResult>, CancellationToken) para criar uma tarefa iniciada que usa o token e a interface de progresso. O delegado para a tarefa iniciada é fornecido por uma função lambda que calcula o resultado desejado. Falaremos mais sobre isso em breve.

    • O método AsyncInfo.Run cria um objeto que implementa a interface IAsyncOperationWithProgress<TResult, TProgress, conecta o mecanismo de> cancelamento do Tempo de Execução do Windows com a origem do token e conecta a função de relatório de progresso do objeto Promise com a interface T> IProgress<.

    • A interface IAsyncOperationWithProgress<TResult, TProgress> é retornada ao JavaScript.

  • A função lambda representada pela tarefa iniciada não recebe nenhum argumento. Como é uma função lambda, ela tem acesso ao token e à interface IProgress. Cada vez que um número candidato é avaliado, a função lambda:

    • Verifica se o próximo ponto percentual de progresso foi alcançado. Em caso afirmativo, a função lambda chamará o IProgress<T>. Report e a porcentagem é passada para a função que o objeto Promise especificou para o progresso do relatório.
    • Usa o token de cancelamento para lançar uma exceção se a operação tiver sido cancelada. Se o método IAsyncInfo.Cancel (que a interface IAsyncOperationWithProgress<TResult, TProgress> herda) tiver sido chamado, a conexão configurada pelo método AsyncInfo.Run garantirá que o token de cancelamento seja notificado.
  • Quando a função lambda retorna a lista de números primos, a lista é passada para a função que o objeto WinJS.Promise especificou para processar os resultados.

Para criar a promessa JavaScript e configurar o mecanismo de cancelamento, adicione as funções asyncRun e asyncCancel a default.js.

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

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

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

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

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

Não se esqueça do código de registro do evento da mesma forma que você fez anteriormente.

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

Ao chamar o método assíncrono GetPrimesInRangeAsync, a função asyncRun cria um objeto WinJS.Promise. O método then do objeto usa três funções que processam os resultados retornados, reagem a erros (incluindo cancelamento) e lidam com relatórios de progresso. Neste exemplo, os resultados retornados são impressos na área de saída. Cancelamento ou conclusão redefine os botões que iniciam e cancelam a operação. O relatório de progresso atualiza o controle de progresso.

A função asyncCancel apenas chama o método cancel do objeto WinJS.Promise.

Para executar o aplicativo, escolha a tecla F5. Para iniciar a operação assíncrona, escolha o botão Assíncrono. O que acontece a seguir depende da velocidade do seu computador. Se a barra de progresso for concluída antes que você tenha tempo de piscar, aumente o tamanho do número inicial passado para GetPrimesInRangeAsync por um ou mais fatores de dez. Você pode ajustar a duração da operação aumentando ou diminuindo a contagem de números a serem testados, mas adicionar zeros no meio do número inicial terá um impacto maior. Para cancelar a operação, escolha o botão Cancelar assíncrono.