命令是ICommand類型的屬性,binding到控件上,用於代替事件,個人認為事件也很好,命令只是輕度解耦前后端。
閑話少說,上代碼,示例是ScreenToGif的源代碼中的一個命令:
public ICommand OpenOptions
{
get
{
return new RelayCommand
{
CanExecutePredicate = a => true, //TODO: Always let this window opens or check if there's any other recorder active?
ExecuteAction = a =>
{
var options = Application.Current.Windows.OfType<Options>().FirstOrDefault();
var tab = a as int? ?? 0; //Parameter that selects which tab to be displayed.
if (options == null)
{
options = new Options(tab);
options.Closed += (sender, args) =>
{
CloseOrNot();
};
//TODO: Open as dialog or not? Block other windows?
options.Show();
}
else
{
if (options.WindowState == WindowState.Minimized)
options.WindowState = WindowState.Normal;
options.SelectTab(tab);
options.Activate();
}
}
};
}
}
這樣看有些麻煩,我們省略一些暫時對理解不重要的東西:
public ICommand OpenOptions
{
get
{
return new RelayCommand
{
CanExecutePredicate = a => true,
ExecuteAction = a =>
{
//這是個lambda表達式
}
};
}
}
實際上就是返回了一個new RelayCommand,這個RelayCommand是作者自定義的一個路由命令的類,代碼如下:
/// <summary>
/// 路由命令
/// </summary>
internal class RelayCommand : ICommand
{
/// <summary>
/// 作者自定義的字段
/// </summary>
public Predicate<object> CanExecutePredicate { get; set; }
/// <summary>
/// 作者自定義的字段
/// </summary>
public Action<object> ExecuteAction { get; set; }
/// <summary>
/// 構造函數
/// </summary>
/// <param name="canExecute"></param>
/// <param name="execute"></param>
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
CanExecutePredicate = canExecute;
ExecuteAction = execute;
}
/// <summary>
/// ICommand字段
/// </summary>
public RelayCommand()
{ }
/// <summary>
/// ICommand字段
/// </summary>
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
/// <summary>
/// ICommad字段
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
return CanExecutePredicate == null || CanExecutePredicate(parameter);
}
/// <summary>
/// ICommand字段
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
ExecuteAction(parameter);
}
}
使用路由的好處就是不管啥命令返回的都是RelayCommand類的實例,只要給該實例綁定上相應的方法就好了,方法可以隨意寫,上面作者就是為路由實例賦值了一個lambda表達式。
下面使用實例操作一下,下面的例子按下按鈕之后修改button的context,為了方便我就直接使用上面ScreenToGif的RelayCommand。為了不與之前的代碼沖突,我新加一個button:
新加的button的context同樣是binding到Name屬性,Command則是binding到了名為“ChangeName”命令上,該命令如下:
此時,MainViewModel.cs的所有代碼如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace WpfApp.ViewModel
{
class MainViewModel : INotifyPropertyChanged
{
#region INPC
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
public ICommand ChangeName
{
get
{
return new RelayCommand() {
CanExecutePredicate = a => true,
ExecuteAction = a =>
{
Name = "ChangeName Command";
}
};
}
}
}
/// <summary>
/// 路由命令
/// </summary>
internal class RelayCommand : ICommand
{
public Predicate<object> CanExecutePredicate { get; set; }
public Action<object> ExecuteAction { get; set; }
/// <summary>
/// 構造函數
/// </summary>
/// <param name="canExecute"></param>
/// <param name="execute"></param>
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
CanExecutePredicate = canExecute;
ExecuteAction = execute;
}
/// <summary>
/// ICommand字段
/// </summary>
public RelayCommand()
{ }
/// <summary>
/// ICommand字段
/// </summary>
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
/// <summary>
/// ICommad字段
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
return CanExecutePredicate == null || CanExecutePredicate(parameter);
}
/// <summary>
/// ICommand字段
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
ExecuteAction(parameter);
}
}
}
按照我的設定,按下第一個按鈕會觸發Button_Click方法(該方法在view的后台代碼中,也就是我的工程中的MainWindow.xaml.cs文件)這是事件的方法,按下后會更改Name屬性的內容,所有binding到該屬性的控件內容都會跟隨更改。
按下第二個按鈕,則是觸發ChangeName命令,該命令則是view的DataContext(也就是MainViewModel類)的ChangeName屬性。按下后同樣會修改Name屬性的內容,所有所有binding到該屬性的控件內容都會跟隨更改。
下面debug一下:
喜大普奔,沒有啥bug,哈哈哈哈哈哈
工程源代碼上傳在GitHub上了:https://github.com/feipeng8848/WPF-Demo
參考:https://www.codeproject.com/Articles/1052346/ICommand-Interface-in-WPF