CommunityToolkit.Mvvm (core)
Utilisez cette skill lors de la création ou de la révision de ViewModels, propriétés, commandes ou validation dans des applications utilisant CommunityToolkit.Mvvm 8.x.
Skills compagnons. Chargez
mvvm-toolkit-messengerpour les patterns pub/subIMessenger. Chargezmvvm-toolkit-dipour l'intégrationMicrosoft.Extensions.DependencyInjection.
Récapitulatif rapide.
[ObservableProperty]sur les champs privés dans les classespartial;[RelayCommand]sur les méthodes d'instance ; héritage deObservableObject(ouObservableValidatorpour les formulaires,ObservableRecipientlors de l'utilisation deIMessenger).
Package & setup
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" />
</ItemGroup>
Cibles : netstandard2.0, netstandard2.1, net6.0+. Fonctionne sur .NET, .NET Framework, Mono. Les source generators sont intégrés au même NuGet — aucune référence d'analyseur supplémentaire requise.
Namespaces :
using CommunityToolkit.Mvvm.ComponentModel; // ObservableObject, [ObservableProperty]
using CommunityToolkit.Mvvm.Input; // [RelayCommand], RelayCommand, AsyncRelayCommand
Règle universelle. Tout type utilisant
[ObservableProperty]ou[RelayCommand]— et tout type englobant, s'il est imbriqué — doit être déclarépartial. Sans cela, les generators émettentMVVMTK0008/MVVMTK0042.
Aide-mémoire des source generators
| Attribut | Appliqué à | Génère |
|---|---|---|
[ObservableProperty] |
champ privé | Propriété publique INotifyPropertyChanged + hooks de méthode partielle OnXxxChanging/OnXxxChanged |
[NotifyPropertyChangedFor(nameof(Other))] |
champ observable | Lève aussi PropertyChanged pour la propriété listée |
[NotifyCanExecuteChangedFor(nameof(MyCommand))] |
champ observable | Appelle MyCommand.NotifyCanExecuteChanged() au changement |
[NotifyDataErrorInfo] |
champ observable sur ObservableValidator |
Appelle ValidateProperty(value) depuis le setter |
[NotifyPropertyChangedRecipients] |
champ observable sur ObservableRecipient |
Broadcast(old, new) après le changement |
[RelayCommand] |
méthode d'instance | RelayCommand / AsyncRelayCommand lazy exposé comme IRelayCommand / IAsyncRelayCommand |
[RelayCommand(CanExecute = nameof(CanX))] |
méthode d'instance | Câble CanExecute à une méthode ou propriété |
[RelayCommand(IncludeCancelCommand = true)] |
méthode async avec CancellationToken |
Génère aussi XxxCancelCommand |
[RelayCommand(AllowConcurrentExecutions = true)] |
méthode async | Autorise les invocations mises en queue/parallèles (la valeur par défaut désactive en cours d'exécution) |
[RelayCommand(FlowExceptionsToTaskScheduler = true)] |
méthode async | Expose les exceptions via ExecutionTask au lieu d'attendre et de relancer |
[property: SomeAttr] |
champ observable ou méthode [RelayCommand] |
Transfère SomeAttr à la propriété générée (ex. [JsonIgnore]) |
Nommage. Champ name / _name / m_name → Name. Méthode LoadAsync →
LoadCommand (le suffixe Async est supprimé ; un On initial l'est aussi).
Voir references/source-generators.md pour
la référence complète des attributs avec échantillons de code généré.
Patterns de ViewModel
Propriété observable simple
public partial class ContactViewModel : ObservableObject
{
[ObservableProperty]
private string? name;
}
Hooks : OnXxxChanging / OnXxxChanged
[ObservableProperty]
private string? name;
partial void OnNameChanged(string? value) =>
Logger.LogInformation("Name changed to {Name}", value);
Les deux surcharges à un argument (value) et deux arguments (oldValue, newValue)
sont disponibles. N'implémentez que celles dont vous avez besoin ; les hooks non implémentés
sont élimés par le compilateur (coût d'exécution nul).
Propriétés dépendantes + commandes dépendantes
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
private string? firstName;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
private string? lastName;
public string FullName => $"{FirstName} {LastName}".Trim();
Encapsuler un modèle non observable
public sealed class ObservableUser(User user) : ObservableObject
{
public string Name
{
get => user.Name;
set => SetProperty(user.Name, value, user, (u, n) => u.Name = n);
}
}
Passez une lambda statique (sans état capturé) pour garder l'appel sans allocation.
Commandes
[RelayCommand]
private void Refresh() => Items.Reset();
[RelayCommand]
private async Task LoadAsync()
{
foreach (var item in await service.GetItemsAsync())
Items.Add(item);
}
[RelayCommand(IncludeCancelCommand = true)]
private async Task DownloadAsync(CancellationToken token)
{
await using var stream = await http.GetStreamAsync(url, token);
// ...
}
[RelayCommand(CanExecute = nameof(CanSave))]
private Task SaveAsync() => repo.SaveAsync(Name!);
private bool CanSave() => !string.IsNullOrWhiteSpace(Name);
N'ayez recours aux constructeurs manuels RelayCommand / AsyncRelayCommand que
si vous devez gérer explicitement la durée de vie de la commande ou la composer à partir de
sources non triviales. Le style attribut couvre ~95% des cas.
Voir references/relaycommand-cookbook.md
pour les recettes sync / async / cancellable / concurrence / surfacing d'erreurs.
Sélection de classe de base
| Classe de base | À utiliser quand |
|---|---|
ObservableObject |
Défaut. INotifyPropertyChanged + INotifyPropertyChanging + surcharges SetProperty + SetPropertyAndNotifyOnCompletion pour les propriétés Task |
ObservableValidator |
Le VM a besoin de INotifyDataErrorInfo (formulaires, saisie de paramètres) |
ObservableRecipient |
Le VM envoie ou reçoit des messages IMessenger — voir la skill mvvm-toolkit-messenger |
C# est à héritage simple : ObservableValidator et ObservableRecipient
étendent tous deux ObservableObject, donc les combiner requiert la composition
(ex. injecter IMessenger dans un ObservableValidator).
Validation
using System.ComponentModel.DataAnnotations;
public sealed partial class RegistrationViewModel : ObservableValidator
{
[ObservableProperty]
[NotifyDataErrorInfo]
[Required, MinLength(2), MaxLength(100)]
private string? name;
[ObservableProperty]
[NotifyDataErrorInfo]
[Required, EmailAddress]
private string? email;
[RelayCommand]
private void Submit()
{
ValidateAllProperties();
if (HasErrors) return;
// soumettre...
}
}
Autres points d'entrée : TrySetProperty, ValidateProperty(value, name),
ClearAllErrors(), GetErrors(propertyName). Les règles personnalisées supportent
les méthodes [CustomValidation] et les sous-classes personnalisées ValidationAttribute.
Voir references/validation.md pour la
surface complète du validateur.
Pièges majeurs
- Oublier
partial. La classe (et tout type englobant) doit êtrepartial. Erreur de compilationMVVMTK0008/MVVMTK0042. - Nom de champ en PascalCase.
[ObservableProperty] private string Name;entre en collision avec la propriété générée. Utilisezname,_nameoum_name. async voidsur[RelayCommand]. Le generator n'enveloppe que les méthodes retournantTaskenIAsyncRelayCommand.async voiddevient unRelayCommandsync et les exceptions ne sont pas observées. Retournez toujoursTask.- Oublier
[NotifyCanExecuteChangedFor]. Le bouton Save reste désactivé même siCanSave()retournerait maintenanttrue. - Muter la même référence détenue par un champ
[ObservableProperty].EqualityComparer<T>.Defaultretournetrue, aucune notification n'est déclenchée. Remplacez l'instance au lieu de la muter.
Pour le tableau complet des diagnostics (MVVMTK0xxx) et d'autres pièges, voir
references/troubleshooting.md.
Mini-procédure pas à pas end-to-end
Une application Notes à deux volets démontrant generators + commandes +
[NotifyCanExecuteChangedFor] :
public sealed partial class NoteViewModel(INotesService notes,
IMessenger messenger) : ObservableRecipient(messenger)
{
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
[NotifyCanExecuteChangedFor(nameof(DeleteCommand))]
private string? filename;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
private string? text;
[RelayCommand(CanExecute = nameof(CanSave))]
private Task SaveAsync()
{
Messenger.Send(new NoteSavedMessage(Filename!));
return notes.SaveAsync(Filename!, Text!);
}
[RelayCommand(CanExecute = nameof(CanDelete))]
private Task DeleteAsync() => notes.DeleteAsync(Filename!);
private bool CanSave() =>
!string.IsNullOrWhiteSpace(Filename) && !string.IsNullOrEmpty(Text);
private bool CanDelete() => !string.IsNullOrWhiteSpace(Filename);
}
Pour l'exemple complet (câblage DI, code-behind de View, XAML, tests unitaires), voir
references/end-to-end-walkthrough.md.
Références & skills compagnons
| Sujet | Où |
|---|---|
| Référence d'attribut source generator | references/source-generators.md |
| Recettes RelayCommand | references/relaycommand-cookbook.md |
| Validation approfondie | references/validation.md |
| Procédure pas à pas complète de l'app Notes | references/end-to-end-walkthrough.md |
Diagnostics MVVMTK0xxx & pièges |
references/troubleshooting.md |
| Messenger pub/sub | Companion skill: mvvm-toolkit-messenger |
Câblage Microsoft.Extensions.DependencyInjection |
Companion skill: mvvm-toolkit-di |
Sources externes :
- Aperçu de la Toolkit : https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/
- Tutoriel WinUI MVVM Toolkit : https://learn.microsoft.com/en-us/windows/apps/tutorials/winui-mvvm-toolkit/intro
- Source : https://github.com/CommunityToolkit/dotnet
- Exemples : https://github.com/CommunityToolkit/MVVM-Samples