mvvm-toolkit

Par github · awesome-copilot

CommunityToolkit.Mvvm (le MVVM Toolkit) — noyau : générateurs de source ([ObservableProperty], [RelayCommand], [NotifyPropertyChangedFor], [NotifyCanExecuteChangedFor], [NotifyDataErrorInfo]), classes de base (ObservableObject / ObservableValidator / ObservableRecipient), commandes (RelayCommand / AsyncRelayCommand) et validation. Skills complémentaires : mvvm-toolkit-messenger pour le pub/sub, mvvm-toolkit-di pour le câblage Microsoft.Extensions.DependencyInjection. Compatible WPF, WinUI 3, MAUI, Uno et Avalonia.

npx skills add https://github.com/github/awesome-copilot --skill mvvm-toolkit

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-messenger pour les patterns pub/sub IMessenger. Chargez mvvm-toolkit-di pour l'intégration Microsoft.Extensions.DependencyInjection.

Récapitulatif rapide. [ObservableProperty] sur les champs privés dans les classes partial ; [RelayCommand] sur les méthodes d'instance ; héritage de ObservableObject (ou ObservableValidator pour les formulaires, ObservableRecipient lors de l'utilisation de IMessenger).


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 émettent MVVMTK0008 / 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_nameName. Méthode LoadAsyncLoadCommand (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

  1. Oublier partial. La classe (et tout type englobant) doit être partial. Erreur de compilation MVVMTK0008 / MVVMTK0042.
  2. Nom de champ en PascalCase. [ObservableProperty] private string Name; entre en collision avec la propriété générée. Utilisez name, _name ou m_name.
  3. async void sur [RelayCommand]. Le generator n'enveloppe que les méthodes retournant Task en IAsyncRelayCommand. async void devient un RelayCommand sync et les exceptions ne sont pas observées. Retournez toujours Task.
  4. Oublier [NotifyCanExecuteChangedFor]. Le bouton Save reste désactivé même si CanSave() retournerait maintenant true.
  5. Muter la même référence détenue par un champ [ObservableProperty]. EqualityComparer<T>.Default retourne true, 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
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 :

Skills similaires