Xamarin.Formsのドキュメント上は見つけれなかったけど、ソースコード的にはTriggerとActionがあったりします。
ただ、この人たちはBindingに対応してないという、ちょっと悲しい感じに仕上がってます。なので、XamarinのBehaviorをベースにBindingに対応したTriggerとActionを作ってみようと思います。
Behaviorの基本クラス
BindingContext
を伝搬するBehaviorBase<T>
クラスを作ります。
using System; using Xamarin.Forms; namespace PrismUnityApp16.Behaviors { publicclass BehaviorBase<T> : Behavior<T> where T : BindableObject { protected T AssociatedObject { get; private set; } protectedoverridevoid OnAttachedTo(T bindable) { base.OnAttachedTo(bindable); this.AssociatedObject = bindable; this.BindingContext = bindable.BindingContext; bindable.BindingContextChanged += this.Bindable_BindingContextChanged; } privatevoid Bindable_BindingContextChanged(object sender, EventArgs e) { this.OnBindingContextChanged(); } protectedoverridevoid OnDetachingFrom(T bindable) { base.OnDetachingFrom(bindable); bindable.BindingContextChanged -= this.Bindable_BindingContextChanged; } protectedoverridevoid OnBindingContextChanged() { base.OnBindingContextChanged(); this.BindingContext = this.AssociatedObject.BindingContext; } } }
Triggerの基本クラスを作る
次にTriggerの基本クラスを作ります。BehaviorBase<T>
を継承してIAction
インターフェースを抱え込む感じで作ります。ContentProperty
にActions
を指定していい感じにXAMLで書けるようにもしておきましょう。
あと、IAction
にBindingContext
を伝搬させるのも忘れないでやっておきます。
using System.Collections.Generic; using System.Linq; using Xamarin.Forms; namespace PrismUnityApp16.Behaviors { [ContentProperty("Actions")] publicclass TriggerBehaviorBase<T> : BehaviorBase<T> where T : BindableObject { public ICollection<IAction> Actions { get; } = new List<IAction>(); protectedvoid InvokeActions(object parameter) { foreach (var action inthis.Actions.ToArray()) { action.Execute(parameter); } } protectedoverridevoid OnAttachedTo(T bindable) { base.OnAttachedTo(bindable); foreach (var action inthis.Actions.ToArray()) { action.BindingContext = this.BindingContext; } } protectedoverridevoid OnDetachingFrom(T bindable) { base.OnDetachingFrom(bindable); foreach (var action inthis.Actions.ToArray()) { action.BindingContext = null; } } protectedoverridevoid OnBindingContextChanged() { base.OnBindingContextChanged(); foreach (var action inthis.Actions.ToArray()) { action.BindingContext = this.BindingContext; } } } }
IActoin
はこんな感じのシンプルなインターフェースです。
namespace PrismUnityApp16.Behaviors { publicinterface IAction { object BindingContext { get; set; } void Execute(object parameter); } }
使ってみよう
イベントをもとにActionを実行するEventTriggerBehavior
とCommandを実行するInvokeCommandAction
を作ってみようと思います。
EventTriggerBehavior
さくっとリフレクションを使ってイベントを拾ってきて登録します。
using System; using System.Reflection; using Xamarin.Forms; namespace PrismUnityApp16.Behaviors { publicclass EventTriggerBehavior : TriggerBehaviorBase<View> { publicstaticreadonly BindableProperty EventNameProperty = BindableProperty .Create(nameof(EventName), typeof(string), typeof(EventTriggerBehavior)); private Delegate EventHandler { get; set; } private EventInfo EventInfo { get; set; } publicstring EventName { get { return (string)this.GetValue(EventNameProperty); } set { this.SetValue(EventNameProperty, value); } } protectedoverridevoid OnAttachedTo(View bindable) { base.OnAttachedTo(bindable); if (string.IsNullOrEmpty(this.EventName)) { return; } this.EventInfo = this.AssociatedObject.GetType().GetRuntimeEvent(this.EventName); if (this.EventInfo == null) { thrownew InvalidOperationException($"{this.EventName} is not found."); } var methodInfo = typeof(EventTriggerBehavior).GetTypeInfo().GetDeclaredMethod(nameof(OnEvent)); this.EventHandler = methodInfo.CreateDelegate(this.EventInfo.EventHandlerType, this); this.EventInfo.AddEventHandler(bindable, this.EventHandler); } privatevoid OnEvent(object sender, object args) { this.InvokeActions(args); } protectedoverridevoid OnDetachingFrom(View bindable) { base.OnDetachingFrom(bindable); this.EventInfo.RemoveEventHandler(bindable, this.EventHandler); } } }
InvokeCommandAction
BindableObject
から継承してIAction
を実装します。Commandを実行する感じに書きましょう。
using System.Windows.Input; using Xamarin.Forms; namespace PrismUnityApp16.Behaviors { publicclass InvokeCommandAction : BindableObject, IAction { publicstaticreadonly BindableProperty CommandProperty = BindableProperty .Create(nameof(Command), typeof(ICommand), typeof(EventTriggerBehavior)); publicstaticreadonly BindableProperty CommandParameterProperty = BindableProperty .Create(nameof(CommandParameter), typeof(object), typeof(EventTriggerBehavior)); publicstaticreadonly BindableProperty ConverterProperty = BindableProperty .Create(nameof(Converter), typeof(IValueConverter), typeof(EventTriggerBehavior)); public ICommand Command { get { return (ICommand)this.GetValue(CommandProperty); } set { this.SetValue(CommandProperty, value); } } publicobject CommandParameter { get { returnthis.GetValue(CommandParameterProperty); } set { this.SetValue(CommandParameterProperty, value); } } public IValueConverter Converter { get { return (IValueConverter)this.GetValue(ConverterProperty); } set { this.SetValue(ConverterProperty, value); } } publicvoid Execute(object parameter) { var p = this.CommandParameter; if (p == null) { p = this.Converter?.Convert(parameter, typeof(object), null, null); } if (this.Command?.CanExecute(p) ?? false) { this.Command.Execute(p); } } } }
使ってみよう
使い方は簡単。例えばButton
のClicked
イベントと紐づける場合はこんな感じ。
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" xmlns:controls="clr-namespace:PrismUnityApp16.Controls" xmlns:behaviors="clr-namespace:PrismUnityApp16.Behaviors" prism:ViewModelLocator.AutowireViewModel="True" x:Class="PrismUnityApp16.Views.MainPage" Title="MainPage"> <StackLayout HorizontalOptions="Center" VerticalOptions="Center"> <Button Text="OK"> <Button.Behaviors> <behaviors:EventTriggerBehavior EventName="Clicked"> <behaviors:InvokeCommandAction Command="{Binding HelloCommand}" /> </behaviors:EventTriggerBehavior> </Button.Behaviors> </Button> </StackLayout> </ContentPage>
ViewModel側はこんな感じです。
using Prism.Commands; using Prism.Mvvm; using Prism.Navigation; using System.Diagnostics; namespace PrismUnityApp16.ViewModels { publicclass MainPageViewModel : BindableBase, INavigationAware { public DelegateCommand HelloCommand { get; } public MainPageViewModel() { this.HelloCommand = new DelegateCommand(() => Debug.WriteLine("Clicked")); } publicvoid OnNavigatedFrom(NavigationParameters parameters) { } publicvoid OnNavigatedTo(NavigationParameters parameters) { } } }
実行してボタンを押すとデバッグウィンドウの出力にClickedと表示されます。
まとめ
デフォで用意しといてくれ。