Skip to content

Alright — this is one of those topics where WPF really starts to feel different from WinForms or typical event-driven UI.

Let’s build this in layers so you don’t just learn syntax, but actually understand how commands behave in real systems.


1. Big Picture

Why WPF uses commands instead of events

In classic UI (WinForms, WebForms):

csharp
button.Click += (s, e) => Save();

This works… until your app grows.

The problem with events

  • Logic lives in code-behind (UI layer)
  • Hard to test (requires UI)
  • Hard to reuse (logic tied to a specific button)
  • Hard to coordinate state (enable/disable logic scattered)

In real systems (dashboards, machine control, enterprise tools), you need:

  • Centralized logic
  • UI-independent behavior
  • Testable actions
  • Dynamic UI state (enabled/disabled based on conditions)

👉 That’s why WPF introduces Commands


2. Beginner Mental Model

Think of a command as an “action object”

Instead of:

“Button calls method”

Think:

“Button triggers an object that represents an action”


Flow in WPF

UI (Button)

Command (ICommand)

ViewModel logic

So:

  • UI does NOT contain logic
  • ViewModel owns behavior
  • Command is the bridge

3. Basic Example

ViewModel

csharp
public class MainViewModel
{
    public ICommand SayHelloCommand { get; }

    public MainViewModel()
    {
        SayHelloCommand = new RelayCommand(SayHello);
    }

    private void SayHello()
    {
        MessageBox.Show("Hello from ViewModel!");
    }
}

XAML

xml
<Button Content="Say Hello"
        Command="{Binding SayHelloCommand}" />

No Click event. No code-behind.


Simple RelayCommand

This is the most common implementation.

csharp
public class RelayCommand : ICommand
{
    private readonly Action _execute;
    private readonly Func<bool>? _canExecute;

    public RelayCommand(Action execute, Func<bool>? canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object? parameter)
        => _canExecute?.Invoke() ?? true;

    public void Execute(object? parameter)
        => _execute();

    public event EventHandler? CanExecuteChanged;

    public void RaiseCanExecuteChanged()
        => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

4. How It Differs from Events

Event-based approach

xml
<Button Click="OnSaveClicked" />
csharp
private void OnSaveClicked(object sender, RoutedEventArgs e)
{
    Save();
}

Problems

  • UI tightly coupled to logic
  • Hard to test Save()
  • No built-in way to control button state

Command-based approach

xml
<Button Command="{Binding SaveCommand}" />

Now:

  • UI is dumb
  • ViewModel controls behavior
  • Logic is reusable + testable

5. How It Really Works in WPF

Let’s go deeper.


ICommand contract

csharp
public interface ICommand
{
    bool CanExecute(object parameter);
    void Execute(object parameter);
    event EventHandler CanExecuteChanged;
}

Execution flow

When user clicks button:

  1. WPF checks:

    csharp
    if (command.CanExecute(parameter))
  2. If true → calls:

    csharp
    command.Execute(parameter)

UI state (enabled / disabled)

WPF automatically disables the button when:

csharp
CanExecute == false

No manual UI code needed.


How UI knows when to re-check?

Through:

csharp
CanExecuteChanged

When this event fires:

👉 WPF re-evaluates CanExecute() 👉 UI updates automatically


Example

csharp
SaveCommand = new RelayCommand(Save, CanSave);

private bool CanSave()
{
    return !string.IsNullOrEmpty(Name);
}

When Name changes:

csharp
SaveCommand.RaiseCanExecuteChanged();

CommandParameter

Pass data from UI → command

xml
<Button Content="Delete"
        Command="{Binding DeleteCommand}"
        CommandParameter="{Binding SelectedItem}" />

In command:

csharp
public void Execute(object parameter)
{
    var item = (MyItem)parameter;
}

6. Real-World Example

Scenario: Save form


ViewModel

csharp
public class FormViewModel : INotifyPropertyChanged
{
    private string _name;

    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            OnPropertyChanged();
            SaveCommand.RaiseCanExecuteChanged();
        }
    }

    public RelayCommand SaveCommand { get; }

    public FormViewModel()
    {
        SaveCommand = new RelayCommand(Save, CanSave);
    }

    private void Save()
    {
        // Save logic
    }

    private bool CanSave()
    {
        return !string.IsNullOrWhiteSpace(Name);
    }
}

XAML

xml
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />

<Button Content="Save"
        Command="{Binding SaveCommand}" />

Behavior

StateButton
Name emptyDisabled
Name validEnabled

👉 No UI logic. Pure ViewModel.


7. Common Mistakes

❌ Forgetting to raise CanExecuteChanged

csharp
// UI won't update
Name = "abc";

✔ Fix:

csharp
SaveCommand.RaiseCanExecuteChanged();

❌ Doing heavy logic inside command constructor

Commands should be thin wrappers, not business logic containers.


❌ Mixing events + commands randomly

  • Leads to inconsistent architecture
  • Hard to maintain

❌ Using commands for everything

Not all interactions need commands:

  • Low-level UI events (mouse move, drag)
  • Visual-only behavior

8. Practical Guidance

When to use commands

✔ Use commands when:

  • It’s a user action (Save, Delete, Submit)
  • Logic belongs in ViewModel
  • You need enable/disable logic
  • You want testability

When to use events

✔ Use events when:

  • UI-specific behavior (animations, drag, focus)
  • No business logic involved

Structuring commands in ViewModels

In real systems:

csharp
public ICommand SaveCommand { get; }
public ICommand DeleteCommand { get; }
public ICommand RefreshCommand { get; }

Or group them:

csharp
public class CustomerCommands
{
    public ICommand Save { get; }
    public ICommand Delete { get; }
}

Scaling in large applications

In enterprise apps:

  • Use generic RelayCommand<T>
  • Use Command factories
  • Centralize command logic patterns
  • Integrate with validation systems

Testability

You can test:

csharp
Assert.False(vm.SaveCommand.CanExecute(null));

vm.Name = "Valid";

Assert.True(vm.SaveCommand.CanExecute(null));

No UI required.


9. Summary

Key ideas to remember

  • Commands replace event handlers for clean architecture
  • ICommand = action abstraction
  • UI triggers → ViewModel handles logic
  • Execute = what to do
  • CanExecute = whether it’s allowed
  • CanExecuteChanged = when UI should update

Mental shift

Old thinking:

“Button calls method”

WPF thinking:

“Button triggers a command, ViewModel decides behavior”


Why this matters in real systems

In large WPF apps (like machine control or dashboards):

  • You avoid UI spaghetti
  • You centralize behavior
  • You gain testability
  • You get automatic UI state management

If you want next step, I’d suggest:

👉 Deep dive: CommandManager & automatic requery behavior This is where WPF gets non-obvious in production systems.

Docs-first project memory for AI-assisted implementation.