常用命令
WPF的命令實際上就是實現了ICommand接口的類,平時使用最多的是RoutedCommand類,還可以使用自定義命令。
RoutedCommand只負責跑腿,並不對命名目標做任何操作,實際操作沒那么方便而且需要在后台實現相關的事件,可以參考WPF 命令。
自定義命令直接在命令目標上起作用,而不像RoutedCommand那樣先在命令目標上激發出路由事件等外圍控件捕捉到事件后再“翻過頭來”對命令目標加以處理。
委托命令
實現一個DelegateCommand,代碼如下:
using System;
using System.Windows.Input;
/// <summary>
/// 委托命令
/// </summary>
public class DelegateCommand : ICommand
{
private Action executeAction;
private Func<bool> canExecuteFunc;
/// <summary>
/// 當出現影響是否應執行該命令的更改時發生。
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// 構造函數,不指定canExecute委托時CanExecute方法默認返回true
/// </summary>
/// <param name="execute"></param>
/// <param name="canExecute"></param>
public DelegateCommand(Action execute, Func<bool> canExecute = null)
{
if (execute != null)
{
executeAction = execute;
canExecuteFunc = canExecute;
}
}
/// <summary>
/// 定義在調用此命令時要調用的方法。
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
if (executeAction != null && CanExecute(parameter))
{
executeAction();
}
}
/// <summary>
/// 定義確定此命令是否可在其當前狀態下執行的方法。
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
if (canExecuteFunc == null)
{
return true;
}
return canExecuteFunc();
}
}
上面的代碼使用了系統的CommandManager.RequerySuggested,如果ViewModel有繼承綁定基類,可以在基類中監控屬性值的變更並觸發CanExecuteChanged以節省性能損耗。此時,添加如下代碼:
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
委托命令(泛型)
為需要傳參數的委托命令實現一個泛型版本,相當於直接使用object,泛型可以在編譯期間檢查類型錯誤。DelegateCommand<T>代碼如下:
using System;
using System.Windows.Input;
/// <summary>
/// 委托命令——帶泛型參數
/// </summary>
/// <typeparam name="T"></typeparam>
public class DelegateCommand<T> : ICommand
{
private Action<T> executeAction;
private Func<T, bool> canExecuteFunc;
/// <summary>
/// 當出現影響是否應執行該命令的更改時發生。
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// 構造函數,不指定canExecute委托時CanExecute方法默認返回true
/// </summary>
/// <param name="execute"></param>
/// <param name="canExecute"></param>
public DelegateCommand(Action<T> execute, Func<T, bool> canExecute = null)
{
if (execute != null)
{
executeAction = execute;
canExecuteFunc = canExecute;
}
}
/// <summary>
/// 定義在調用此命令時要調用的方法。
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
if (executeAction != null && CanExecute(parameter))
{
executeAction(ChangeTo<T>(parameter));
}
}
/// <summary>
/// 定義確定此命令是否可在其當前狀態下執行的方法。
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
if (canExecuteFunc == null)
{
return true;
}
return canExecuteFunc(ChangeTo<T>(parameter));
}
/// <summary>
/// object轉為泛型類型,兼容數值、對象實例
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
private static T ChangeTo<T>(object obj)
{
T result = default(T);
if (obj != null)
{
try
{
result = (T)Convert.ChangeType(obj, typeof(T));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
return result;
}
}
泛型版本的類型轉換比較麻煩,泛型參數需要考慮字符串、數值、對象實例等情況。參數盡量使用后台綁定的值,綁定更新過程可以將類型轉換異常提前暴露出來,避免異常發生在命令執行過程中。
使用委托命令
創建一個MainViewModel,代碼如下:
class MainViewModel
{
public bool CanExecute { get; set; }
public int Param { get; set; }
public DelegateCommand NormalCommand { get; }
public DelegateCommand<int> ParamCommand { get; }
public MainViewModel()
{
NormalCommand = new DelegateCommand(() => { MessageBox.Show("無參命令執行成功"); }, () => CanExecute);
ParamCommand = new DelegateCommand<int>(i => { MessageBox.Show("參數命令執行成功:" + i); }, i => CanExecute);
}
}
界面的XAML代碼如下:
<StackPanel>
<CheckBox Content = "命令開關" IsChecked="{Binding CanExecute}"/>
<Label Content = "命令參數:" />
< TextBox Text="{Binding Param}"/>
<Button Content = "無參數命令" Command="{Binding NormalCommand}"/>
<Button Content = "有參數命令" Command="{Binding ParamCommand}" CommandParameter="{Binding Param}" />
</StackPanel>
在后台代碼中添加DataContext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
運行程序,效果如下: