在 WinForms 中,我們常常會直接寫事件處理,例如:
private void Button_Click(object sender, EventArgs e)
{
// 執行邏輯
}
但在 WPF + MVVM 中,我們希望 把邏輯放在 ViewModel,而不是寫在 View 的 Code-behind。這時候就要用到 Command 模式。
WPF 的所有 Command 都要實作 ICommand
介面:
public interface ICommand
{
event EventHandler CanExecuteChanged;
bool CanExecute(object parameter);
void Execute(object parameter);
}
手動實作 ICommand
會很冗長,所以大家會用一個常見的輔助類別:RelayCommand
。
using System;
using System.Windows.Input;
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
using System.Windows.Input;
public class StockViewModel
{
public ICommand SelectStockCommand { get; }
public StockViewModel()
{
SelectStockCommand = new RelayCommand(
execute: _ => SelectStock(),
canExecute: _ => true // 這裡可以加條件,例如有股票清單時才可執行
);
}
private void SelectStock()
{
Console.WriteLine("選股邏輯執行中...");
}
}
XAML:
<Button Content="選股" Command="{Binding SelectStockCommand}" Width="100" Height="30"/>
效果:
SelectStock()
Click
事件在真實的應用中,常常需要呼叫 API 或存取資料庫,這些操作應該是 非同步 的。
這時候就會用 AsyncCommand
,它能避免 UI 卡住。
using System;
using System.Threading.Tasks;
using System.Windows.Input;
public class AsyncCommand : ICommand
{
private readonly Func<Task> _execute;
private readonly Func<bool> _canExecute;
private bool _isExecuting;
public AsyncCommand(Func<Task> execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => !_isExecuting && (_canExecute?.Invoke() ?? true);
public async void Execute(object parameter)
{
_isExecuting = true;
RaiseCanExecuteChanged();
try
{
await _execute();
}
finally
{
_isExecuting = false;
RaiseCanExecuteChanged();
}
}
public event EventHandler CanExecuteChanged;
private void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public class StockViewModel
{
public ICommand LoadStockCommand { get; }
public StockViewModel()
{
LoadStockCommand = new AsyncCommand(LoadStocksAsync);
}
private async Task LoadStocksAsync()
{
await Task.Delay(2000); // 模擬 API 呼叫
Console.WriteLine("股票清單已載入");
}
}
XAML:
<Button Content="載入股票" Command="{Binding LoadStockCommand}" Width="120" Height="30"/>
效果:
LoadStocksAsync()
_isExecuting = true
)今天我們學會了:
ICommand
是 Command 模式的核心介面RelayCommand
讓我們能快速建立同步命令AsyncCommand
可以安全地執行非同步任務