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.