かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

DelegateCommandっていらないかも・・・?

ViewにCommandを持つのかViewModelにCommandを持つのかの違いだけど、個人的な趣味では、ViewにCommandを持つ、この方法が気に入ってきた。今日ひらめいた。

下のようなFrameworkElementを継承したICommandインターフェースを実装したクラスを定義します。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;

namespace CommandHolderTest
{
    public class CommandHolder : FrameworkElement, ICommand
    {
        #region ExecuteActionプロパティ
        public Action ExecuteAction
        {
            get { return (Action)GetValue(ExecuteActionProperty); }
            set { SetValue(ExecuteActionProperty, value); }
        }

        public static readonly DependencyProperty ExecuteActionProperty =
            DependencyProperty.Register(
                "ExecuteAction", 
                typeof(Action), 
                typeof(CommandHolder), 
                new PropertyMetadata(null));
        #endregion

        #region ExecuteActionsプロパティ
        public IEnumerable<Action> ExecuteActions
        {
            get { return (IEnumerable<Action>)GetValue(ExecuteActionsProperty); }
            set { SetValue(ExecuteActionsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ExecuteActions.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ExecuteActionsProperty =
            DependencyProperty.Register(
                "ExecuteActions", 
                typeof(IEnumerable<Action>), 
                typeof(CommandHolder), 
                new PropertyMetadata(null));
        #endregion
        
        public void Execute()
        {
            // 何か設定されてたら実行する
            if (ExecuteAction != null)
            {
                ExecuteAction();
            }
            if (ExecuteActions != null)
            {
                foreach (var action in ExecuteActions)
                {
                    action();
                }
            }
        }

        #region ICommand Members

        bool ICommand.CanExecute(object parameter)
        {
            // とりあえず常に実行可能
            return true;
        }

        private EventHandler _canExecuteChanged;
        event EventHandler ICommand.CanExecuteChanged
        {
            add { _canExecuteChanged += value; }
            remove { _canExecuteChanged -= value; }
        }

        void ICommand.Execute(object parameter)
        {
            Execute();
        }

        #endregion
    }
}

Commandとして実行する処理をAction型のプロパティで受け取るシンプルなクラスです。こいつをViewに置くようにすると、ViewModelではCommandじゃなくてAction型を公開するだけでOKになります。
多少コードがすっきりします。

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;

namespace CommandHolderTest
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion

        private string _message;
        public string Message
        {
            get { return _message; }
            set
            {
                _message = value;
                OnPropertyChanged("Message");
            }
        }

        // Actionを公開する
        public Action GreetAction
        {
            get
            {
                return GreetExecute;
            }
        }
        public void GreetExecute()
        {
            Message = "こんにちは@" + DateTime.Now;
        }
    }
}

XAMLでは、CommandHolderを置いて、ExecuteActionプロパティとGreetActionプロパティをバインドで結び付けます。

<UserControl 
    x:Class="CommandHolderTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:CommandHolderTest"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400" xmlns:my="clr-namespace:CommandHolderTest">
    <UserControl.DataContext>
        <local:MainPageViewModel />
    </UserControl.DataContext>
    <Grid x:Name="LayoutRoot" Background="White">
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="button1" VerticalAlignment="Top" Width="75" Command="{Binding ElementName=commandHolder1}" />
        <my:CommandHolder x:Name="commandHolder1" ExecuteAction="{Binding Path=GreetAction}" />
        <TextBlock HorizontalAlignment="Left" Margin="93,16,0,0" Name="textBlock1" Text="{Binding Path=Message}" VerticalAlignment="Top" />
    </Grid>
</UserControl>

ButtonのCommandプロパティには、CommandHolderをバインドします。

この方法のもう1つの利点は、Loadedイベントのように、CommandがサポートされてないCommandを発行してくれないイベントでは、イベントハンドラでCommandHolderのExecuteメソッドを呼び出してやればOKという点です。
ViewModelを意識せずにアクションを実行できる。

commandHolder1.Execute();

個人的には、DelegateCommandより好みなのだけど、どうだろう?