[WPF] MVVM 軟體架構模式 - 透過 DelegateCommand 綁定方法 (傳遞事件參數)

透過 DelegateCommand 綁定方法 (傳遞事件參數)

到目前為止,我們嘗試過傳遞的參數還是自訂的參數,若是要傳遞事件本身的參數該怎麼辦呢?

問題是 ------ WPF 沒有內建方法傳遞事件參數!!

回顧上一篇用 Interactivity 是用 InvokeCommandAction 來綁定事件與命令 (繼承自 TriggerAction)

但偏偏這個 InvokeCommandAction 沒有處理事件參數而且又用修飾詞 Sealed 不讓繼承、無法擴充!!

因為這裡要自己手工打造一個 EventCommandAction 來完成 InvokeCommandAction 做不到的地方

新增個類別叫做 EventCommandAction,註冊一個相依屬性 (DependencyProperty) 來承接處理事件參數

using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace MVVM
{
    // 新增 EventCommandAction 類別來補強 WPF 沒有內建方法傳遞事件參數的問題
    public class EventCommandAction : TriggerAction<DependencyObject>
    {
        // 用 Command 相依屬性來儲存要綁定的命令
        public ICommand Command
        {
            get => (ICommand)GetValue(CommandProperty);
            set => SetValue(CommandProperty, value);
        }

        // 要註冊之相依性屬性的名稱 (String)
        // 屬性的類型 (Type)
        // 正在註冊相依性屬性的擁有者類型 (Type)
        // 相依性屬性的屬性中繼資料 (PropertyMetadata)
        public static readonly 
            DependencyProperty CommandProperty =
            DependencyProperty.Register(nameof(Command),
                                        typeof(ICommand),
                                        typeof(EventCommandAction),
                                        null);

        // 改寫Invoke(),讓傳入的parameter傳遞給Command.CanExecute()與Command.Execute()
        protected override void Invoke(object parameter)
        {
            if (Command != null && Command.CanExecute(parameter))
                Command.Execute(parameter);
        }
    }
}

做好了以上前置動作後,就可以開始來改造 DelegateCommand,加入可承接泛型(Generic)變數的功能來接事件參數(EventArgs 屬性)

using System;
using System.Diagnostics;
using System.Windows.Input;

namespace MVVM
{
    public class DelegateCommand<T> : ICommand where T : class
    {
        private readonly Func<T, bool> _canExecute;
        private readonly Action<T> _execute;
        bool canExecuteCache;

        // 建構子(多型)
        public DelegateCommand(Action<T> execute, object canExecute) : this(execute, null)
        {
        }
        // 建構子(傳入參數)
        public DelegateCommand(Action<T> execute, Func<T, bool> canExecute)
        {
            // 簡化寫法 if(execute == null) throw new ArgumentNullException("execute");
            this._execute = execute ?? throw new ArgumentNullException("execute");
            this._canExecute = canExecute;
        }

        #region -- ICommand Members --
        // 在XAML使用Interaction繫結這個事件
        public event EventHandler CanExecuteChanged;

        // 下面兩個方法是提供給 View 使用的
        [DebuggerStepThrough]
        public bool CanExecute(object parameter)
        {
            bool temp = _canExecute((T)parameter);

            if (canExecuteCache != temp)
            {
                canExecuteCache = temp;
                if (CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, new EventArgs());
                }
            }
            return canExecuteCache;
        }

        public void Execute(object parameter)
        {
            _execute((T)parameter);
        }
        #endregion
    }
}

在 ViewModel 中加入以下程式碼,這次範例來響應按鍵按下且按下的是 Ctrl(左邊右邊都可以)就冒出訊息 Trigger

public class TestViewModels : ViewModelBase  // ViewModelBase繼承INotifyPropertyChanged介面
{
    public TestViewModels()
    {
        //初始化GenericDelegateCommand
        cmdKeyDown = new DelegateCommand<KeyEventArgs>(cmdkeydown, CanExecute);
    }

    private bool CanExecute(object param)
    {
        return true;  // 假設都執行
    }

    // DelegateCommand
    #region command1
    public ICommand cmdKeyDown { get; set; }
    private void cmdkeydown(KeyEventArgs param)
    {
        if ((param.Key.ToString() == Key.LeftCtrl.ToString()) || param.Key.ToString() == Key.RightCtrl.ToString())
            MessageBox.Show("Trigger!!");
    }
    #endregion command1
}

對應的 View 的 xaml 別忘記加入命名空間 - interactivity 與 EventCommandAction 所在的命名空間

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:root="clr-namespace:MVVM"

xaml 再加入以下部分

<Grid>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName = "KeyDown">
            <root:EventCommandAction Command = "{Binding cmdKeyDown}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Grid>

到此終於大功告成!!