WPF Bootcamp
MVVM Core Workflow
MVVM is a collaboration pattern. The view declares structure and binding targets. The view model exposes state and actions. The model layer handles domain rules and data access.
Good MVVM does not eliminate all code-behind. It simply makes behavior predictable by putting durable state and decisions where they can be tested.
What you'll learn
- Which responsibilities belong in the view, view model, and model/service layers.
- How
INotifyPropertyChangedandICommandunderpin most MVVM flows. - When code-behind is acceptable and when it is masking missing view model design.
- How data, commands, and user intent move through a real MVVM screen over time.
public sealed class CustomerEditorViewModel : INotifyPropertyChanged
{
public string Name { get; set; } = string.Empty;
public ICommand SaveCommand { get; }
public event PropertyChangedEventHandler? PropertyChanged;
}
This is the center of the pattern: the view model exposes state and actions that the view can bind to without knowing the details of domain work or storage.
The mental model in one sentence
MVVM says the view declares what the UI looks like, the view model declares what the UI knows and can do, and the model or service layer handles business rules and persistence.
View
Markup, bindings, templates, visual state, and view-only behavior.
View model
State, commands, validation-facing state, and coordination of use cases.
Model or services
Domain entities, persistence, API calls, business rules, and infrastructure concerns.
Why it matters
The view stays replaceable, the logic stays testable, and the responsibilities stop bleeding into each other.
What belongs in the view
The view is responsible for structure and presentation. It owns layout, visuals, bindings, styles, templates, and interaction wiring such as commands and view-specific event hookups.
<StackPanel>
<TextBox Text="{Binding Name}" />
<Button Content="Save" Command="{Binding SaveCommand}" />
</StackPanel>
The view should know which control is a text box, which button is visible, and how content is laid out. It should not own durable application state just because a named control happens to exist on screen.
What belongs in the view model
The view model owns state the screen needs to display and mutate, plus the actions the user can request from that screen.
public sealed class CustomerEditorViewModel : INotifyPropertyChanged
{
private string _name = string.Empty;
public string Name
{
get => _name;
set
{
if (_name == value)
{
return;
}
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public ICommand SaveCommand { get; }
public event PropertyChangedEventHandler? PropertyChanged;
}
- Put editable form state here.
- Put selected-item state here.
- Put command properties here.
- Put validation-facing screen state here.
The view model should not depend on concrete controls or visual tree traversal. It should describe the screen's state and actions in UI-agnostic terms.
What belongs in models and services
The view model coordinates work, but it should not become the place where every domain rule and infrastructure concern gets dumped.
Model
Domain entities, validation rules, and business meaning that exist even without a screen.
Service
Persistence, HTTP calls, navigation orchestration, dialogs, logging, and similar cross-cutting operations.
The view model should call into services or manipulate models, not replace them entirely.
How a normal MVVM screen works
- The view gets a view model as its
DataContext. - The view binds controls to properties and commands on that view model.
- The user edits values or invokes commands through the view.
- The binding system updates the view model as needed.
- The view model validates, transforms, or coordinates work through services.
- The view model raises property-change notifications so the view refreshes.
That loop is the core workflow. The screen stays declarative, but it is still reactive because the binding system and command system keep the view and view model synchronized.
INotifyPropertyChanged and ICommand are the backbone
Most MVVM screens rely on two simple interfaces.
INotifyPropertyChangedtells the view when bound properties changed.ICommandlets the view expose user actions declaratively.
If a view model is missing one of these where it should have it, the MVVM flow usually becomes awkward quickly.
Practical rules
- Put persistent UI state in the view model, not in named controls.
- Keep view models unaware of concrete controls and visual tree details.
- Push domain work into services or models so the view model coordinates rather than owns everything.
- Allow code-behind for view-only concerns such as focus, animations, or platform-specific glue.
When code-behind is acceptable
Good MVVM does not ban code-behind. It limits it to concerns that are truly about the view layer.
Usually acceptable
Focus management, animation triggers, view-only composition glue, platform host integration, and control-specific behavior that does not belong in domain logic.
Usually a smell
Saving records, mutating durable screen state, making service calls, or deciding business rules inside a click handler.
The question is not "is this code in the view file?" The question is "is this concern fundamentally about the view?"
Signs a view model is taking on too much
MVVM can fail in the other direction too. A view model can become a giant coordinator with no clear boundaries.
- It knows too much about navigation, dialogs, persistence, and every other service in the app.
- It makes detailed visual decisions such as pixel measurements or control choreography.
- It becomes difficult to explain which state is screen state versus domain state.
When that happens, the answer is usually not "put it back in the view." The answer is often to introduce better service boundaries or split the view model into clearer responsibilities.
A worked end-to-end example
<StackPanel>
<TextBox Text="{Binding Name, Mode=TwoWay}" />
<TextBlock Text="{Binding StatusMessage}" />
<Button Content="Save" Command="{Binding SaveCommand}" />
</StackPanel>
public sealed class CustomerEditorViewModel : INotifyPropertyChanged
{
private readonly ICustomerService _customerService;
private string _name = string.Empty;
private string _statusMessage = string.Empty;
public string Name
{
get => _name;
set
{
if (_name == value)
{
return;
}
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public string StatusMessage
{
get => _statusMessage;
private set
{
if (_statusMessage == value)
{
return;
}
_statusMessage = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StatusMessage)));
}
}
public ICommand SaveCommand { get; }
public event PropertyChangedEventHandler? PropertyChanged;
}
- The user types into the text box.
- The binding updates
Nameon the view model. - The user clicks Save.
- The command calls into a service to persist the data.
- The view model updates
StatusMessage. - The text block refreshes because property change notification propagated back through binding.
That is MVVM working end to end: the view declares, the view model coordinates, and services do the domain or infrastructure work.
Common mistakes
- Making the view model responsible for pixel-level presentation decisions.
- Turning the view model into a dumping ground for service location, navigation, dialogs, and validation with no clear boundaries.
- Refusing all code-behind even for pure view concerns, which often leads to worse abstractions.
- Letting controls become the source of truth for state that should live in the view model.
- Using MVVM language while still pushing core workflow through code-behind handlers.
InkkSlinger parity note: The existing docs and samples already use a WPF-like MVVM flow. The more faithfully you keep state in the view model and use binding or commands in the view, the more portable your design becomes between WPF and InkkSlinger.
Next, add collections to the picture so your screens can render repeated data instead of one-off controls.