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:
OnUnloadedfires 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 —
ViewModelLocatorvsViewLocator, mapping, and lifecycle binding. Design Decisions — why View is not in DI.