Quantcast
Channel: かずきのBlog@hatena
Viewing all articles
Browse latest Browse all 1387

Xamarin.FormsでBehaviorのTriggerとActionを作る

$
0
0

Xamarin.Formsのドキュメント上は見つけれなかったけど、ソースコード的にはTriggerとActionがあったりします。

github.com

ただ、この人たちは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インターフェースを抱え込む感じで作ります。ContentPropertyActionsを指定していい感じにXAMLで書けるようにもしておきましょう。

あと、IActionBindingContextを伝搬させるのも忘れないでやっておきます。

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

使ってみよう

使い方は簡単。例えばButtonClickedイベントと紐づける場合はこんな感じ。

<?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と表示されます。

まとめ

デフォで用意しといてくれ。


Viewing all articles
Browse latest Browse all 1387

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>