演练:用 C# 或 Visual Basic 创建一个简单的组件,然后从 JavaScript 中调用该组件

本演练演示如何将 .NET Framework 4.5 与 Visual Basic 或 C# 结合使用以创建自己的、打包在 Windows 运行时组件中的 Windows 运行时类型,以及如何从使用 JavaScript 为 Windows 生成的 Windows 应用商店应用程序中调用该组件。

使用 Visual Studio 便于向应用程序中添加用 C# 或 Visual Basic 编写的 Windows 运行时组件以及创建可从 JavaScript 中调用的 Windows 运行时类型。在内部,Windows 运行时类型可以使用 Windows 应用商店应用程序中允许的任何 .NET Framework 功能。(有关更多信息,请参见用 C# 和 Visual Basic 创建 Windows 运行时组件.NET for Windows Store 应用程序概述。)在外部,类型的成员只能为其参数和返回值公开 Windows 运行时类型。在生成解决方案时,Visual Studio 会生成 .NET Framework 的**“Windows 运行时组件”**项目,然后执行可创建 Windows 元数据 (.winmd) 文件的生成步骤。这是你的 Windows 运行时组件,Visual Studio 将其包括在你的应用程序中。

备注

.NET Framework 自动将一些常用的 .NET Framework 类型(如基元数据类型和集合类型)映射到 Windows 运行时等效类型。这些 .NET Framework 类型可用在 Windows 运行时组件的公共接口中,将向该组件的用户显示为相应的 Windows 运行时类型。请参见用 C# 和 Visual Basic 创建 Windows 运行时组件

本演练阐释了以下任务。在完成第一部分(使用 JavaScript 设置 Windows 应用商店应用程序)后,可按任意顺序完成其余部分。

  • 创建简单的 Windows 运行时类

  • 从 JavaScript 代码和托管代码中使用 Windows 运行时

  • 从组件返回托管类型

  • 声明事件

  • 公开异步操作

先决条件:

若要完成本演练,你需要具备以下条件:

  • Windows 8

  • Microsoft Visual Studio 2012 或 Microsoft Visual Studio Express 2012 for Windows 8

创建简单的 Windows 运行时类

本部分创建一个使用 JavaScript 为 Windows 生成的 Windows 应用商店应用程序,并添加一个 Visual Basic 或 C# Windows 运行时组件项目。本部分演示如何定义托管的 Windows 运行时类型、如何从 JavaScript 中创建该类型的实例以及如何调用静态和实例成员。该示例应用程序的可视显示有意设置为暗色,以突出组件。可根据喜好随意调整其外观。

  1. 在 Visual Studio 中,创建一个新 JavaScript 项目:在菜单栏上,依次选择**“文件”“新建”“项目”(在 Visual Studio Express 2012 for Windows 8 中,选择“文件”“新建项目”)。在“新建项目”对话框的“已安装的模板”部分中,选择“JavaScript”,然后选择“Windows Store”。(如果“Windows Store”不可用,请确保使用 Windows 8。)选择“空白应用程序”**模板并输入项目名称 SampleApp。

  2. 创建组件项目:在**“解决方案资源管理器”中,打开 SampleApp 解决方案的快捷菜单,并选择“添加”,然后选择“新建项目”向解决方案中添加新的 C# 或 Visual Basic 项目。在“添加新项目”对话框的“已安装的模板”部分,选择“Visual Basic”“Visual C#”,然后选择“Windows Store”。选择“Windows 运行时组件”**模板并输入项目名称 SampleComponent。

  3. 将类的名称更改为 Example。请注意,默认情况下将类标记为 public sealed(在 Visual Basic 中为 Public NotInheritable)。从组件中公开的所有 Windows 运行时类必须都是密封的。

  4. 向该类中添加两个简单的成员:一个 static 方法(在 Visual Basic 中为 Shared 方法)和一个实例属性:

    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. 可选:若要为新添加的成员启用 IntelliSense,请在**“解决方案资源管理器”中打开 SampleComponent 项目的快捷菜单,然后选择“生成”**。

  6. 在 JavaScript 项目的**“解决方案资源管理器”中,打开“引用”的快捷菜单,然后选择“添加引用”以打开“引用管理器”。选择“解决方案”,然后选择“项目”。选中 SampleComponent 项目的复选框并选择“确定”**以添加引用。

Hh779077.collapse_all(zh-cn,VS.110).gif从 JavaScript 中调用组件

若要从 JavaScript 中使用 Windows 运行时类型,请将以下代码添加到 default.js 文件末尾(在项目的 js 文件夹中),即 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;
}

请注意,每个成员名称的第一个字母从大写形式更改为小写形式。此转换属于 JavaScript 提供的支持的一部分,旨在允许自然地使用 Windows 运行时。命名空间和类名采用 Pascal 大小写形式。成员名称采用 camel 大小写形式,全部采用小写形式的事件名称除外。请参见在 JavaScript 中使用 Windows 运行时。camel 大小写规则可能容易引起混淆。一系列大写首字母通常显示为小写,但如果三个大写字母后跟一个小写字母,则只有前两个字母以小写形式显示:例如,名为 IDStringKind 的成员显示为 idStringKind。在 Visual Studio 中,可以生成 Windows 运行时组件项目,然后在 JavaScript 项目中使用 IntelliSense 来查看正确的大小写形式。

同样,.NET Framework 也提供支持,目的是在托管代码中允许自然地使用 Windows 运行时。在本文的后续部分以及文章用 C# 和 Visual Basic 创建 Windows 运行时组件.NET Framework 对 Windows 应用商店应用程序和 Windows 运行时的支持情况中对此进行了讨论。

Hh779077.collapse_all(zh-cn,VS.110).gif创建简单的用户界面

在 JavaScript 项目中,打开 default.html 文件并更新主体,如以下代码所示。此代码包括用于示例应用程序的完整控件集,并为单击事件指定了函数名称。

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

在 JavaScript 项目的 css 文件夹中,打开 default.css。按照下述要求修改 body 部分,并添加样式来控制按钮布局和输出文本的位置。

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(zh-cn,VS.110).gif生成并运行应用程序

若要生成并运行解决方案,请选择**“F5”**键。(如果收到运行时错误消息,指出 SampleComponent 未定义,则说明缺少对该类库项目的引用。)

Visual Studio 首先编译类库,然后执行 MSBuild 任务,该任务通过运行 Winmdexp.exe(Windows 运行时元数据导出工具)来创建 Windows 运行时组件。该组件包含在 .winmd 文件中,该文件同时包含托管代码和描述代码的 Windows 元数据。当你编写在 Windows 运行时组件中无效的代码时,WinMdExp.exe 将生成生成错误消息,该错误消息显示在 Visual Studio IDE 中。Visual Studio 将你的组件添加至 Windows 应用商店应用程序的应用程序包(.appx 文件),并生成相应的清单。

选择**“基本操作 1”**按钮,以便从静态 GetAnswer 方法中向输出区域分配返回值,同时创建 Example 类的实例,并在输出区域中显示其 SampleProperty 属性的值。

选择**“基本操作 2”**按钮,令 SampleProperty 属性的值递增并在输出区域中显示新值。基元类型(如字符串和数字)可用作参数类型和返回类型,并可在托管代码和 JavaScript 之间传递。由于 JavaScript 中的数字以双精度浮点格式存储,因此它们会转换为 .NET Framework 数字类型。

备注

默认情况下,只能在 JavaScript 代码中设置断点。若要调试 Visual Basic 或 C# 代码,请参见用 C# 和 Visual Basic 创建 Windows 运行时组件

若要停止调试并关闭应用程序,请从应用程序切换到 Visual Studio,并选择**“Shift+F5”**。

从 JavaScript 和托管代码中使用 Windows 运行时

可以从 JavaScript 或托管代码调用 Windows 运行时。Windows 运行时 对象可以来回在这两者之间传递,并且事件可以从任何一方进行处理。但是,由于 JavaScript 和 .NET Framework 对 Windows 运行时的支持不同,因此,在两种环境中使用 Windows 运行时类型的方式在某些细节上也有所不同。以下示例使用 Windows.Foundation.Collections.PropertySet 类演示了这些差异。在此示例中,在托管代码中创建 PropertySet 集合的实例并注册事件处理程序来跟踪该集合中的更改。然后添加 JavaScript 代码,该代码会获取集合、注册自己的事件处理程序并使用该集合。最后,添加一个方法,该方法从托管代码中对集合进行更改并显示处理托管异常的 JavaScript。

在 SampleComponent 项目中,添加名为 PropertySetStats 的新 public sealed 类(在 Visual Basic 中为 Public NotInheritable 类)。该类包装 PropertySet 集合并处理其 MapChanged 事件。事件处理程序跟踪产生的每种更改的数量,DisplayStats 方法会生成 HTML 格式的报告。请注意其他 using 语句(在 Visual Basic 中为 Imports 语句);小心地将其添加到现有 using 语句中,不要覆盖这些语句。

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

事件处理程序遵循熟悉的 .NET Framework 事件模式,事件的发送方(在此示例中为 PropertySet 对象)除外,它转换为 IObservableMap<string, object> 接口(在 Visual Basic 中为 IObservableMap(Of String, Object)),这是 Windows 运行时接口 IObservableMap<K, V> 的实例化。(如有必要,可将该发送方转换为其类型。)此外,事件参数以接口而非对象方式提供。

在 default.js 文件中添加 Runtime1 函数,如下所示。此代码创建一个 PropertySetStats 对象,获取其 PropertySet 集合,并添加自己的事件处理程序(onMapChanged 函数)来处理 MapChanged 事件。在对集合进行更改后,runtime1 调用 DisplayStats 方法来显示更改类型的摘要。

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

在 JavaScript 中处理 Windows 运行时事件的方式与在 .NET Framework 代码中处理这些事件的方式不同。JavaScript 事件处理程序仅采用一个参数。在 Visual Studio 调试器中查看此对象时,第一个属性是发送方。事件参数接口的成员也直接显示在此对象中。

若要运行该应用程序,请选择**“F5”**键。如果类未密封,则会收到错误消息“导出的非密封类型‘SampleComponent.Example’当前不受支持。请将其标记为已密封”。

选择**“运行时 1”按钮。在添加或更改元素时,事件处理程序会显示更改,在末尾还调用 DisplayStats 方法来生成计数摘要。若要停止调试并关闭应用程序,请切换回 Visual Studio 并选择“Shift+F5”**。

若要从托管代码向 PropertySet 集合中再添加两个项,请将以下代码添加到 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

此代码突出显示了两种环境中 Windows 运行时类型使用方式的另一种差异。如果亲自键入此代码,你会注意到 IntelliSense 不显示你在 JavaScript 代码中使用的 insert 方法,而是显示在 .NET Framework 的集合中常见的 Add 方法。这是因为,有些常用的集合接口在 Windows 运行时和 .NET Framework 中具有不同的名称,但具有类似的功能。在托管代码中使用这些接口时,它们显示为 .NET Framework 等效项。用 C# 和 Visual Basic 创建 Windows 运行时组件中对此进行了讨论。在 JavaScript 中使用相同的接口时,与 Windows 运行时的唯一差别是成员名称开头的大写字母变为小写形式。

最后,若要调用 AddMore 方法进行异常处理,请将 runtime2 函数添加到 default.js 中。

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

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

若要运行该应用程序,请选择**“F5”键。选择“运行时 1”,然后选择“运行时 2”**。JavaScript 事件处理程序会报告对集合的第一项更改。不过,第二项更改具有重复键。.NET Framework 字典的用户预期 Add 方法会引发异常,并且确实会发生这种情况。JavaScript 处理 .NET Framework 异常。

备注

您无法通过 JavaScript 代码显示异常消息。消息文本会由堆栈跟踪替换。有关更多信息,请参见用 C# 和 Visual Basic 创建 Windows 运行时组件中的“引发异常”。

相比而言,当 JavaScript 用重复键调用 insert 方法时,该项的值已更改。出现这种行为上的差异是由于 JavaScript 和 .NET Framework 支持 Windows 运行时的方式不同,如用 C# 和 Visual Basic 创建 Windows 运行时组件所述。

从组件返回托管类型

如前所述,可在 JavaScript 代码和 C# 或 Visual Basic 代码之间来回自由地传递本机 Windows 运行时类型。大多数情况下,类型名称和成员名称在这两种情况下将是相同的(但在 JavaScript 中,成员名称以小写字母开头)。然而在上一部分中,PropertySet 类在托管代码中的成员似乎并不相同。(例如,在 JavaScript 中调用 insert 方法,在 .NET Framework 代码中调用 Add 方法。)本部分探讨这些差异如何影响传递至 JavaScript 的 .NET Framework 类型。

除了返回你在组件中创建或从 JavaScript 传递给组件的 Windows 运行时类型外,还可将在托管代码中创建的托管类型返回给 JavaScript,就像它是对应的 Windows 运行时类型一样。即使在运行时类的第一个简单示例中,成员的参数和返回类型也是 Visual Basic 或 C# 基元类型,它们是 .NET Framework 类型。若要为集合进行此演示,请将以下代码添加到 Example 类中,以创建可返回字符串的泛型字典的方法,按整数建立索引:

        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

请注意,字典必须作为接口返回,该接口通过 Dictionary<TKey, TValue> 实现并映射到 Windows 运行时接口。在这种情况下,接口是 IDictionary<int, string>(在 Visual Basic 中为 IDictionary(Of Integer, String))。将 Windows 运行时类型 IMap<int, string> 传递给托管代码时,它显示为 IDictionary<int, string>,反之(将托管类型传递给 JavaScript 时)亦然。

重要

当托管类型实现多个接口时,JavaScript 使用列表中显示在最前面的接口。例如,如果将 Dictionary<int, string> 返回给 JavaScript 代码,那么无论你将哪个接口指定为返回类型,它都显示为 IDictionary<int, string>。这意味着,如果第一个接口不包括在后续接口中出现的成员,该成员将对 JavaScript 不可见。

若要测试新方法并使用字典,请将 returns1 和 returns2 函数添加到 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>";
}

此 JavaScript 代码有一些需要关注的事情。首先,它包括一个 showMap 函数,用于以 HTML 格式显示字典的内容。在 showMap 的代码中,请注意迭代模式。在 .NET Framework 中,泛型 IDictionary 接口中没有 First 方法,由 Count 属性而不是 Size 方法返回大小。在 JavaScript 中,IDictionary<int, string> 显示为 Windows 运行时类型 IMap<int, string>。(请参见 IMap<K,V> 接口。)

在 returns2 函数中,与前面的示例相同,JavaScript 通过调用 Insert 方法(在 JavaScript 中为 insert)向字典中添加项。

若要运行该应用程序,请选择**“F5”键。若要创建和显示字典的初始内容,请选择“返回 1”按钮。若要向字典中再添加两个项,请选择“返回 2”**按钮。请注意,各项按插入顺序显示,这与 Dictionary<TKey, TValue> 一样。如果希望对这些项排序,可从 GetMapOfNames 返回 SortedDictionary<int, string>。(在前面的示例中使用的 PropertySet 类与 Dictionary<TKey, TValue> 具有不同的内部组织。)

当然,JavaScript 不是强类型语言,因此,使用强类型的泛型集合可能导致一些令人惊讶的结果。再次选择**“返回 2”按钮。JavaScript 会主动将“7”强制为数字 7,将 ct 中存储的数字 7 强制为字符串。它还将字符串“forty”强制为零。但还不止于此。再选择几次“返回 2”**按钮。在托管代码中,即使将值转换为正确类型,Add 方法也会生成重复键异常。相比之下,Insert 方法更新与现有密钥关联的值并返回一个指示是否已向字典中添加新键的 Boolean 值。因此,与键 7 关联的值会不断更改。

另一个意外行为:如果将未指定的 JavaScript 变量作为字符串参数来传递,则会获取字符串“undefined”。简而言之,在向 JavaScript 代码传递 .NET Framework 集合类型时要格外小心。

备注

如果有大量文本要连接,可以通过将代码移入 .NET Framework 方法和使用 StringBuilder 类来更有效地执行此操作,如 showMap 函数所示。

虽然无法从 Windows 运行时组件中公开自己的泛型类型,但可以使用类似如下的代码为 Windows 运行时类返回 .NET Framework 泛型集合:

        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> 实现 IList<T>,它在 JavaScript 中显示为 Windows 运行时类型 IVector<T>

声明事件

可以使用标准 .NET Framework 事件模式或 Windows 运行时所使用的其他模式声明事件。.NET Framework 支持在 System.EventHandler<TEventArgs> 委托和 Windows 运行时 EventHandler<T> 委托之间的等效性,因此,使用 EventHandler<TEventArgs> 是实现标准 .NET Framework 模式的好方法。若要查看其工作原理,请将以下类对添加到 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

在 Windows 运行时中公开事件时,事件参数类从 System.Object 继承。它不像在 .NET Framework 中那样从 System.EventArgs 中继承,因为 EventArgs 不是 Windows 运行时类型。

备注

如果为事件声明自定义事件访问器(在 Visual Basic 中为 Custom 关键字),则必须使用 Windows 运行时事件模式。请参见Windows 运行时组件中的自定义事件和事件访问器

若要处理 Test 事件,请将 events1 函数添加到 default.js 中。events1 函数为 Test 事件创建一个事件处理程序函数,并立即调用 OnTest 方法来引发事件。如果在事件处理程序的主体中设置断点,可以看到传递给单个参数的对象包括源对象以及 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);
}

公开异步操作

.NET Framework 具有一套丰富的、可用于根据 Task 和泛型 Task<TResult> 类进行异步处理和并行处理的工具。若要在 Windows 运行时组件中公开基于任务的异步处理,请使用 Windows 运行时接口 IAsyncActionIAsyncActionWithProgress<TProgress>IAsyncOperation<TResult>IAsyncOperationWithProgress<TResult, TProgress>。(在 Windows 运行时中,运算返回结果,但操作不返回结果。)

本部分演示一个可取消的异步操作,该操作报告进度并返回结果。GetPrimesInRangeAsync 方法使用 AsyncInfo 类生成任务并将其取消和进度报告功能添加到 WinJS.Promise 对象中。首先将以下 using 语句(在 Visual Basic 中为 Imports)添加到 Example 类中:

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

现在,将 GetPrimesInRangeAsync 方法添加到 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 是一个非常简单的质数查找工具,这是设计使然。此处的重点是实现异步操作,因此,简单是非常重要的。在演示取消时,缓慢实现是一个优点。GetPrimesInRangeAsync 通过强力来查找质数:它用小于或等于其平方根的所有整数来除候选数字,而不是仅使用质数来除。逐句通过此代码:

  • 在开始异步操作之前,执行管理活动,如验证参数并对无效输入引发异常。

  • 此实现的关键是 AsyncInfo.Run<TResult, TProgress>(Func<CancellationToken, IProgress<TProgress>, Task<TResult>>) 方法以及作为该方法的唯一参数的委托。委托必须接受取消标记和用于报告进度的接口,还必须返回使用这些参数的已启动任务。当 JavaScript 调用 GetPrimesInRangeAsync 方法时,将执行以下步骤(不一定按此处给定的顺序):

  • 已启动任务表示的 lambda 函数不带任何参数。它是 lambda 函数,因此可以访问标记和 IProgress 接口。每次对候选数字进行计算时,lambda 函数都会执行以下操作:

    • 检查以确定是否已达到进度的下一个百分点。如果已达到,lambda 函数将调用 IProgress<T>.Report 方法,并将百分比传递给 Promise 对象指定的用于报告进度的函数。

    • 如果操作已取消,则使用取消标记引发异常。如果已调用 IAsyncInfo.Cancel 方法(IAsyncOperationWithProgress<TResult, TProgress> 接口继承该方法),则 AsyncInfo.Run 方法设置的连接确保通知取消标记。

  • 当 lambda 函数返回质数列表时,该列表将传递给 WinJS.Promise 对象指定的用于处理结果的函数。

若要创建 JavaScript 承诺并设置取消机制,请将 asyncRun 和 asyncCancel 函数添加到 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();
}

通过调用异步 GetPrimesInRangeAsync 方法,asyncRun 函数创建一个 WinJS.Promise 对象。该对象的 then 方法采用三个函数来处理返回的结果,对错误(包括取消)做出反应,并处理进度报告。在此示例中,将在输出区域中输出返回的结果。取消或完成可重置用于启动和取消操作的按钮。进度报告可更新进度控件。

asyncCancel 函数只调用 WinJS.Promise 对象的 cancel 方法。

若要运行该应用程序,请选择**“F5”键。若要开始异步操作,请选择“异步”按钮。接下来会出现什么情况取决于计算机的速度。如果进度栏在眨眼之间快速完成,则可将传递给 GetPrimesInRangeAsync 的起始数字增大十倍或数十倍。可以通过增加或减少要测试的数字计数来微调操作的持续时间,但在起始数字中添加零将产生更大的影响。若要取消该操作,请选择“取消异步”**按钮。

请参见

概念

.NET for Windows Store 应用程序概述

.NET for Windows Store 应用程序 - 支持的 API

用 C# 和 Visual Basic 创建 Windows 运行时组件

使用 Async 和 Await 的异步编程(C# 和 Visual Basic)

创建 Windows 运行时组件

Windows 运行时组件中的自定义事件和事件访问器

其他资源

.NET Framework 对 Windows 应用商店应用程序和 Windows 运行时的支持情况