1. 程式人生 > 實用技巧 >WPF 手動實現 INotifyPropertyChanged 和 ICommand

WPF 手動實現 INotifyPropertyChanged 和 ICommand

檢視 INotifyPropertyChanged 介面原始碼

namespace System.ComponentModel
{
    //
    // 摘要:
    //     Notifies clients that a property value has changed.
    public interface INotifyPropertyChanged
    {
        //
        // 摘要:
        //     Occurs when a property value changes.
        event PropertyChangedEventHandler PropertyChanged;
    }
}

INotifyPropertyChanged介面定義了一個屬性改變處理事件,通知客戶端這個屬性值已經發生改變。

定義NotifyObject實現INotifyPropertyChanged

    public class NotifyObject : INotifyPropertyChanged
    {
        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Checks if a property already matches a desired value. Sets the property and
        /// notifies listeners only when necessary.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="storage"></param>
        /// <param name="value"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        protected virtual bool SetProperty<T>(ref T storage,T value,[CallerMemberName] string propertyName=null)
        {
            if (EqualityComparer<T>.Default.Equals(storage, value)) return false;

            storage = value;

            RaisePropertyChanged(propertyName);

            return true;
        }

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName"></param>
        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

通過SetProperty<T>泛型方法 可以接收任意型別的屬性,然後判斷屬性值是否發生變化,如果變化就觸發PropertyChanged事件,通知UI本屬性值已經發生改變。

檢視ICommand原始碼

namespace System.Windows.Input
{
    //
    // 摘要:
    //     Defines a command.
    public interface ICommand
    {
        //
        // 摘要:
        //     Occurs when changes occur that affect whether or not the command should execute.
        event EventHandler CanExecuteChanged;

        //
        // 摘要:
        //     Defines the method that determines whether the command can execute in its current
        //     state.
        //
        // 引數:
        //   parameter:
        //     Data used by the command. If the command does not require data to be passed,
        //     this object can be set to null.
        //
        // 返回結果:
        //     true if this command can be executed; otherwise, false.
        bool CanExecute(object parameter);
        //
        // 摘要:
        //     Defines the method to be called when the command is invoked.
        //
        // 引數:
        //   parameter:
        //     Data used by the command. If the command does not require data to be passed,
        //     this object can be set to null.
        void Execute(object parameter);
    }
}

ICommand介面定義了一個普通的事件,和命令執行方法Execute()、命令是否可以執行方法CanExecute()

定義DelegateCommand實現 ICommand

public class DelegateCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;
        private readonly Action _executeMethod;
        private readonly Func<bool> _canExecuteMethod;

        /// <summary>
        /// Creates a new instance of DelegateCommand with the Action to invoke on execution.
        /// </summary>
        /// <param name="executeMethod"></param>
        public DelegateCommand(Action executeMethod)
            : this(executeMethod, () => true)
        {

        }

        /// <summary>
        /// Creates a new instance of DelegateCommand with the Action to invoke on execution
        /// and a Func to query for determining if the command can execute.
        /// </summary>
        /// <param name="executeMethod"></param>
        /// <param name="canExecuteMethod"></param>
        public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
        {
            if(executeMethod==null||canExecuteMethod==null)
                throw  new ArgumentNullException(nameof(executeMethod));
            _executeMethod = executeMethod;
            _canExecuteMethod = canExecuteMethod;
        }

        /// <summary>
        /// Executes the command.
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        {
            _executeMethod();
        }

        /// <summary>
        /// Determines if the command can be executed.
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(object parameter)
        {
            return _canExecuteMethod();
        }
    }

通過DelegateCommand建構函式載入兩個委託(Action _executeMethod ,Func<bool> _canExecuteMethod),如果存在可以正常實現命令,這裡ICommand的實現也是極簡模式,後面可以繼續擴充套件。

在ViewModel中使用NotifyObject和DelegateCommand

    public class MainWindowViewModel:NotifyObject
    {
        /// <summary>
        /// 輸入1
        /// </summary>
        private double _input1;
        public double Input1
        {
            get => _input1;
            set => SetProperty(ref _input1,value);
        }

        /// <summary>
        /// 輸入2
        /// </summary>
        private double _input2;
        public double Input2
        {
            get => _input2;
            set => SetProperty(ref _input2, value);
        }

        /// <summary>
        /// 結果
        /// </summary>
        private double _result;
        public double Result
        {
            get => _result;
            set => SetProperty(ref _result, value);
        }

        /// <summary>
        /// 加法命令
        /// </summary>
        public DelegateCommand _addCommand;
        public DelegateCommand AddCommand => _addCommand ??= new DelegateCommand(Add);
        private  void Add()
        {
            Result = Input1+Input2;
        }
    }

在ViewModel 定義Input1 Input2 Result 跟View中的控制元件進行資料繫結,定義AddCommand跟View中事件擁有者繫結(命令繫結),當UI介面點選加法按鈕,事件處理器就會響應這個命令執行Add()方法,完成運算。

總結:

通過上述介面實現,簡單可以實現資料繫結和命令繫結,這個思路主要借鑑Prism框架,也是一個學習過程記錄。