RoutedComand\RelayCommand\DelegateCommand 的實現比較


首先介紹CommandManager類,它有一個重要的靜態事件:
RequerySuggested: Occurs when the CommandManager detects conditions that might change the ability of a command to execute.
當CommandManager認為當前的某個改變或動作有可能會改變command的能否執行的狀態時,就觸發該事件。例如焦點改變,所以這個事件會多次被觸發。

另外有一個重要的靜態方法:
InvalidateRequerySuggested(): Forces the CommandManager to raise the RequerySuggested event.
手動的調用這個方法強制的觸發RequerySuggested事件。

如下是測試的代碼,xaml:

View Code
<Window x:Class="RelayAndDelegateCommand.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" xmlns:local="clr-namespace:RelayAndDelegateCommand">
    <StackPanel>
        <Button Content="Test Routed Command" Command="{x:Static local:MainWindow.TestRoutedCommand}"  />
        <Button Margin="0,8,0,0" Content="Test Relay Command" Command="{Binding TestRelayCommand}" />
        <Button Margin="0,8,0,0" Content="Test Delegate Command" Command="{Binding TestDelegateCommand}"  />
        <Button  Margin="0,20,0,0" Content="Click me" HorizontalAlignment="Center" Name="button1" VerticalAlignment="Top" Width="80" Click="button1_Click" />
    </StackPanel>
</Window>

code-main window:

View Code
public partial class MainWindow : Window
    {
        private bool _cansave = false;

        public MainWindow()
        {
            InitializeComponent();
            this.CommandBindings.Add(new CommandBinding(TestRoutedCommand, new ExecutedRoutedEventHandler(OnTestRoutedCommandExecuted), new CanExecuteRoutedEventHandler(OnTestRoutedCommandCanExecute)));
            this.DataContext = this;
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            _cansave = true;
            // DelegateCommand needs manually raise can execute changed.
            (TestDelegateCommand as DelegateCommand).RaiseCanExecuteChanged();
        }

        #region 1. TestRoutedCommand

        public static readonly RoutedCommand TestRoutedCommand = new RoutedCommand();

        public void OnTestRoutedCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Hello world from RoutedCommand");
        }

        public void OnTestRoutedCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = _cansave;
            Debug.WriteLine("CanExecute from RoutedCommand");
        }

        #endregion

        #region 2. TestRelayCommand

        private ICommand _testRelayCommand;
        public ICommand TestRelayCommand
        {
            get
            {
                if (_testRelayCommand == null)
                {
                    _testRelayCommand = new RelayCommand(new Action<object>(OnTestRelayCommandExecuted), new Predicate<object>(OnTestRelayCommandCanExecute));
                }
                return _testRelayCommand;
            }
        }

        public void OnTestRelayCommandExecuted(object para)
        {
            MessageBox.Show("Hello world from RelayCommand");
        }

        public bool OnTestRelayCommandCanExecute(object para)
        {
            Debug.WriteLine("CanExecute from RelayCommand");
            return _cansave;
        }

        #endregion

        #region 3. TestDelegateCommand

        private ICommand _testDelegateCommand;
        public ICommand TestDelegateCommand
        {
            get
            {
                if (_testDelegateCommand == null)
                {
                    _testDelegateCommand = new DelegateCommand(new Action<object>(OnTestDelegateCommandExecuted), new Predicate<object>(OnTestDelegateCommandCanExecute));
                }
                return _testDelegateCommand;
            }
        }

        public void OnTestDelegateCommandExecuted(object para)
        {
            MessageBox.Show("Hello world from DelegateCommand");
        }

        public bool OnTestDelegateCommandCanExecute(object para)
        {
            Debug.WriteLine("CanExecute from DelegateCommand");
            return _cansave;
        }

        #endregion
    }

code-一個簡單的RelayCommand類:

View Code
public class RelayCommand : ICommand
    {
        #region Fields

        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        #endregion

        #region Constructors

        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }

        #endregion

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }

        // When command manager thinks the canexecute might change(e.g. focus changed), it raises RequerySuggested event.
        // The CanExecuteChanged is automatically registered by command binding, the execution logic of updating the button's
        // enabled\disabled state(value below) which is usually executed when CanExecuteChanged triggered, now is delegated to
        // RequerySuggested event, so when RequerySuggested triggered, the execution logic is being executed, and button's state gets updated.
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        #endregion
    }

code-一個簡單的DelegateCommand類:

View Code
public class DelegateCommand : ICommand
    {
        #region Fields

        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        #endregion

        #region Constructors

        public DelegateCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }

        #endregion

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }
        public event EventHandler CanExecuteChanged;

        // The CanExecuteChanged is automatically registered by command binding, we can assume that it has some execution logic 
        // to update the button's enabled\disabled state(though we cannot see). So raises this event will cause the button's state be updated.
        public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
                CanExecuteChanged(this, EventArgs.Empty);
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        #endregion
    }

在這個例子中,第一個RoutedCommand是系統定義的,后面兩個是經常看到的兩個自定義的command的實現,它們都要實現

ICommand接口。這個接口有一個重要的事件public event EventHandler CanExecuteChanged,command binding機制內部會

自動的注冊這個事件,當我們觸發這個事件的時候,command binding機制內部會執行相應的邏輯來更新該命令可用不可用的狀態(如上面的DelegateCommand的實現)。

另一種實現方式如上面的RelayCommand所示,

public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

它把更新命令可用不可用的狀態的邏輯(上面代碼中的value)代理給了CommandManager.RequerySuggested事件,而這個事件的觸發是由CommandManager自己來檢測的,當RequerySuggested事件被觸發時,執行同樣的邏輯(上面的value),command同樣得到刷新。

該實現與DelegateCommand的不同是DelegateCommand需要自己手動的調用RaiseCanExecuteChanged()方法來刷新,而RelayCommand的實現是一種懶的方式,不需要自己調用,由系統檢測。
這種懶的方式帶來的問題就是導致CanExecute方法多次被執行,例如上面說到的焦點改變時,可能會帶來性能影響。
如果查看RoutedCommand的實現,可以發現它的實現和RelayCommand是一樣的,所以平時我們使用它的時候並不需要手動的
通知這個命令刷新了。

RoutedCommand的內部實現:

 

INotifyPropertyChanged接口的PropertyChanged事件和ICommand接口的CanExecuteChanged事件類似,
通常在一個數據綁定中,我們並沒有去注冊這個事件,而我們經常調用如下的方法去通知某個地方要去做些更新的動作了。
protected void OnPropertyChanged(string propertyName)
{
    if (this.PropertyChanged != null)
         this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
那么是誰注冊這個事件去做一些事情呢?答案是數據綁定機制內部去注冊的,所以我們只管觸發這個事件發送一個通知就好了。

 

示例下載:http://files.cnblogs.com/bear831204/RelayAndDelegateCommand.zip


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM