ハンドラーを使用してコントロールをカスタマイズする

Browse sample. サンプルを参照する

ハンドラーをカスタマイズして、コントロールの API を通じて可能なカスタマイズを超えて、クロスプラットフォーム コントロールの外観と動作を拡張できます。 クロスプラットフォーム コントロールのネイティブ ビューを変更するこのカスタマイズは、次のいずれかの方法でハンドラーのマッパーを変更することによって実現されます。

  • PrependToMapping は、.NET MAUI コントロール マッピングが適用される前にハンドラーのマッパーを変更します。
  • ModifyMapping は、既存のマッピングを変更します。
  • AppendToMapping は、.NET MAUI コントロール マッピングが適用された後にハンドラーのマッパーを変更します。

これらの各メソッドには、2 つの引数を必要とする同じシグネチャがあります:

  • string ベースのキー。 .NET MAUI によって提供されるマッピングのいずれかを変更する場合は、.NET MAUI で使用されるキーを指定する必要があります。 .NET MAUI コントロール マッピングで使用されるキー値は、nameof(IEntry.IsPassword) などのインターフェイス名とプロパティ名に基づいています。 各クロスプラットフォーム コントロールを抽象化するインターフェイスとそのプロパティについては「こちら」を参照してください。 これは、プロパティが変更されるたびにハンドラーのカスタマイズを実行する場合に使う必要があるキー形式です。 それ以外の場合、キーは任意の値にすることができます。型が公開しているプロパティの名前と対応している必要はありません。 たとえば、 MyCustomization は、カスタマイズとして実行されるネイティブ ビューの変更を使用して、キーとして指定できます。 ただし、このキー形式の結果として、ハンドラーのマッパーが最初に変更されたときにのみ、ハンドラーのカスタマイズが実行されます。
  • Action は、ハンドラーのカスタマイズを実行するメソッドを表します。 Action は、2 つの引数を指定します:
    • カスタマイズされるハンドラーのインスタンスを提供する handler 引数。
    • ハンドラーが実装するクロスプラットフォーム コントロールのインスタンスを提供する view 引数。

重要

ハンドラーのカスタマイズはグローバルであり、特定のコントロール インスタンスにスコープされません。 ハンドラーのカスタマイズは、アプリ内の任意の場所で行うことができます。 ハンドラーをカスタマイズすると、その型のすべてのコントロール (アプリ内のあらゆる場所) に影響します。

各ハンドラー クラスは、その PlatformView プロパティを介してクロスプラットフォーム コントロールのネイティブ ビューを公開します。 このプロパティにアクセスすると、ネイティブ ビュー プロパティの設定、ネイティブ ビュー メソッドの呼び出し、ネイティブ ビュー イベントのサブスクライブを行うことができます。 さらに、ハンドラーによって実装されるクロスプラットフォーム コントロールは、その VirtualView プロパティを介して公開されます。

ハンドラーは、条件付きコンパイルを使用してプラットフォームごとにカスタマイズし、プラットフォームに基づいてマルチターゲット コードにカスタマイズできます。 または、部分クラスを使用して、コードをプラットフォーム固有のフォルダーとファイルに整理することもできます。 条件付きコンパイルの詳細については、「条件付きコンパイル」を参照してください。

コントロールをカスタマイズする

.NET MAUI Entry ビューは、IEntry インターフェイスを実装する単一行のテキスト入力コントロールです。 EntryHandler は、Entry ビューを各プラットフォームの次のネイティブ ビューにマッピングします:

  • [iOS/Mac Catalyst]: UITextField
  • Android: AppCompatEditText
  • Windows: TextBox

次の図は、Entry ビューが EntryHandler を介してネイティブ ビューにどのようにマッピングされるかを示しています。

Entry handler architecture.

EntryHandler クラスの Entry プロパティ マッパーは、クロスプラットフォーム コントロール プロパティをネイティブ ビュー API にマッピングします。 これにより、プロパティが Entry に設定されると、基盤になるネイティブ ビューが必要に応じて更新されます。

プロパティ マッパーは、各プラットフォームで Entry をカスタマイズするように変更できます。

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryPage : ContentPage
{
    public CustomizeEntryPage()
    {
        InitializeComponent();
        ModifyEntry();
    }

    void ModifyEntry()
    {
        Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("MyCustomization", (handler, view) =>
        {
#if ANDROID
            handler.PlatformView.SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
            handler.PlatformView.EditingDidBegin += (s, e) =>
            {
                handler.PlatformView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
            };
#elif WINDOWS
            handler.PlatformView.GotFocus += (s, e) =>
            {
                handler.PlatformView.SelectAll();
            };
#endif
        });
    }
}

この例では、Entry カスタマイズはページ クラスで行われます。 そのため、Android、iOS、および Windows 上のすべての Entry コントロールは、 CustomizeEntryPage のインスタンスが作成されるとカスタマイズされます。 カスタマイズは、ハンドラーの PlatformView プロパティにアクセスすることで実行されます。これにより、各プラットフォームのクロスプラットフォーム コントロールにマップされるネイティブ ビューへのアクセスが提供されます。 次に、ネイティブ コードは、フォーカスを取得したときに Entry 内のすべてのテキストを選択してハンドラーをカスタマイズします。

マッパーの詳細については、「Mappers」を参照してください。

特定のコントロール インスタンスをカスタマイズする

ハンドラーはグローバルであり、コントロールのハンドラーをカスタマイズすると、アプリ内の同じタイプのすべてのコントロールがカスタマイズされます。 ただし、特定のコントロール インスタンス用のハンドラーは、コントロールをサブクラス化し、そのコントロールがサブクラス化された型の場合にのみ基本コントロール型のハンドラーを変更することによってカスタマイズできます。 たとえば、複数の Entry コントロールを含むページで特定の Entry コントロールをカスタマイズするには、まず Entry コントロールをサブクラス化します。

namespace CustomizeHandlersDemo.Controls
{
    internal class MyEntry : Entry
    {
    }
}

次に、プロパティ マッパーを介して EntryHandler をカスタマイズし、MyEntry インスタンスに対してのみ必要な変更を実行できます:

Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("MyCustomization", (handler, view) =>
{
    if (view is MyEntry)
    {
#if ANDROID
        handler.PlatformView.SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
        handler.PlatformView.EditingDidBegin += (s, e) =>
        {
            handler.PlatformView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
        };
#elif WINDOWS
        handler.PlatformView.GotFocus += (s, e) =>
        {
            handler.PlatformView.SelectAll();
        };
#endif
    }
});

ハンドラーのカスタマイズが App クラスで実行される場合、アプリ内のすべての MyEntry インスタンスはハンドラーの変更に従ってカスタマイズされます。

ハンドラーのライフサイクルを使用してコントロールをカスタマイズする

すべてのハンドラー ベースの .NET MAUI コントロールは、HandlerChanging イベントと HandlerChanged イベントをサポートしています。 クロスプラットフォーム コントロールを実装するネイティブ ビューが使用可能で初期化されると、HandlerChanged イベントが発生します。 HandlerChanging イベントは、コントロールのハンドラーがクロスプラットフォーム コントロールから削除されるときに発生します。 ハンドラーのライフサイクル イベントの詳細については、「ハンドラーのライフサイクル」をご覧ください。

ハンドラーのライフサイクルは、ハンドラーのカスタマイズを実行するために使用できます。 たとえば、ネイティブ ビュー イベントをサブスクライブやサブスクライブ解除を行うには、カスタマイズするクロスプラットフォーム コントロールの HandlerChanged イベントと HandlerChanging イベントのイベント ハンドラー を登録する必要があります。

<Entry HandlerChanged="OnEntryHandlerChanged"
       HandlerChanging="OnEntryHandlerChanging" />

ハンドラーは、条件付きコンパイルを使用するか、部分クラスを使用してコードをプラットフォーム固有のフォルダーとファイルに整理することで、プラットフォームごとにカスタマイズできます。 フォーカスを取得したときにすべてのテキストが選択されるように Entry をカスタマイズすることで、各方法を順番に説明します。

条件付きコンパイル

HandlerChanged イベントと HandlerChanging イベントのイベント ハンドラー を含む分離コード ファイルを、条件付きコンパイルを使用した次の例に示します。

#if ANDROID
using AndroidX.AppCompat.Widget;
#elif IOS || MACCATALYST
using UIKit;
#elif WINDOWS
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml;
#endif

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryHandlerLifecyclePage : ContentPage
{
    public CustomizeEntryHandlerLifecyclePage()
    {
        InitializeComponent();
    }

    void OnEntryHandlerChanged(object sender, EventArgs e)
    {
        Entry entry = sender as Entry;
#if ANDROID
        (entry.Handler.PlatformView as AppCompatEditText).SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
        (entry.Handler.PlatformView as UITextField).EditingDidBegin += OnEditingDidBegin;
#elif WINDOWS
        (entry.Handler.PlatformView as TextBox).GotFocus += OnGotFocus;
#endif
    }

    void OnEntryHandlerChanging(object sender, HandlerChangingEventArgs e)
    {
        if (e.OldHandler != null)
        {
#if IOS || MACCATALYST
            (e.OldHandler.PlatformView as UITextField).EditingDidBegin -= OnEditingDidBegin;
#elif WINDOWS
            (e.OldHandler.PlatformView as TextBox).GotFocus -= OnGotFocus;
#endif
        }
    }

#if IOS || MACCATALYST                   
    void OnEditingDidBegin(object sender, EventArgs e)
    {
        var nativeView = sender as UITextField;
        nativeView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
    }
#elif WINDOWS
    void OnGotFocus(object sender, RoutedEventArgs e)
    {
        var nativeView = sender as TextBox;
        nativeView.SelectAll();
    }
#endif
}

HandlerChanged イベントは、クロスプラットフォーム コントロールを実装するネイティブ ビューが作成され、初期化された後に発生します。 そのため、そのイベント ハンドラーは、ネイティブ イベント サブスクリプションを実行する場所になります。 これには、ネイティブ イベントにアクセスできるように、ハンドラーの PlatformView プロパティをネイティブ ビューの型 (基本型) にキャストする必要があります。 この例では、iOS、Mac Catalyst、Windows で、OnEntryHandlerChanged イベントは Entry を実装するネイティブ ビューがフォーカスを得たときに発生するネイティブ ビュー イベントをサブスクライブします。

OnEditingDidBeginOnGotFocus のイベント ハンドラーは、それぞれのプラットフォームで Entry のネイティブ ビューにアクセスし、Entry 内のすべてのテキストを選択します。

HandlerChanging イベントは、既存のハンドラーがクロスプラットフォーム コントロールから削除される前やクロスプラットフォーム コントロールの新しいハンドラーが作成される前に発生します。 そのため、そのイベント ハンドラーでは、ネイティブ イベント サブスクリプションを削除し、他のクリーンアップを実行する必要があります。 このイベントに付随する HandlerChangingEventArgs オブジェクトには OldHandler プロパティと NewHandler プロパティがあり、それぞれ新旧のハンドラーに設定されます。 この例では、OnEntryHandlerChanging イベントによって、iOS、Mac Catalyst、Windows のネイティブ ビュー イベントへのサブスクリプションが削除されます。

部分クラス

条件付きコンパイルを使用するのではなく、部分クラスを使用して、コントロールのカスタマイズ コードをプラットフォーム固有のフォルダーとファイルに整理することもできます。 この方法では、カスタマイズ コードはクロスプラットフォーム部分クラスとプラットフォーム固有の部分クラスに分離されます。 次の例は、クロスプラットフォーム部分クラスを示します。

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryPartialMethodsPage : ContentPage
{
    public CustomizeEntryPartialMethodsPage()
    {
        InitializeComponent();
    }

    partial void ChangedHandler(object sender, EventArgs e);
    partial void ChangingHandler(object sender, HandlerChangingEventArgs e);

    void OnEntryHandlerChanged(object sender, EventArgs e) => ChangedHandler(sender, e);
    void OnEntryHandlerChanging(object sender, HandlerChangingEventArgs e) => ChangingHandler(sender, e);
}

重要

クロスプラットフォーム部分クラスは、プロジェクトのどの Platforms の子フォルダーにも配置しないでください。

この例では、2 つのイベント ハンドラーが ChangedHandlerChangingHandler という名前の部分メソッドを呼び出します。これらのシグネチャはクロスプラットフォーム部分クラスで定義されています。 その後、部分メソッドの実装はプラットフォーム固有の部分クラスで定義されます。これは、ビルド システムが特定のプラットフォーム用にビルドするときにネイティブ コードのビルドのみを試行するように、適切な Platforms の子フォルダーに配置する必要があります。 たとえば、次のコードは、プロジェクトの Platforms>Windows フォルダー内の CustomizeEntryPartialMethodsPage クラスを示します。

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace CustomizeHandlersDemo.Views
{
    public partial class CustomizeEntryPartialMethodsPage : ContentPage
    {
        partial void ChangedHandler(object sender, EventArgs e)
        {
            Entry entry = sender as Entry;
            (entry.Handler.PlatformView as TextBox).GotFocus += OnGotFocus;
        }

        partial void ChangingHandler(object sender, HandlerChangingEventArgs e)
        {
            if (e.OldHandler != null)
            {
                (e.OldHandler.PlatformView as TextBox).GotFocus -= OnGotFocus;
            }
        }

        void OnGotFocus(object sender, RoutedEventArgs e)
        {
            var nativeView = sender as TextBox;
            nativeView.SelectAll();
        }
    }
}

この方法の利点は、条件付きコンパイルが必要ない点と、部分メソッドを各プラットフォームに実装する必要がない点です。 実装がプラットフォームで提供されていない場合、メソッドとそのメソッドに対するすべての呼び出しはコンパイル時に削除されます。 部分メソッドの詳細については、「部分メソッド」をご覧ください。

.NET MAUI プロジェクトの Platforms フォルダーの編成の詳細については、「部分クラスとメソッド」をご覧ください。 プラットフォーム コードを Platforms フォルダーのサブフォルダーに配置する必要がないようにマルチターゲットを構成する方法の詳細については、「マルチターゲットを構成する」をご覧ください。