VB のたまご

作成日: 2016/10/25, 更新日: 2016/10/25


LivetCallMethodActionでイベント引数を受け取りたい

  •  WPF 開発では、尾上さんの Livet を使っています。イベント処理では、LivetCallMethodAction 経由で、直接メソッド呼び出しすることで、 コマンド定義した時よりもコードがシンプルになります(簡単に言うと、コマンド経由でメソッドを呼び出しているので、メソッドが呼べればコマンドは不要。ただし、そういう仕様でも良い場合だけ)。 で、ListBox 内で選択した時のイベント SelectionChanged イベントを使いたい場合、選択中の値もほしくなるんですよね。

  • <ListBox ItemsSource="{Binding Items}" SelectedValue="{Binding selectedValue}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
                <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="ListBox_SelectionChanged" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </ListBox>
    

  •  こんな感じで、選択中の値は、別途 SelectedValue にバインドしたプロパティを使っていました。 でもやっぱり、メソッド側で、引数として受け取った方が回りくどくないというか、楽というか、とにかくできると良いのに、とか思っていました。 で、他の MVVM フレームワークの MvvmLight では、EventToCommand アクションの引数で、PassEventArgsToCommand プロパティに True を設定すると、イベント引数を受け取れるそうです。 いいなぁ。ほしいなぁ。

  • <ListBox ItemsSource="{Binding Items}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
                <mi:EventToCommand Command="{Binding SelectionChangedCommand}" PassEventArgsToCommand="True" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </ListBox>
    

  •  こういう風に使うみたいです。いいなぁ。ほしいなぁ。と。でも、Livet を使いたいし。 こういうパターンの場合、先人様がどこかで実現させて公開していることが多いので、探したところ見つけました。 最近どういう検索経路でも最終的にたどり着く Stackoverflow です。

EventToCommandBehavior

  •  Stackoverflow に乗っているサンプルそのままです。これを利用して、イベントとコマンドとイベント引数を紐づけます。

  • 別記事のプログラムでも使っていて、View 層に置いています。
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Interactivity;
    using System.Reflection;
    
    namespace EventViewer.Views
    {
    
        // stackoverflow
        // MVVM Passing EventArgs As Command Parameter
        // http://stackoverflow.com/questions/6205472/mvvm-passing-eventargs-as-command-parameter
    
        // LivetCallMethodAction で、イベント引数を受け取りたい。の実装。
        // →Behavior 作成。LivetCallMethodAction は未使用になった。。。
    
    
        public class EventToCommandBehavior : Behavior<FrameworkElement>
        {
            private Delegate _handler;
            private EventInfo _oldEvent;
    
            // Event
            public string Event { get { return (string)GetValue(EventProperty); } set { SetValue(EventProperty, value); } }
            public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior), new PropertyMetadata(null, OnEventChanged));
    
            // Command
            public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } }
            public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior), new PropertyMetadata(null));
    
            // PassArguments (default: false)
            public bool PassArguments { get { return (bool)GetValue(PassArgumentsProperty); } set { SetValue(PassArgumentsProperty, value); } }
            public static readonly DependencyProperty PassArgumentsProperty = DependencyProperty.Register("PassArguments", typeof(bool), typeof(EventToCommandBehavior), new PropertyMetadata(false));
    
    
            private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var beh = (EventToCommandBehavior)d;
    
                if (beh.AssociatedObject != null) // is not yet attached at initial load
                    beh.AttachHandler((string)e.NewValue);
            }
    
            protected override void OnAttached()
            {
                AttachHandler(this.Event); // initial set
            }
    
            /// <summary>
            /// Attaches the handler to the event
            /// </summary>
            private void AttachHandler(string eventName)
            {
                // detach old event
                if (_oldEvent != null)
                    _oldEvent.RemoveEventHandler(this.AssociatedObject, _handler);
    
                // attach new event
                if (!string.IsNullOrEmpty(eventName))
                {
                    EventInfo ei = this.AssociatedObject.GetType().GetEvent(eventName);
                    if (ei != null)
                    {
                        MethodInfo mi = this.GetType().GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic);
                        _handler = Delegate.CreateDelegate(ei.EventHandlerType, this, mi);
                        ei.AddEventHandler(this.AssociatedObject, _handler);
                        _oldEvent = ei; // store to detach in case the Event property changes
                    }
                    else
                        throw new ArgumentException(string.Format("The event '{0}' was not found on type '{1}'", eventName, this.AssociatedObject.GetType().Name));
                }
            }
    
            /// <summary>
            /// Executes the Command
            /// </summary>
            private void ExecuteCommand(object sender, EventArgs e)
            {
                object parameter = this.PassArguments ? e : null;
                if (this.Command != null)
                {
                    if (this.Command.CanExecute(parameter))
                        this.Command.Execute(parameter);
                }
            }
        }
    }
    
    
    使う側(View)
    
    <ListBox ItemsSource="{Binding Items}">
        <i:Interaction.Behaviors>
            <v:EventToCommandBehavior Event="SelectionChanged" Command="{Binding SelectionChangedCommand}" PassArguments="True" />
        </i:Interaction.Behaviors>
    </ListBox>
    
    
    ViewModel
    using System.Windows.Controls;
    using System.Collections.ObjectModel;
    
    
            private ListenerCommand<SelectionChangedEventArgs> _SelectionChangedCommand;
    
            public ListenerCommand<SelectionChangedEventArgs> SelectionChangedCommand
            {
                get
                {
                    if (_SelectionChangedCommand == null)
                        _SelectionChangedCommand = new ListenerCommand<SelectionChangedEventArgs>(SelectionChanged);
    
                    return _SelectionChangedCommand;
                }
            }
    
            private void SelectionChanged(SelectionChangedEventArgs e)
            {
                // TODO
                var item = e.AddedItems[0] as string; // ObservableCollection<string> の場合
                Console.WriteLine(item);
            }
    
    

  •  こんな感じで使います。

おわりに

  •  作っておいてなんですが、イベント引数の中には View 関連にアクセスできてしまうプロパティが無きにしもあらずなので、 本当は、触っちゃいけないのかなとか思ったりしています。これを使う時は、自分ルールで、そういうプロパティにはアクセスしないと決めています。 ただ、心が折れそうなときはアクセスしてしまいそうになるかもしれないので、業務的な場合は、定義しない(仕組み上そういうことができないままの)方がいいかもしれません。 最後までこの記事を読んでいただき、ありがとうございました。