Table of Contents

MVVM Pattern

Binding Modes

Mode XAML View ViewModel
View-first ViewModelLocator.AutoWireViewModel="True" XAML / new From DI
ViewModel-first ContentControl Content="{Binding Vm}" ViewLocator creates From DI

Options.EnableViewLocator only toggles ViewModel-first (ViewLocator on DataTemplates). AutoWire always works when a mapping exists. Configure via ConfigureOptions:

public override void ConfigureOptions(CrystalOptions options)
{
    options.EnableViewLocator = true; // default
}

Registration

AddMvvmTransient / AddMvvmSingleton register ViewModel in DI and record View mapping on CrystalApplication.Mvvm:

services.AddMvvmTransient<MainView, MainViewModel>();
services.AddMvvmSingleton<AboutView, AboutViewModel>();

Shell views (MainWindow, etc.) are not in DI — use CreateShell:

CreateShell<MainWindow, MainView>();

ILifecycleAware (Optional)

OnLoadedAsync runs on every Loaded. isFirstLoad is per ViewModel instance.

Refresh on every visit:

public partial class MainViewModel : ObservableObject, ILifecycleAware
{
    public Task OnLoadedAsync(bool isFirstLoad) => LoadDataAsync();
    public Task OnUnloaded() => SaveStateAsync();
}

Run setup only once (typical for a singleton ViewModel):

public Task OnLoadedAsync(bool isFirstLoad)
{
    if (!isFirstLoad) return Task.CompletedTask;
    return LoadDataAsync();
}

Tab / WebView: OnUnloaded fires whenever the view leaves the visual tree.

EventToCommand (Optional)

Bridge View events to ICommand without Avalonia.Xaml.Behaviors. Types live in the default Avalonia xmlns (same as ViewModelLocator) — no extra xmlns.

Single event (shorthand):

<Button Content="Save"
        EventToCommand.EventName="Click"
        EventToCommand.Command="{Binding SaveCommand}"
        EventToCommand.CommandParameter="{Binding SelectedItem}"/>

Multiple events:

<Border>
  <EventToCommand.Bindings>
    <EventBinding EventName="PointerPressed" Command="{Binding PressCommand}" PassEventArgs="True"/>
    <EventBinding EventName="DoubleTapped" Command="{Binding OpenCommand}"/>
  </EventToCommand.Bindings>
</Border>
API Description
EventToCommand.Bindings / EventBinding Multiple event → command mappings
EventBinding.Command ICommand (usual {Binding})
EventName / Command / … on control Shorthand for one event

Shorthand and Bindings can be used together. Prefer Avalonia routed events (Click → ClickEvent).

In 3.1.1, name-based routed event bindings work with Native AOT without event-field preservation attributes. You can also use EventToCommand.RoutedEvent or EventBinding.RoutedEvent to supply the event directly. See the EventToCommand guide for examples and CLR event limitations.

API Summary

API Description
AddMvvmTransient<TView, TViewModel>() AddTransient<TViewModel>() + mapping
AddMvvmSingleton<TView, TViewModel>() AddSingleton<TViewModel>() + mapping
CreateShell<TWindow, TView>() Create shell with new by platform lifetime
CrystalApplication.Mvvm Per-app mappings and ServiceProvider
CrystalApplication.Options / ConfigureOptions Per-app CrystalOptions (also DI singleton)
Options.EnableViewLocator Whether to register ViewModel-first ViewLocator
EventToCommand Event → ICommand (shorthand + Bindings / EventBinding)
ILifecycleAware Optional load/unload hooks

Further Reading

How it works: Architecture — MVVM Wiring — ViewModelLocator vs ViewLocator, mapping, and lifecycle binding. Design Decisions — why View is not in DI.