Blazor イベント ハンドラーを使用して、C# コードを DOM イベントにアタッチする

完了

ほとんどの HTML 要素では、ページの読み込みの完了、ユーザーによるボタンのクリック、HTML 要素の内容の変更など、何か重要なことが起きたときにトリガーされるイベントが公開されています。 アプリでは、次のいくつかの方法でイベントを処理できます。

  • アプリでは、イベントを無視できます。
  • アプリでは、JavaScript で記述されたイベント ハンドラーを実行して、イベントを処理できます。
  • アプリでは、C# で記述された Blazor イベント ハンドラーを実行して、イベントを処理できます。

このユニットでは、3 番目のオプション (イベントを処理するために C# で Blazor イベント ハンドラーを作成する方法) について詳しく説明します。

Blazor と C# を使用してイベントを処理する

Blazor アプリの HTML マークアップの各要素では、多くのイベントをサポートしています。 これらのイベントのほとんどは、標準の Web アプリケーションで使用できる DOM イベントに対応していますが、コードの記述によってトリガーされるユーザー定義のイベントを作成することもできます。 Blazor でイベントをキャプチャするには、イベントを処理する C# メソッドを記述してから、Blazor ディレクティブを使ってイベントをメソッドにバインドします。 DOM イベントの場合、Blazor ディレクティブでは、@onkeydown@onfocus などの同等の HTML イベントと同じ名前を共有します。 たとえば、Blazor Server アプリを使って生成されるサンプル アプリの Counter.razor ページには、次のコードが含まれています。 このページには、ボタンが表示されます。 ユーザーがボタンを選択すると、ボタンのクリック回数を示すカウンターを増分する IncrementCount メソッドが @onclick イベントでトリガーされます。 カウンター変数の値は、ページの <p> 要素によって表示されます。

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

多くのイベント ハンドラー メソッドは、追加のコンテキスト情報を提供するパラメーターを受け取ります。 このパラメーターは、EventArgs パラメーターと呼ばれます。 たとえば、@onclick イベントでは、ユーザーがどのボタンをクリックしたか、ボタンのクリックと同時に CtrlAlt などのボタンを押したかどうかに関する情報を MouseEventArgs パラメーターで渡します。 メソッドを呼び出すときに、このパラメーターを指定する必要はありません。Blazor ランタイムによって自動的に追加されます。 このパラメーターは、イベント ハンドラーでクエリを実行できます。 次のコードでは、ボタンのクリックと同時にユーザーが Ctrl キーを押した場合に、前述の例に示したカウンターを 5 つずつ増分します。

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>


@code {
    private int currentCount = 0;

    private void IncrementCount(MouseEventArgs e)
    {
        if (e.CtrlKey) // Ctrl key pressed as well
        {
            currentCount += 5;
        }
        else
        {
            currentCount++;
        }
    }
}

その他のイベントでは、さまざまな EventArgs パラメーターが用意されています。 たとえば、@onkeypress イベントでは、ユーザーが押したキーを示す KeyboardEventArgs パラメーターを渡します。 どの DOM イベントにも、この情報が不要な場合は、イベント処理メソッドから EventArgsパラメーターを省略できます。

JavaScript でのイベント処理と Blazor を使用したイベント処理について

従来の Web アプリケーションでは、JavaScript を使用してイベントをキャプチャし、処理します。 HTML <script> 要素の一部として関数を作成し、イベントが発生したときにその関数を呼び出すように配置します。 上で示した Blazor の例と比較するため、次のコードでは、ユーザーが [Click me] ボタンを選ぶたびに値を増やして結果を表示する、HTML ページのフラグメントが示されています。 このコードでは、jQuery ライブラリを使用して DOM にアクセスします。

<p id="currentCount">Current count: 0</p>

<button class="btn btn-primary" onclick="incrementCount()">Click me</button>

<!-- Omitted for brevity -->

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
    var currentCount = 0;

    function incrementCount() {
        currentCount++;
        $('#currentCount').html('Current count:' + currentCount);
    }
</script>

イベント ハンドラーの 2 つのバージョンの構文上の違いに加えて、次の機能の違いに注意してください。

  • JavaScript では、イベントの名前の先頭に @ 記号が付きません。これは Blazor ディレクティブではありません。
  • Blazor コードでは、イベント処理メソッドをイベントにアタッチするときに、メソッドの名前を指定します。 JavaScript では、イベント処理メソッドを呼び出すステートメントを記述し、丸かっこと必要なパラメーターを指定します。
  • 最も重要なのは、JavaScript イベント ハンドラーがクライアントのブラウザーで実行されることです。 Blazor Server アプリを構築している場合、Blazor のイベント ハンドラーはサーバー上で実行され、イベント ハンドラーが完了すると、UI に加えられた変更のみがブラウザーで更新されます。 また、Blazor のメカニズムでは、イベント ハンドラーは、セッション間で共有される静的データにアクセスできます。JavaScript モデルではアクセスできません。 ただし、@onmousemove などの頻繁に発生する一部のイベントの処理では、サーバーへのネットワーク ラウンドトリップが必要になるため、ユーザー インターフェイスの動作が遅くなる可能性があります。 ブラウザーでのこのようなイベントは、JavaScript を使って処理する方がよい場合があります。

重要

イベント ハンドラーから JavaScript コードを使用し、C# Blazor コードを使用して、DOM を操作できます。 ただし、Blazor は DOM の独自のコピーを保持しており、必要に応じてそれを使ってユーザー インターフェイスを更新します。 JavaScript と Blazor コードを使用して DOM 内の同じ要素を変更すると、DOM が破損し、Web アプリのデータのプライバシーとセキュリティが侵害される可能性があります。

イベントを非同期に処理する

既定では、Blazor イベント ハンドラーは同期されます。 イベント ハンドラーが、Web サービスの呼び出しなど、長時間実行される可能性のある操作を実行すると、イベント ハンドラーを実行するスレッドは、操作が完了するまでブロックされます。 これにより、ユーザー インターフェイスの応答が低下する可能性があります。 これに対処するには、イベント ハンドラー メソッドを非同期として指定します。 C# async キーワードを使用します。 メソッドでは、Task オブジェクトを返す必要があります。 次に、イベント ハンドラー メソッド内で await 演算子を使用して、実行時間の長いタスクを別のスレッドで開始し、他の作業のために現在のスレッドを解放できます。 実行時間の長いタスクが完了すると、イベント ハンドラーが再開されます。 次のイベント ハンドラーの例では、時間のかかるメソッドを非同期に実行します。

<button @onclick="DoWork">Run time-consuming operation</button>

@code {
    private async Task DoWork()
    {
        // Call a method that takes a long time to run and free the current thread
        var data = await timeConsumingOperation();

        // Omitted for brevity
    }
}

Note

C# での非同期メソッドの作成について詳しくは、「非同期プログラミングのシナリオ」をご覧ください。

イベントを使用してフォーカスを DOM 要素に設定する

HTML ページでは、ユーザーは要素間をタブ移動できます。フォーカスでは、ページに HTML 要素が表示される順序で自然に移動します。 場合によっては、この順序をオーバーライドし、ユーザーに特定の要素へのアクセスを強制する必要があります。

このタスクを実行する最も簡単な方法は、FocusAsync メソッドを使用します。 これは、ElementReference オブジェクトのインスタンス メソッドです。 ElementReference では、フォーカスを設定する項目を参照する必要があります。 @ref 属性で要素参照を指定し、同じ名前がコード内にある C# オブジェクトを作成します。

次の例では、<button> 要素の @onclick イベント ハンドラーで、フォーカスを <input> 要素に設定しています。 <input> 要素の @onfocus イベント ハンドラーは、要素がフォーカスを取得すると、"Received focus" (フォーカスを取得した) というメッセージを表示します。 <input> 要素は、コード内の InputField 変数を介して参照されます。

<button class="btn btn-primary" @onclick="ChangeFocus">Click me to change focus</button>
<input @ref=InputField @onfocus="HandleFocus" value="@data"/>

@code {
    private ElementReference InputField;
    private string data;

    private async Task ChangeFocus()
    {
        await InputField.FocusAsync();
    }

    private async Task HandleFocus()
    {
        data = "Received focus";
    }

次の図は、ユーザーがボタンを選択した場合の結果を示しています。

Screenshot of the web page after the user has clicked the button to set the focus to the input element.

Note

アプリでは、エラーの発生後にユーザーに入力の変更を求めるなど、特定の理由で特定のコントロールのみにフォーカスを設定する必要があります。 フォーカスの設定を使って、ページ上の要素間を一定の順序で移動するようユーザーに強制しないでください。こうすると、要素を見直して入力を変更したいユーザーをかなりイライラさせる可能性があります。

インライン イベント ハンドラーを記述する

C# では、ラムダ式がサポートされています。 ラムダ式を使用すると、匿名関数を作成できます。 ラムダ式は、ページまたはコンポーネントの他の場所で再利用する必要がない簡易イベント ハンドラーがある場合に便利です。 このユニットの開始時に示したクリック数の最初の例では、IncrementCount メソッドを削除し、代わりに、メソッド呼び出しを同じタスクを実行するラムダ式に置き換えることができます。

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="() => currentCount++">Click me</button>

@code {
    private int currentCount = 0;
}

Note

ラムダ式のしくみについて詳しくは、「ラムダ式と匿名関数」をご覧ください。

この方法は、イベント処理メソッドに他の引数を指定する場合にも役立ちます。 次の例の HandleClick メソッドは、通常のクリック イベント ハンドラーと同じ方法で MouseEventArgs パラメーターを受け取りますが、文字列パラメーターも受け入れます。 メソッドでは、以前の同様にクリック イベントを処理しますが、ユーザーが Ctrl キーを押したというメッセージも表示します。 ラムダ式では、HandleCLick メソッドを呼び出し、MouseEventArgs パラメーター (mouseEvent) と文字列を渡します。

@page "/counter"
@inject IJSRuntime JS

<h1>Counter</h1>

<p id="currentCount">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick='mouseEvent => HandleClick(mouseEvent, "Hello")'>Click me</button>

@code {
    private int currentCount = 0;

    private async Task HandleClick(MouseEventArgs e, string msg)
    {
        if (e.CtrlKey) // Ctrl key pressed as well
        {
            await JS.InvokeVoidAsync("alert", msg);
            currentCount += 5;
        }
        else
        {
            currentCount++;
        }
    }
}

Note

この例では、Blazor に同等の関数がないため、JavaScript の alert 関数を使ってメッセージを表示します。 JavaScript 相互運用機能を使用して、Blazor コードから JavaScript を呼び出します。 この手法の詳細については、別のモジュールで扱います。

イベントの既定の DOM アクションをオーバーライドする

いくつかの DOM イベントには、そのイベントに使用できるイベント ハンドラーの有無に関係なく、イベントの発生時に実行される既定のアクションがあります。 たとえば、<input> 要素の @onkeypress イベントでは、ユーザーが押したキーに対応する文字が常に表示され、キー入力が処理されます。 次の例では、@onkeypress イベントを使用して、ユーザーの入力を大文字に変換します。 さらに、ユーザーが @ 文字を入力すると、イベント ハンドラーによってアラートが表示されます。

<input value=@data @onkeypress="ProcessKeyPress"/>

@code {
    private string data;

    private async Task ProcessKeyPress(KeyboardEventArgs e)
    {
        if (e.Key == "@")
        {
            await JS.InvokeVoidAsync("alert", "You pressed @");
        }
        else
        {
            data += e.Key.ToUpper();
        }
    }
}

このコードを実行して @ キーを押すと、アラートが表示されますが、@ 文字も入力に追加されます。 @ 文字の追加は、イベントの既定のアクションです。

Screenshot of the user input showing the @ character.

この文字が入力ボックスに表示されない場合は、次のように、イベントの preventDefault 属性で既定のアクションをオーバーライドできます。

<input value=@data @onkeypress="ProcessKeyPress" @onkeypress:preventDefault />

イベントはそれでも起動しますが、イベント ハンドラーによって定義されたアクションのみが実行されます。

DOM の子要素の一部のイベントは、その親要素のイベントをトリガーできます。 次の例では、<div> 要素に @onclick イベント ハンドラーが含まれます。 <div> 内の <button> には独自の @onclick イベント ハンドラーがあります。 さらに、<div> には <input> 要素が含まれています。

<div @onclick="HandleDivClick">
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
    <input value=@data @onkeypress="ProcessKeyPress" @onkeypress:preventDefault />
</div>

@code {
    private async Task HandleDivClick()
    {
        await JS.InvokeVoidAsync("alert", "Div click");
    }

    private async Task ProcessKeyPress(KeyboardEventArgs e)
    {
        // Omitted for brevity
    }

    private int currentCount = 0;

    private void IncrementCount(MouseEventArgs e)
    {
        // Omitted for brevity
    }
}

アプリが実行され、<div> 要素によって占有されている領域の要素 (または空の領域) をユーザーがクリックすると、HandleDivClick メソッドが実行され、メッセージが表示されます。 ユーザーが Click me ボタンを選択すると、IncrementCount メソッドが実行され、その後に HandleDivClick が続き、@onclick イベントが DOM ツリーに伝達されます。 <div> が @onclick イベントも処理する別の要素の一部である場合、そのイベント ハンドラーも DOM ツリーのルートなどに対して実行されます。 次に示すように、イベントの stopPropagation 属性を使って、イベントのこのような上方への拡散を抑制できます。

<div @onclick="HandleDivClick">
    <button class="btn btn-primary" @onclick="IncrementCount" @onclick:stopPropagation>Click me</button>
    <!-- Omitted for brevity -->
</div>

EventCallback を使用してコンポーネント間でイベントを処理する

Blazor ページには、1 つ以上の Blazor コンポーネントを含めることができます。コンポーネントは親子リレーションシップで入れ子にすることができます。 EventCallback を使うことにより、子コンポーネントのイベントで、親コンポーネントのイベント ハンドラー メソッドをトリガーできます。 コールバックは、親コンポーネント内のメソッドを参照します。 子コンポーネントでは、コールバックを呼び出してメソッドを実行できます。 このメカニズムは、delegate を使用して、C# アプリケーションのメソッドを参照する場合と似ています。

コールバックでは、1 つのパラメーターを受け取ることができます。 EventCallback はジェネリック型です。 型パラメーターでは、コールバックに渡される引数の型を指定します。

たとえば、次のシナリオを考えてみます。 ユーザーが入力文字列を入力し、大文字、小文字、大文字/小文字の混在文字に変換する、そこから文字をフィルター処理する、その他の種類の変換を実行するなど、それらの文字列を何らかの方法で変換できる TextDisplay という名前のコンポーネントを作成するとします。 ただし、TextDisplay コンポーネントのコードを記述する時点では、変換プロセスがどのようなものになるかわからないため、代わりに、この操作を別のコンポーネントまで先延ばしにしようと考えます。 次に示すのは、TextDisplay コンポーネントのコードです。 これは、ユーザーがテキスト値を入力できる <input> 要素の形式で入力文字列を提供します。

@* TextDisplay component *@
@using WebApplication.Data;

<p>Enter text:</p>
<input @onkeypress="HandleKeyPress" value="@data" />

@code {
    [Parameter]
    public EventCallback<KeyTransformation> OnKeyPressCallback { get; set; }

    private string data;

    private async Task HandleKeyPress(KeyboardEventArgs e)
    {
        KeyTransformation t = new KeyTransformation() { Key = e.Key };
        await OnKeyPressCallback.InvokeAsync(t);
        data += t.TransformedKey;
    }
}

TextDisplay コンポーネントでは、OnKeyPressCallback という名前の EventCallback オブジェクトを使用します。 HandleKeypress メソッドのコードでは、コールバックを呼び出します。 @onkeypress イベント ハンドラーは、キーが押され、HandleKeypress メソッドを呼び出すたびに実行されます。 HandleKeypress メソッドでは、ユーザーが押したキーを使用して KeyTransformation オブジェクトを作成し、このオブジェクトをパラメーターとしてコールバックに渡します。 KeyTransformation 型は、次の 2 つのフィールドで構成される単純なクラスです。

namespace WebApplication.Data
{
    public class KeyTransformation
    {
        public string Key { get; set; }
        public string TransformedKey { get; set; }
    }
}

key フィールドには、ユーザーが入力した値が含まれ、TransformedKey フィールドでは、処理が完了すると、変換されたキー値を保持します。

この例では、EventCallback オブジェクトはコンポーネント パラメーターであり、その値はコンポーネントの作成時に指定されます。 このアクションは、TextTransformer という名前の別のコンポーネントによって実行されます。

@page "/texttransformer"
@using WebApplication.Data;

<h1>Text Transformer - Parent</h1>

<TextDisplay OnKeypressCallback="@TransformText" />

@code {
    private void TransformText(KeyTransformation k)
    {
        k.TransformedKey = k.Key.ToUpper();
    }
}

TextTransformer コンポーネントは、TextDisplay コンポーネントのインスタンスを作成する Blazor ページです。 ページのコード セクションの TransformText メソッドへの参照を OnKeypressCallback パラメーターに設定します。 TransformText メソッドでは、引数として指定された KeyTransformation オブジェクトを受け取り、大文字に変換された Key プロパティで見つかった値を TransformedKey プロパティに入力します。 次の図は、TextTransformer ページによって表示される TextDisplay コンポーネントの <input> フィールドにユーザーが値を入力したときの、制御のフローを示したものです。

Diagram of the flow of control with an EventCallback in a child component.

この方法の利点は、OnKeypressCallback パラメーターのコールバックを提供する任意のページで TextDisplay コンポーネントを使用できることです。 表示と処理は完全に分離されています。 TextDisplay コンポーネントの EventCallback パラメーターのシグネチャに一致する他のコールバックで TransformText メソッドを切り替えることができます。

コールバックが適切な EventArgs パラメーターで型指定されている場合は、中間メソッドを使用せずに、コールバックをイベント ハンドラーに直接接続できます。 たとえば、子コンポーネントは、次のような @onclick などのマウス イベントを処理できるコールバックを参照する場合があります。

<button @onclick="OnClickCallback">
    Click me!
</button>

@code {
    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

この場合、EventCallbackMouseEventArgs 型パラメーターを受け取ります。そのため、@onclick イベントのハンドラーとして指定できます。

知識を確認

1.

Blazor コンポーネント間でイベントを渡すために使用する機能は何ですか?

2.

HTML DOM 要素の既定のアクションをオーバーライドするには、どのようなメソッドを使用しますか?