かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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より好みなのだけど、どうだろう?