MAUIはまだプレビュー版で、ほとんど情報がなく、MAUIのコントロールをカスタマイズする場合はどうするのかなと思い調べてみました。
カスタマイズ方法
コントロールをカスタマイズする方法としては3つあるようです。
- カスタムレンダラー
- 独自ハンドラー
- マッパーのカスタマイズ
カスタムレンダラー
Xamarin.Formsと同じように、カスタムレンダラーでコントロールをカスタマイズできます。MAUIのプロジェクトは、各プラットフォームのコードはフォルダで分かれており、iOSなら、Platforms/iOS
、Androidなら、Platforms/Android
にコードを追加します。
iOS
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Compatibility.Platform.iOS;
using Microsoft.Maui.Controls.Platform;
using UIKit;
namespace MauiApp1.Platforms.iOS
{
public class MyEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BackgroundColor = UIColor.Red;
}
}
}
}
Android
using Android.Content;
using Android.Graphics;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
namespace MauiApp1.Platforms.Android
{
internal class MyEntryRenderer : EntryRenderer
{
public MyEntryRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.SetBackgroundColor(Color.Red);
}
}
}
}
Xamarin.Formsで、カスタムレンダラーを使ったことがある人なら、「あれっ?」と思うかもしれませんが、上記のコードではExportRenderer
属性を記述していません。MAUIでは、カスタムレンダラーの登録には、ExportRenderer
属性を使わないようです。(記述しても登録できません。)
MAUIでは、MauiApp
インスタンス作成時に登録を行います。MauiApp
はMauiAppBuilder
を使って作成しますが、その際にConfigureMauiHandlers
メソッドを呼び出し、そのコールバックの引数であるIMauiHandlersCollection
のAddCompatibilityRenderer
メソッドで登録を行います。各プラットフォームに対するコードの切り替えは、条件付きコンパイルで行います。
using Microsoft.Maui;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Controls.Compatibility;
using Microsoft.Maui.Controls.Hosting;
namespace MauiApp1
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
})
.ConfigureMauiHandlers(handler =>
{
#if __IOS__
handler.AddCompatibilityRenderer<MyEntry, Platforms.iOS.MyEntryRenderer>();
#elif __ANDROID__
handler.AddCompatibilityRenderer<MyEntry, Platforms.Android.MyEntryRenderer>();
#endif
});
return builder.Build();
}
}
}
ちなみに、MyEntry
は、Entry
クラスを継承したクラスです。
using Microsoft.Maui.Controls;
namespace MauiApp1
{
public class MyEntry : Entry
{
}
}
独自ハンドラー
Xamarin.Formsでは、ネイティブのコントロールを操作するクラスはレンダラーでしたが、MAUIでは、ハンドラーという名前のクラスで行われます。Entry
コントロールなら、EntryHandler
クラスになります。このハンドラーを継承した独自のハンドラーを作成することでもカスタマイズができそうです。
EntryHandler
を継承した独自ハンドラーの例が以下になります。
iOS
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform.iOS;
using UIKit;
namespace MauiApp1.Platforms.iOS
{
public class MyEntryHandler : EntryHandler
{
protected override MauiTextField CreateNativeView()
{
var nativeView = base.CreateNativeView();
nativeView.BackgroundColor = UIColor.Red;
return nativeView;
}
protected override void ConnectHandler(MauiTextField nativeView)
{
base.ConnectHandler(nativeView);
}
protected override void DisconnectHandler(MauiTextField nativeView)
{
base.DisconnectHandler(nativeView);
}
}
}
Android
using Android.Graphics;
using AndroidX.AppCompat.Widget;
using Microsoft.Maui.Handlers;
namespace MauiApp1.Platforms.Android
{
public class MyEntryHandler : EntryHandler
{
protected override AppCompatEditText CreateNativeView()
{
var nativeView = base.CreateNativeView();
nativeView.SetBackgroundColor(Color.Red);
return nativeView;
}
protected override void ConnectHandler(AppCompatEditText nativeView)
{
base.ConnectHandler(nativeView);
}
protected override void DisconnectHandler(AppCompatEditText nativeView)
{
base.DisconnectHandler(nativeView);
}
}
}
この例では、CreateNativeView
をオーバーライドして、そこでプロパティの設定を行っています。あと、カスタマイズできそうな所としては、ConnectHandler
がありますが、ここは名前が表すようにイベントの登録を行うためのメソッドのようなので、イベントを登録するならここに記述します。ちなみに、イベントの登録の解除は、DisconnectHandler
で行うことになります。
ハンドラーの登録は、カスタムレンダラーと同じようにConfigureMauiHandlers
を使います。ハンドラーでは、AddHandler
を呼び出して登録を行います。
using Microsoft.Maui;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Controls.Hosting;
namespace MauiApp1
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
})
.ConfigureMauiHandlers(handler =>
{
#if __IOS__
handler.AddHandler<MyEntry, Platforms.iOS.MyEntryHandler>();
#elif __ANDROID__
handler.AddHandler<MyEntry, Platforms.Android.MyEntryHandler>();
#endif
});
return builder.Build();
}
}
}
マッパーのカスタマイズ
ハンドラーは、コントロールのプロパティ値変更に対する、実際のネイティブのコントロールで行う処理を、マッパーというクラス(IPropertyMapper
を実装したクラス)で保持しています。このマッパーには独自の処理を追加することが可能となっています。追加するには、マッパーのAppendToMapping
を呼び出します。
以下が、App
クラスで追加を行っている例です。
using Microsoft.Maui;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Handlers;
using Application = Microsoft.Maui.Controls.Application;
namespace MauiApp1
{
public partial class App : Application
{
public App()
{
InitializeComponent();
EntryHandler.EntryMapper.AppendToMapping(nameof(IView.Background), (handler, view) =>
{
if (view is MyEntry)
{
#if __IOS__
handler.NativeView.BackgroundColor = Colors.Red.ToNative();
#elif __ANDROID__
handler.NativeView.SetBackgroundColor(Colors.Red.ToNative());
#endif
}
});
MainPage = new MainPage();
}
}
}
ハンドラーからは、ネイティブのコントロール(NativeView
)にアクセスできるので、そこからカスタマイズすることができます。
AppendToMapping
の第一引数は、マッピングのキーで、Background
を設定しています。このキーの名前の付け方には少し注意が必要です。もし、コントロールのプロパティに存在しない名前を付ければ、コントロール初期化時に追加した処理が呼び出されだけです。しかし、プロパティに存在する名前の場合、このキーで既に処理が登録されているので、既存の処理との兼ね合いを考えなければいけません。
今回キーに設定したBackground
は、プロパティに存在する名前なので、既に処理が登録されています。この既存の処理が追加によってどうなるかというと、AppendToMapping
で追加した場合は、既存処理の後に追加した処理が実行されるようになります。なので、この場合、xamalなどでBackground
プロパティを設定しても、追加した処理で背景色を書き換えてしまうといった動作になります。また、後からコントロールのプロパティの値を変更した場合でも、この追加処理が実行されます。
ちなみに、他にも追加する方法があり、PrependToMapping
を使えば、既存処理の前に追加処理を行う動作になります。また、ModifyMapping
を使えば、既存処理を引数で受けることができます。
おわりに
今回、MAUIのコントロールのカスタマイズ方法を調べたことで、MAUIの構成を少し理解することができました。MAUIが今後どうなっていくのかはまだわかりませんが、動向は追っていきたいと思います。