Skip to main content

mvvm-toolkit

CommunityToolkit.Mvvm (the MVVM Toolkit) core: source generators ([ObservableProperty], [RelayCommand], [NotifyPropertyChangedFor], [NotifyCanExecuteChangedFor], [NotifyDataErrorInfo]), base classes (ObservableObject / ObservableValidator / ObservableRecipient), commands (RelayCommand / AsyncRelayCommand), and validation. Companion skills: mvvm-toolkit-messenger for pub/sub, mvvm-toolkit-di for Microsoft.Extensions.DependencyInjection wiring. Works across WPF, WinUI 3, MAUI, Uno, and Avalonia.

소스 정보

저장소
github/awesome-copilot
최근 소스 활동
2026년 5월 11일 01:29
감지된 SKILL.md 언어
영어
스타
40,947
포크
5,319

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
6 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
mvvm-toolkit
description
CommunityToolkit.Mvvm (the MVVM Toolkit) core: source generators ([ObservableProperty], [RelayCommand], [NotifyPropertyChangedFor], [NotifyCanExecuteChangedFor], [NotifyDataErrorInfo]), base classes (ObservableObject / ObservableValidator / ObservableRecipient), commands (RelayCommand / AsyncRelayCommand), and validation. Companion skills: mvvm-toolkit-messenger for pub/sub, mvvm-toolkit-di for Microsoft.Extensions.DependencyInjection wiring. Works across WPF, WinUI 3, MAUI, Uno, and Avalonia.
# CommunityToolkit.Mvvm (core) Use this skill when authoring or reviewing ViewModels, properties, commands, or validation in apps that use `CommunityToolkit.Mvvm` 8.x. > **Companion skills.** Load **`mvvm-toolkit-messenger`** for `IMessenger` > pub/sub patterns. Load **`mvvm-toolkit-di`** for > `Microsoft.Extensions.DependencyInjection` integration. > **Quick recap.** `[ObservableProperty]` on private fields in `partial` > classes; `[RelayCommand]` on instance methods; inherit from > `ObservableObject` (or `ObservableValidator` for input forms, > `ObservableRecipient` when using `IMessenger`). --- ## Package & setup ```xml <ItemGroup> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" /> </ItemGroup> ``` Targets: `netstandard2.0`, `netstandard2.1`, `net6.0`+. Works on .NET, .NET Framework, Mono. Source generators ship in the same NuGet — no extra analyzer reference required. Namespaces: ```csharp using CommunityToolkit.Mvvm.ComponentModel; // ObservableObject, [ObservableProperty] using CommunityToolkit.Mvvm.Input; // [RelayCommand], RelayCommand, AsyncRelayCommand ``` > **Universal rule.** Every type that uses `[ObservableProperty]` or > `[RelayCommand]` — and every enclosing type, if nested — must be > declared `partial`. Without it, the generators emit > `MVVMTK0008` / `MVVMTK0042`. --- ## Source generators cheat sheet | Attribute | Applied to | Generates | |-----------|-----------|-----------| | `[ObservableProperty]` | private field | Public `INotifyPropertyChanged` property + `OnXxxChanging`/`OnXxxChanged` partial-method hooks | | `[NotifyPropertyChangedFor(nameof(Other))]` | observable field | Also raises `PropertyChanged` for the listed property | | `[NotifyCanExecuteChangedFor(nameof(MyCommand))]` | observable field | Calls `MyCommand.NotifyCanExecuteChanged()` on change | | `[NotifyDataErrorInfo]` | observable field on `ObservableValidator` | Calls `ValidateProperty(value)` from the setter | | `[NotifyPropertyChangedRecipients]` | observable field on `ObservableRecipient` | `Broadcast(old, new)` after the change | | `[RelayCommand]` | instance method | Lazy `RelayCommand` / `AsyncRelayCommand` exposed as `IRelayCommand` / `IAsyncRelayCommand` | | `[RelayCommand(CanExecute = nameof(CanX))]` | instance method | Wires `CanExecute` to a method or property | | `[RelayCommand(IncludeCancelCommand = true)]` | async method with `CancellationToken` | Also generates `XxxCancelCommand` | | `[RelayCommand(AllowConcurrentExecutions = true)]` | async method | Allows queued/parallel invocations (default disables while running) | | `[RelayCommand(FlowExceptionsToTaskScheduler = true)]` | async method | Surfaces exceptions via `ExecutionTask` instead of awaiting and rethrowing | | `[property: SomeAttr]` | observable field or `[RelayCommand]` method | Forwards `SomeAttr` onto the generated property (e.g., `[JsonIgnore]`) | **Naming.** Field `name` / `_name` / `m_name` → `Name`. Method `LoadAsync` → `LoadCommand` (the `Async` suffix is stripped; a leading `On` is also stripped). See [`references/source-generators.md`](references/source-generators.md) for the full attribute reference with generated-code samples. --- ## ViewModel patterns ### Simple observable property ```csharp public partial class ContactViewModel : ObservableObject { [ObservableProperty] private string? name; } ``` ### Hooks: `OnXxxChanging` / `OnXxxChanged` ```csharp [ObservableProperty] private string? name; partial void OnNameChanged(string? value) => Logger.LogInformation("Name changed to {Name}", value); ``` Both single-arg `(value)` and two-arg `(oldValue, newValue)` overloads are available. Implement only the ones you need; unimplemented hooks are elided by the compiler (zero runtime cost). ### Dependent properties + dependent commands ```csharp [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(); ``` ### Wrapping a non-observable model ```csharp public sealed class ObservableUser(User user) : ObservableObject { public string Name { get => user.Name; set => SetProperty(user.Name, value, user, (u, n) => u.Name = n); } } ``` Pass a static lambda (no captured state) to keep the call allocation-free. --- ## Commands ```csharp [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); ``` Reach for manual `RelayCommand` / `AsyncRelayCommand` constructors only when you must own the command's lifetime explicitly or compose it from non-trivial sources. The attribute style covers ~95% of cases. See [`references/relaycommand-cookbook.md`](references/relaycommand-cookbook.md) for sync / async / cancellable / concurrency / error-surfacing recipes. --- ## Base class selection | Base class | Use when | |------------|---------| | `ObservableObject` | Default. `INotifyPropertyChanged` + `INotifyPropertyChanging` + `SetProperty` overloads + `SetPropertyAndNotifyOnCompletion` for `Task` properties | | `ObservableValidator` | The VM needs `INotifyDataErrorInfo` (forms, settings input) | | `ObservableRecipient` | The VM sends or receives `IMessenger` messages — see the **`mvvm-toolkit-messenger`** skill | C# is single-inheritance: `ObservableValidator` and `ObservableRecipient` both extend `ObservableObject`, so combining them requires composition (e.g., inject `IMessenger` into an `ObservableValidator`). --- ## Validation ```csharp 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; // submit... } } ``` Other entry points: `TrySetProperty`, `ValidateProperty(value, name)`, `ClearAllErrors()`, `GetErrors(propertyName)`. Custom rules support `[CustomValidation]` methods and custom `ValidationAttribute` subclasses. See [`references/validation.md`](references/validation.md) for the full validator surface area. --- ## Top pitfalls 1. **Forgetting `partial`.** Class (and every enclosing type) must be `partial`. Compile error `MVVMTK0008` / `MVVMTK0042`. 2. **PascalCase field name.** `[ObservableProperty] private string Name;` collides with the generated property. Use `name`, `_name`, or `m_name`. 3. **`async void` on `[RelayCommand]`.** The generator only wraps `Task`-returning methods as `IAsyncRelayCommand`. `async void` becomes a sync `RelayCommand` and exceptions are unobserved. Always return `Task`. 4. **Forgetting `[NotifyCanExecuteChangedFor]`.** The Save button stays disabled even though `CanSave()` would now return `true`. 5. **Mutating the same reference held by an `[ObservableProperty]` field.** `EqualityComparer<T>.Default` returns `true`, no notification fires. Replace the instance instead of mutating it. For the full diagnostic table (`MVVMTK0xxx`) and more pitfalls, see [`references/troubleshooting.md`](references/troubleshooting.md). --- ## End-to-end mini walkthrough A two-pane Notes app demonstrating generators + commands + `[NotifyCanExecuteChangedFor]`: ```csharp 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); } ``` For the full sample (DI wiring, View code-behind, XAML, unit tests), see [`references/end-to-end-walkthrough.md`](references/end-to-end-walkthrough.md). --- ## References & companion skills | Topic | Where | |-------|-------| | Source generator attribute reference | [`references/source-generators.md`](references/source-generators.md) | | RelayCommand recipes | [`references/relaycommand-cookbook.md`](references/relaycommand-cookbook.md) | | Validation deep dive | [`references/validation.md`](references/validation.md) | | Full Notes-app walkthrough | [`references/end-to-end-walkthrough.md`](references/end-to-end-walkthrough.md) | | `MVVMTK0xxx` diagnostics & pitfalls | [`references/troubleshooting.md`](references/troubleshooting.md) | | **Messenger pub/sub** | Companion skill: **`mvvm-toolkit-messenger`** | | **`Microsoft.Extensions.DependencyInjection` wiring** | Companion skill: **`mvvm-toolkit-di`** | External sources: - Toolkit overview: <https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/> - WinUI MVVM Toolkit tutorial: <https://learn.microsoft.com/en-us/windows/apps/tutorials/winui-mvvm-toolkit/intro> - Source: <https://github.com/CommunityToolkit/dotnet> - Samples: <https://github.com/CommunityToolkit/MVVM-Samples>
GitHub에서 보기