次の方法で共有


C# または Visual Basic Windows ランタイム コンポーネントの作成と JavaScript からの呼び出しに関するチュートリアル

このチュートリアルでは、Visual Basic または C# で .NET を使って、Windows ランタイム コンポーネントにパッケージ化される独自の Windows ランタイム型を作成する方法と、JavaScript Universal Windows Platform (UWP) アプリからそのコンポーネントを呼び出す方法について説明します。

Visual Studio を使用すると、C# または Visual Basic で記述された Windows ランタイム コンポーネント (WRC) プロジェクト内に独自のカスタム Windows ランタイム型を簡単に作成して配置し、次に JavaScript アプリケーション プロジェクトからその WRC を参照して、そのアプリケーションからそれらのカスタム型を使用することができます。

内部的には、UWP アプリケーションで許可されているすべての .NET 機能をその Windows ランタイム型で使用できます。

外部的には、型のメンバーによってパラメーターと戻り値の Windows ランタイム型のみを公開できます。 ソリューションをビルドすると、Visual Studio によって .NET WRC プロジェクトがビルドされ、Windows メタデータ (.winmd) ファイルを作成するビルド ステップが実行されます。 これは、Visual Studio がアプリに含めるWindows ランタイム コンポーネントです。

Note

.NET により、一般的に使用される .NET の型 (プリミティブ データ型やコレクション型など) が対応する Windows ランタイム型に自動的にマップされます。 .NET のこれら型は、Windows ランタイム コンポーネントのパブリック インターフェイス内で使用でき、対応する Windows ランタイム型としてコンポーネントのユーザーに表示されます。 「C# および Visual Basic を使用した Windows ランタイム コンポーネント」を参照してください。

前提条件:

Note

JavaScript を使用した Universal Windows Platform (UWP) プロジェクトは、Visual Studio 2019 ではサポートされていません。 「Visual Studio 2019 の JavaScript と TypeScript」を参照してください。 このトピックに従うには、Visual Studio 2017 を使用することをお勧めします。 「Visual Studio 2017 の JavaScript」を参照してください。

単純なWindows ランタイム クラスの作成

このセクションでは、JavaScript UWP アプリケーションを作成し、Visual Basic または C# Windows ランタイム コンポーネント プロジェクトをソリューションに追加します。 Windows ランタイム型を定義し、JavaScript から型のインスタンスを作成する方法を説明します。また、静的メンバーとインスタンス メンバーを呼び出す方法についても説明します。 コンポーネントに重点を置くために、サンプル アプリの外観は意図的に目立たないようにしています。

  1. Visual Studio で、新しい JavaScript プロジェクトを作成します。メニュー バーで、 File、New、Project を選択します。 [新しいプロジェクト] ダイアログ ボックスの [インストールされたテンプレート セクションで、JavaScript を選択し、[ Windows] を選択し、[Universalします。 (Windows が使用できない場合は、Windows 8 以降を使用していることを確認してください)。 Blank Application テンプレートを選択し、プロジェクト名として SampleApp を入力します。

  2. コンポーネント プロジェクトの作成: ソリューション エクスプローラーで、SampleApp ソリューションのショートカット メニューを開き、[追加]< を選択し、新しいプロジェクトを選択して、新しい C# または Visual Basic プロジェクトをソリューションに追加します。 [新しいプロジェクトの追加] ダイアログ ボックスの [インストールされたテンプレート] セクションで、[Visual Basic または Visual C#] を選択し、[Windows] を選択し、[Universal] をしますWindows ランタイム コンポーネント テンプレートを選択し、プロジェクト名として「SampleComponent」と入力します。

  3. クラスの名前を Example に変更します。 既定では、クラスは public sealed (Visual Basic では Public NotInheritable ) としてマークされていることに注意してください。 コンポーネントから公開するすべての Windows ランタイム クラスを保護する必要があります。

  4. クラスに、 static メソッド (Visual Basic では Shared メソッド) とインスタンス プロパティの 2 つの単純なメンバーを追加します。

    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 プロジェクトのショートカット メニューを開き、Build を選択します。

  6. ソリューション エクスプローラーの JavaScript プロジェクトで、References のショートカット メニューを開き、[参照の追加] を選択して、Reference Manager を開きます。 プロジェクト を選択し、ソリューション を選択します。 SampleComponent プロジェクトのチェック ボックスをオンにし、 OK を選択して参照を追加します。

JavaScript からコンポーネントを呼び出す

JavaScript のWindows ランタイム型を使用するには、Visual Studio テンプレートによって提供されるdefault.js ファイル (プロジェクトの js フォルダー内) に匿名関数に次のコードを追加します。 app.oncheckpoint イベント ハンドラーの後、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;
}

各メンバー名の最初の文字が大文字から小文字に変更されていることに注意してください。 この変換は、Windows ランタイムの自然な使用を可能にするために JavaScript が提供するサポートの一部です。 名前空間とクラス名は Pascal で大文字と小文字が区別されます。 メンバー名は、すべて小文字のイベント名を除き、キャメル ケースです。 「 JavaScript でのWindows ランタイムの使用を参照してください。 キャメルケースのルールは混乱を招く可能性があります。 通常、一連の最初の大文字は小文字で表示されますが、3 つの大文字の後に小文字が続く場合、最初の 2 文字のみが小文字で表示されます。たとえば、IDStringKind という名前のメンバーは idStringKind として表示されます。 Visual Studio では、Windows ランタイム コンポーネント プロジェクトをビルドし、JavaScript プロジェクトで IntelliSense を使用して正しい大文字と小文字を区別できます。

同様に、.NET は、マネージド コードでの Windows ランタイムの自然な使い方を可能にするためのサポートを提供します。 これについては、この記事の後続のセクション、および「C# および Visual Basic を使用した Windows ランタイム コンポーネント」と UWP アプリ用と Windows ランタイム用の .NET サポートに関する記事で説明されています。

シンプルなユーザー インターフェイスを作成する

JavaScript プロジェクトで、default.html ファイルを開き、次のコードに示すように本文を更新します。 このコードには、サンプル アプリのコントロールの完全なセットが含まれており、クリック イベントの関数名を指定します。

アプリを初めて実行するときは、Basics1 ボタンと Basics2 ボタンだけがサポートされています。

<body>
            <div id="buttons">
            <button id="button1" >Basics 1</button>
            <button id="button2" >Basics 2</button>

            <button id="runtimeButton1">Runtime 1</button>
            <button id="runtimeButton2">Runtime 2</button>

            <button id="returnsButton1">Returns 1</button>
            <button id="returnsButton2">Returns 2</button>

            <button id="events1Button">Events 1</button>

            <button id="btnAsync">Async</button>
            <button id="btnCancel" disabled="disabled">Cancel Async</button>
            <progress id="primeProg" value="25" max="100" style="color: yellow;"></progress>
        </div>
        <div id="output">
        </div>
</body>

JavaScript プロジェクトの css フォルダーで、default.cssを開きます。 次のように本文セクションを変更し、ボタンのレイアウトと出力テキストの配置を制御するスタイルを追加します。

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

次に、default.jsで app.onactivated の processAll 呼び出しに then 句を追加して、イベント リスナー登録コードを追加します。 setPromise を呼び出す既存のコード行を置き換え、次のコードに変更します。

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

これは、HTML でクリック イベント ハンドラーを直接追加するよりも、HTML コントロールにイベントを追加する方が優れた方法です。 「"Hello, world" アプリを作成する (JS)」をご覧ください。

アプリをビルドして実行する

ビルドする前に、すべてのプロジェクトのターゲット プラットフォームを、お使いのコンピューターに応じて Arm、x64、または x86 に変更します。

ソリューションをビルドして実行するには、F5 キーを選択します。 (SampleComponent が未定義であることを示す実行時エラー メッセージが表示された場合、クラス ライブラリ プロジェクトへの参照がありません)。

Visual Studio はまずクラス ライブラリをコンパイルしてから、Winmdexp.exe (Windows ランタイム メタデータ エクスポート ツール) を実行してWindows ランタイム コンポーネントを作成する MSBuild タスクを実行します。 コンポーネントは、マネージド コードとコードを記述する Windows メタデータの両方を含む .winmd ファイルに含まれています。 WinMdExp.exeでは、Windows ランタイム コンポーネントで無効なコードを記述するとビルド エラー メッセージが生成され、エラー メッセージが Visual Studio IDE に表示されます。 Visual Studio は UWP アプリケーションのアプリケーション パッケージ (.appx ファイル) にコンポーネントを追加し、適切なマニフェストを生成します。

[基本 1] ボタンを選択して、静的な GetAnswer メソッドからの戻り値を出力領域に割り当て、Example クラスのインスタンスを作成し、その SampleProperty プロパティの値を出力領域に表示します。 出力を次に示します。

"The answer is 42."
0

[基本 2] ボタンを選択すると、SampleProperty プロパティの値がインクリメントされ、出力領域に新しい値が表示されます。 文字列や数値などのプリミティブ型は、パラメーター型と戻り値の型として使用でき、マネージド コードと JavaScript の間で渡すことができます。 JavaScript の数値は倍精度浮動小数点形式で格納されるため、.NET Framework の数値型に変換されます。

既定では、ブレークポイントは JavaScript コードでのみ設定できます。 Visual Basic または C# のコードをデバッグするときは、「C# および Visual Basic での Windows ランタイム コンポーネントの作成」をご覧ください。

デバッグを停止してアプリを閉じるには、アプリから Visual Studio に切り替えて、Shift + F5 キーを押します。

JavaScript とマネージド コードからのWindows ランタイムの使用

Windows ランタイムは、JavaScript またはマネージド コードから呼び出すことができます。 Windows ランタイムオブジェクトは 2 つのオブジェクト間でやり取りでき、イベントはどちらの側からでも処理できます。 ただし、JavaScript と .NET では Windows ランタイムをサポートする方法が異なるため、この 2 つの環境での Windows ランタイム型の使用方法は細部が異なります。 次の例では、 Windows.Foundation.Collections.PropertySet クラスを使用して、これらの違いを示します。 この例では、マネージド コードで PropertySet コレクションのインスタンスを作成し、コレクション内の変更を追跡するイベント ハンドラーを登録します。 次に、コレクションを取得し、独自のイベント ハンドラーを登録し、コレクションを使用する JavaScript コードを追加します。 最後に、マネージド コードからコレクションに変更を加え、マネージド例外を処理する JavaScript を示すメソッドを追加します。

重要: この例では、UI スレッドでイベントを発生させます。 非同期呼び出しなど、バックグラウンド スレッドからイベントを発生させる場合は、JavaScript がイベントを処理するために追加の作業を行う必要があります。 詳しくは、「Windows ランタイム コンポーネントでのイベントの発生」をご覧ください。

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 オブジェクト) が、Windows ランタイム インターフェイス IObservableMap<K, V> をインスタンス化したものである IObservableMap<string, object> インターフェイス (Visual Basic では IObservableMap(Of String, Object)) にキャストされる点が異なります。 (必要に応じて、送信者をその型にキャストできます)。また、イベント引数は、オブジェクトとしてではなくインターフェイスとして表示されます。

default.js ファイルで、次のように Runtime1 関数を追加します。 このコードでは、PropertySetStats オブジェクトを作成し、その PropertySet コレクションを取得し、MapChanged イベントを処理するための独自のイベント ハンドラー onMapChanged 関数を追加します。 コレクションに変更を加える後、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;
        default:
            break;
     }

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

JavaScript でWindows ランタイムイベントを処理する方法は、.NET Framework コードでのイベントの処理方法とは大きく異なります。 JavaScript イベント ハンドラーは、引数を 1 つだけ受け取ります。 Visual Studio デバッガーでこのオブジェクトを表示する場合、最初のプロパティは送信者です。 イベント引数インターフェイスのメンバーも、このオブジェクトに直接表示されます。

アプリを実行するには、F5 キーを押します。 クラスがシールされていない場合は、"封印されていない型 'SampleComponent.Example' のエクスポートは現在サポートされていません。 シールとしてマークしてください。

Runtime 1 ボタンを選択します。 イベント ハンドラーは、要素が追加または変更されると変更を表示し、最後に DisplayStats メソッドを呼び出してカウントの概要を生成します。 デバッグを停止してアプリを閉じるには、Visual Studio に戻り、Shift + F5 キーを押します。

マネージ コードから PropertySet コレクションにさらに 2 つの項目を追加するには、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

このコードでは、2 つの環境でWindows ランタイム型を使用する方法のもう 1 つの違いを示します。 このコードを自分で入力すると、JavaScript コードで使用した insert メソッドが IntelliSense に表示されないことがわかります。 代わりに、.NET のコレクションで一般的に見られる Add メソッドが表示されます。 これは、一般的に使われる一部のコレクション インターフェイスは、Windows ランタイムと .NET で名前が異なりますが、機能はほぼ同じであるためです。 これらのインターフェイスをマネージド コードで使用すると、.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 + "<br/>";
   }

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

前に行ったのと同じ方法でイベント ハンドラー登録コードを追加します。

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

アプリを実行するには、F5 キーを押します。 [Runtime 1] を選択し[Runtime 2]>します。 JavaScript イベント ハンドラーは、コレクションへの最初の変更を報告します。 ただし、2 つ目の変更には重複するキーがあります。 .NET Framework ディクショナリのユーザーは、Add メソッドが例外をスローすることを期待しており、これが発生します。 JavaScript で .NET の例外を処理します。

JavaScript コードから例外のメッセージを表示することはできません。 メッセージ テキストはスタック トレースに置き換えられます。 詳しくは、「C# および Visual Basic での Windows ランタイム コンポーネントの作成」の「例外のスロー」をご覧ください。

これに対し、JavaScript が重複するキーを使用して insert メソッドを呼び出すと、項目の値が変更されました。 「C# および Visual Basic を使用した Windows ランタイム コンポーネント」に記載されているように、動作のこの違いは、JavaScript と .NET で Windows ランタイムをサポートする方法が異なることに起因しています。

コンポーネントからマネージド型を返す

前に説明したように、JavaScript コードと C# または Visual Basic コードの間で、ネイティブのWindows ランタイム型を自由にやり取りできます。 ほとんどの場合、型名とメンバー名はどちらの場合も同じになります (ただし、メンバー名は JavaScript で小文字で始まる点が異なります)。 ただし、前のセクションでは、PropertySet クラスにマネージド コードに異なるメンバーが含まれているように見えました。 (たとえば、JavaScript では insert メソッドを呼び出し、.NET コードでは Add メソッドを呼び出しました)。このセクションでは、これらの違いが JavaScript に渡される .NET Framework 型に与える影響について説明します。

コンポーネントで作成した型または JavaScript からコンポーネントに渡したWindows ランタイム型を返すだけでなく、マネージド コードで作成されたマネージド型を、対応するWindows ランタイム型であるかのように JavaScript に返すことができます。 ランタイム クラスの最初の単純な例でも、メンバーのパラメーターと戻り値の型は、.NET Framework 型である Visual Basic または C# プリミティブ型でした。 コレクションに対してこれを示すには、次のコードを 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 に渡されるときに逆の値が true になります。

重要: マネージド型が複数のインターフェイスを実装している場合、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);
}

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

イベント登録コードを他のイベント登録コードと同じ then ブロックに追加します。

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

この JavaScript コードについては、いくつかの興味深い点を確認してください。 まず第一に、辞書の内容を HTML で表示する showMap 関数が含まれています。 showMap のコードで、反復パターンに注目してください。 .NET では、ジェネリック IDictionary インターフェイスに First メソッドはありません。また、サイズは Size メソッドではなく Count プロパティによって返されます。 JavaScript では、IDictionary<int、string> は Windows ランタイム 型 IMap<int,string> のように見えます。 ( を参照してください。IMap<K,V> インターフェイス。)

returns2 関数では、前の例と同様に、JavaScript は Insert メソッド (JavaScript に挿入) を呼び出してディクショナリに項目を追加します。

アプリを実行するには、F5 キーを押します。 ディクショナリの初期コンテンツを作成して表示するには、[ Returns 1 ] ボタンを選択します。 辞書にさらに 2 つのエントリを追加するには、 [戻る 2 ] ボタンを選択します。 Dictionary<TKey、TValue> から予想されるとおり、エントリは挿入順に表示されます。 並べ替えを行う場合は、GetMapOfNames から SortedDictionary<int,string> を返すことができます。 (前の例で使用した PropertySet クラスには、Dictionary とは異なる内部組織があります<TKey、TValue>.)

もちろん、JavaScript は厳密に型指定された言語ではないため、厳密に型指定されたジェネリック コレクションを使用すると、驚くべき結果が得られます。 [戻る 2] ボタンをもう一度選択します。 JavaScript では、"7" を数値 7 に強制し、ct に格納されている数値 7 を文字列に強制的に強制します。 そして、文字列 "40" をゼロに強制します。 しかし、それは始まりにすぎません。 [戻る 2] ボタンをもう数回選択します。 マネージド コードでは、値が正しい型にキャストされた場合でも、Add メソッドは重複するキー例外を生成します。 これに対し、Insert メソッドは、既存のキーに関連付けられている値を更新し、新しいキーがディクショナリに追加されたかどうかを示すブール値を返します。 このため、キー 7 に関連付けられている値は変化し続けます。

もう 1 つの予期しない動作: 未割り当ての JavaScript 変数を文字列引数として渡すと、文字列 "undefined" が返されます。 つまり、.NET Framework コレクション型を JavaScript コードに渡すときは注意が必要です。

注: 大量のテキストを連結する場合は、showMap 関数に示すように、コードを .NET Framework メソッドに移動し、StringBuilder クラスを使うことで、この連結をより効率的に実行できます。

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 の IVector<T> Windows ランタイム型として表示されます。

イベントの宣言

イベントは、標準の .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 から継承されます。 EventArgs は Windows ランタイム型ではないため、.NET の場合と同様に、System.EventArgs からは継承しません。

イベントのカスタム イベント アクセサー (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);
}

イベント登録コードを他のイベント登録コードと同じ then ブロックに追加します。

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

非同期操作の公開

.NET Framework には、Task クラスと汎用 Task<TResult> クラスに基づいて、非同期処理と並列処理のための豊富なツール セットがあります。 Windows ランタイム コンポーネントでタスク ベースの非同期処理を公開するには、 IAsyncActionIAsyncActionWithProgress<TProgress>IAsyncOperation<TResult>IAsyncOperationWithProgress<TResult、TProgress> のWindows ランタイム インターフェイスを使用します。 (Windows ランタイムでは、操作は結果を返しますが、アクションは返しません)。

このセクションでは、進行状況を報告し、結果を返すキャンセル可能な非同期操作を示します。 GetPrimesInRangeAsync メソッドは、 AsyncInfo クラスを使用してタスクを生成し、そのキャンセル機能と進行状況レポート機能を WinJS.Promise オブジェクトに接続します。 まず、GetPrimesInRangeAsync メソッドをサンプル クラスに追加します。

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 は非常に単純な素数ファインダーであり、仕様です。 ここでは非同期操作の実装に重点を置いているので、シンプルさが重要であり、取り消しを示す場合は、実装が遅いのが利点です。 GetPrimesInRangeAsync は、ブルート フォースによって素数を検索します。素数のみを使用するのではなく、平方根以下のすべての整数で候補を除算します。 次のコードをステップ実行します。

  • 非同期操作を開始する前に、パラメーターの検証や無効な入力の例外のスローなどのハウスキーピング アクティビティを実行します。

  • この実装で重要になるのは、AsyncInfo.Run<TResult, TProgress>(Func<CancellationToken, IProgress<TProgress>, Task<TResult>>) メソッドと、このメソッドの唯一のパラメーターであるデリゲートです。 デリゲートは、進行状況を報告するためのキャンセル トークンとインターフェイスを受け入れ、それらのパラメーターを使用する開始タスクを返す必要があります。 JavaScript が GetPrimesInRangeAsync メソッドを呼び出すと、次の手順が実行されます (必ずしもここに示されている順序ではありません)。

    • WinJS.Promise オブジェクトは、返された結果の処理、キャンセルへの対応、進行状況レポートの処理を行う関数を提供します。

    • AsyncInfo.Run メソッドは、キャンセル ソースと、IProgress<T> インターフェイスを実装するオブジェクトを作成します。 デリゲートには、キャンセル ソースから CancellationToken トークンと IProgress<T> インターフェイスの両方を渡します。

      注: Promise オブジェクトが取り消し機能に対応する関数を提供しない場合、AsyncInfo.Run は引き続き取り消し可能なトークンを渡し、取り消しが引き続き発生する可能性があります。 Promise オブジェクトが進行状況の更新を処理する関数を提供しない場合でも、AsyncInfo.Run は IProgress<T> を実装するオブジェクトを提供しますが、そのレポートは無視されます。

    • デリゲートは Task.Run<TResult>(Func<TResult>, CancellationToken) メソッドを使って、トークンと進行状況インターフェイスを使う開始タスクを作成します。 開始タスクのデリゲートは、目的の結果を計算するラムダ関数によって提供されます。 詳細については、すぐに説明します。

    • AsyncInfo.Run メソッドは、IAsyncOperationWithProgress<TResult、TProgress> インターフェイスを実装するオブジェクトを作成し、Windows ランタイムキャンセル メカニズムをトークン ソースに接続し、Promise オブジェクトの進行状況レポート関数を IProgress<T> インターフェイスに接続します。

    • IAsyncOperationWithProgress<TResult、TProgress> インターフェイスが JavaScript に返されます。

  • 開始タスクによって表されるラムダ関数は、引数を受け取りません。 ラムダ関数であるため、トークンと IProgress インターフェイスにアクセスできます。 候補番号が評価されるたびに、ラムダ関数は次のようになります。

    • 次の進行状況ポイントに達したかどうかを確認します。 存在する場合、ラムダ関数は IProgress<T> を呼び出します。Report メソッドと割合は、進行状況を報告するために Promise オブジェクトが指定した関数に渡されます。
    • キャンセル トークンを使用して、操作が取り消された場合に例外をスローします。 IAsyncInfo.Cancel メソッド (IAsyncOperationWithProgress<TResult、TProgress> インターフェイスが継承する) が呼び出された場合、AsyncInfo.Run メソッドによって設定された接続によって、キャンセル トークンが確実に通知されます。
  • ラムダ関数が素数のリストを返すと、結果を処理するために WinJS.Promise オブジェクトが指定した関数にリストが渡されます。

JavaScript Promise を作成し、キャンセル メカニズムを設定するには、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();
}

前と同じイベント登録コードを忘れないでください。

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

非同期 GetPrimesInRangeAsync メソッドを呼び出すことによって、asyncRun 関数は WinJS.Promise オブジェクトを作成します。 オブジェクトの then メソッドは、返された結果を処理し、エラー (キャンセルを含む) に対応し、進行状況レポートを処理する 3 つの関数を受け取ります。 この例では、返された結果が出力領域に出力されます。 取り消しまたは完了すると、操作を起動および取り消すボタンがリセットされます。 進行状況レポートでは、進行状況コントロールが更新されます。

asyncCancel 関数は、WinJS.Promise オブジェクトの cancel メソッドを呼び出すだけです。

アプリを実行するには、F5 キーを押します。 非同期操作を開始するには、 Async ボタンを選択します。 次に何が起こるかは、コンピューターの速度によって異なります。 点滅する時間が経過する前に進行状況バーが完了に zip 圧縮された場合は、GetPrimesInRangeAsync に渡される開始番号のサイズを 10 の 1 つ以上の要因で増やします。 テストする数値の数を増減することで、操作の期間を微調整できますが、開始番号の中央にゼロを追加すると大きな影響を与えます。 操作を取り消すには、 Cancel Async ボタンを選択します。