[WPF] MVVM 軟體架構模式 - 透過 EventCommand 再進化傳遞事件參數 (註冊 Command/CommandParameter/InvokeParameter 三個依賴屬性)

MVVM 軟體架構模式 - 透過 EventCommand 再進化傳遞事件參數 (註冊 Command/CommandParameter/InvokeParameter 三個依賴屬性)

這裡把前一篇的 EventCommandAction 拓展改寫,這就叫它 EventCommand 吧~少打幾個字~XD

先看改造過的 EventCommand 類別

public class EventCommand : TriggerAction<DependencyObject>
{
    private string commandName;

    public ICommand Command
    {
        get
        {
            return (ICommand)this.GetValue(CommandProperty);
        }
        set
        {
            this.SetValue(CommandProperty, value);
        }
    }
    public string CommandName
    {
        get
        {
            return this.commandName;
        }
        set
        {
            if (this.CommandName != value)
            {
                this.commandName = value;
            }
        }
    }
    public object CommandParameter
    {
        get
        {
            return this.GetValue(CommandParameterProperty);
        }

        set
        {
            this.SetValue(CommandParameterProperty, value);
        }
    }
    public object InvokeParameter
    {
        get
        {
            return this.GetValue(InvokeParameterProperty);
        }
        set
        {
            this.SetValue(InvokeParameterProperty, value);
        }
    }

    public object Sender { get; set; }

    public static readonly 
        DependencyProperty CommandProperty =
        DependencyProperty.Register(nameof(Command),
                                    typeof(ICommand),
                                    typeof(EventCommand),
                                    null);
    public static readonly 
        DependencyProperty CommandParameterProperty =
        DependencyProperty.Register(nameof(CommandParameter),
                                    typeof(object),
                                    typeof(EventCommand),
                                    null);
    public static readonly 
        DependencyProperty InvokeParameterProperty =
        DependencyProperty.Register(nameof(InvokeParameter),
                                    typeof(object),
                                    typeof(EventCommand),
                                    null);

    protected override void Invoke(object parameter)
    {
        this.InvokeParameter = parameter;
        if (this.AssociatedObject != null)
        {
            ICommand command = this.ResolveCommand();
            if ((command != null) && command.CanExecute(this.CommandParameter))
            {
                command.Execute(this.CommandParameter);
            }
        }
    }

    private ICommand ResolveCommand()
    {
        ICommand command = null;
        if (this.Command != null)
        {
            return this.Command;
        }
        var frameworkElement = this.AssociatedObject as FrameworkElement;
        if (frameworkElement != null)
        {
            object dataContext = frameworkElement.DataContext;
            if (dataContext != null)
            {
                PropertyInfo commandPropertyInfo = dataContext
                    .GetType()
                    .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                    .FirstOrDefault(
                        p =>
                        typeof(ICommand).IsAssignableFrom(p.PropertyType) &&
                        string.Equals(p.Name, this.CommandName, StringComparison.Ordinal)
                    );

                if (commandPropertyInfo != null)
                {
                    command = (ICommand)commandPropertyInfo.GetValue(dataContext, null);
                }
            }
        }
        return command;
    }
}

最大改動是多了兩個依賴屬性 CommandParameter 與 InvokeParameter 來處理事件參數

前一篇例子是只能參數留空 null(此時為事件自身參數)或是自訂綁定值,而這不同的是建立一個 ResolveCommand 方法來解析參數來源,可以指定傳遞 Visual Tree 上元件屬性或參數

這有兩個前臺 XAML 使用範例,使用前依然別忘記引入命名空間

xmlns:root="clr-namespace:MVVM"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

範例1 - 在 Grid 上放下物件時,把事件參數傳遞給觸發命令的方法 (DragEventArgs)

<Grid AllowDrop = "True">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Drop">
            <root:EventCommand
                  CommandName="DropCommand"
                  CommandParameter="{Binding RelativeSource={RelativeSource Self},
                                             Path=InvokeParameter}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Grid >

範例2 - 在 ComboBox 上選擇事件時,把事件參數傳遞給觸發命令的方法 (SelectionChangedEventArgs)

<ComboBox>
    <ComboBoxItem Content="Foo option 1"/>
    <ComboBoxItem Content="Foo option 2"/>
    <ComboBoxItem Content="Foo option 3"/>C
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <root:EventCommand
                  Command="{Binding SubmitFormCommand}"
                  CommandParameter="{Binding RelativeSource={RelativeSource Self},
                                             Path=InvokeParameter}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>               
</ComboBox>

雖然這兩個範例都是引用自身事件參數,但有一個不同是範例1 是使用 CommandName 指定處理方法,範例2 使用 Command 指定,要注意使用的語法差異外還不要同時混用

另外要怎麼使用傳遞進去的參數呢?請參考『[WPF] MVVM 軟體架構模式 - 透過 DelegateCommand 綁定方法 (傳遞事件參數)』囉~不負責任結束~XD