iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0

在 WinForms 中,我們常常會直接寫事件處理,例如:

private void Button_Click(object sender, EventArgs e)
{
    // 執行邏輯
}

但在 WPF + MVVM 中,我們希望 把邏輯放在 ViewModel,而不是寫在 View 的 Code-behind。這時候就要用到 Command 模式


ICommand 介面

WPF 的所有 Command 都要實作 ICommand 介面:

public interface ICommand
{
    event EventHandler CanExecuteChanged;
    bool CanExecute(object parameter);
    void Execute(object parameter);
}
  • CanExecute:決定按鈕能不能按
  • Execute:執行要做的動作
  • CanExecuteChanged:通知 UI「可否執行」的狀態改變

RelayCommand

手動實作 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;
    }
}

在 ViewModel 使用 RelayCommand

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("選股邏輯執行中...");
    }
}

在 View 綁定 Command

XAML:

<Button Content="選股" Command="{Binding SelectStockCommand}" Width="100" Height="30"/>

效果:

  • 按下按鈕 → 執行 ViewModel 的 SelectStock()
  • UI 不需要 Click 事件

AsyncCommand

在真實的應用中,常常需要呼叫 API 或存取資料庫,這些操作應該是 非同步 的。
這時候就會用 AsyncCommand,它能避免 UI 卡住。

實作簡單版 AsyncCommand

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);
}

ViewModel 使用 AsyncCommand

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 可以安全地執行非同步任務
  • 在 XAML 綁定 Command,就能完全把事件處理從 View 移到 ViewModel


上一篇
Day 18 - WPF Trigger介紹
下一篇
Day 20 - 整合 API 與 WPF
系列文
30天快速上手製作WPF選股工具 — 從C#基礎到LiteDB與Web API整合21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言