mcpField
M
mvvm-toolkit

mvvm-toolkit

github.com/github开发40.5k92 次安装今天更新
代码仓库

关于 mvvm-toolkit

mvvm-toolkit 是为使用 CommunityToolkit.Mvvm 8.x 的应用开发者准备的技能,涵盖 ViewModel、属性、命令和验证。它说明源生成器的用法、基类选择及常见问题。

主要功能

  1. 01用特性生成可观察属性
  2. 02生成同步与异步命令
  3. 03支持属性与命令通知
  4. 04涵盖表单数据验证
  5. 05提供诊断与常见问题指导

适用场景

  1. 01为 ViewModel 添加可观察属性
  2. 02为操作创建同步或异步命令
  3. 03构建带输入验证的表单
  4. 04检查 CommunityToolkit.Mvvm 实现

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/mvvm-toolkit确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
end-to-end-walkthrough.md
relaycommand-cookbook.md
source-generators.md
troubleshooting.md
validation.md

详细介绍

CommunityToolkit.Mvvm(核心)

在使用 CommunityToolkit.Mvvm 8.x 的应用中编写或审查 ViewModel、属性、命令或验证时,请使用此技能。

配套技能。 加载 mvvm-toolkit-messenger 以了解 IMessenger 发布/订阅模式。加载 mvvm-toolkit-di 以了解 Microsoft.Extensions.DependencyInjection 集成。

快速回顾。 在 partial 类的私有字段上使用 [ObservableProperty];在实例方法上使用 [RelayCommand];继承 ObservableObject(或对输入表单继承 ObservableValidator,在使用 IMessenger 时继承 ObservableRecipient)。


包与设置

<ItemGroup>
  <PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" />
</ItemGroup>

目标框架:netstandard2.0、netstandard2.1、net6.0+。可在 .NET、.NET Framework、Mono 上运行。源生成器随同一个 NuGet 包提供——无需额外的分析器引用。

命名空间:

using CommunityToolkit.Mvvm.ComponentModel;   // ObservableObject, [ObservableProperty]
using CommunityToolkit.Mvvm.Input;             // [RelayCommand], RelayCommand, AsyncRelayCommand

通用规则。 每个使用 [ObservableProperty] 或 [RelayCommand] 的类型——以及每个外层类型(如果是嵌套的)——都必须声明为 partial。否则,生成器会报出 MVVMTK0008 / MVVMTK0042。


源生成器速查表

特性应用于生成内容
[ObservableProperty]私有字段公共 INotifyPropertyChanged 属性 + OnXxxChanging/OnXxxChanged 分部方法钩子
[NotifyPropertyChangedFor(nameof(Other))]可观察字段同时为所列属性触发 PropertyChanged
[NotifyCanExecuteChangedFor(nameof(MyCommand))]可观察字段在变更时调用 MyCommand.NotifyCanExecuteChanged()
[NotifyDataErrorInfo]ObservableValidator 上的可观察字段在 setter 中调用 ValidateProperty(value)
[NotifyPropertyChangedRecipients]ObservableRecipient 上的可观察字段变更后调用 Broadcast(old, new)
[RelayCommand]实例方法以 IRelayCommand / IAsyncRelayCommand 形式公开的惰性 RelayCommand / AsyncRelayCommand
[RelayCommand(CanExecute = nameof(CanX))]实例方法将 CanExecute 连接到方法或属性
[RelayCommand(IncludeCancelCommand = true)]带 CancellationToken 的异步方法同时生成 XxxCancelCommand
[RelayCommand(AllowConcurrentExecutions = true)]异步方法允许排队/并行调用(默认在运行期间禁用)
[RelayCommand(FlowExceptionsToTaskScheduler = true)]异步方法通过 ExecutionTask 呈现异常,而不是 await 并重新抛出
[property: SomeAttr]可观察字段或 [RelayCommand] 方法将 SomeAttr 转发到生成的属性上(例如 [JsonIgnore])

命名。 字段 name / _name / m_name → Name。方法 LoadAsync → LoadCommand(Async 后缀会被去除;前导 On 也会被去除)。

有关完整特性参考及生成代码示例,请参阅 references/source-generators.md。


ViewModel 模式

简单的可观察属性
public partial class ContactViewModel : ObservableObject
{
    [ObservableProperty]
    private string? name;
}
钩子:OnXxxChanging / OnXxxChanged
[ObservableProperty]
private string? name;

partial void OnNameChanged(string? value) =>
    Logger.LogInformation("Name changed to {Name}", value);

单参数 (value) 和双参数 (oldValue, newValue) 重载均可用。只需实现你需要的那些;未实现的钩子会被编译器省略(零运行时开销)。

依赖属性 + 依赖命令
[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();
包装非可观察模型
public sealed class ObservableUser(User user) : ObservableObject
{
    public string Name
    {
        get => user.Name;
        set => SetProperty(user.Name, value, user, (u, n) => u.Name = n);
    }
}

传递静态 lambda(不捕获状态)以保持调用无分配。


命令

[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);

仅当你必须显式拥有命令的生命周期或从非平凡来源组合它时,才使用手动 RelayCommand / AsyncRelayCommand 构造函数。特性风格覆盖了约 95% 的情况。

有关同步 / 异步 / 可取消 / 并发 / 错误呈现的配方,请参阅 references/relaycommand-cookbook.md。


基类选择

基类适用场景
ObservableObject默认选择。INotifyPropertyChanged + INotifyPropertyChanging + SetProperty 重载 + 用于 Task 属性的 SetPropertyAndNotifyOnCompletion
ObservableValidatorVM 需要 INotifyDataErrorInfo(表单、设置输入)
ObservableRecipientVM 发送或接收 IMessenger 消息 — 参见 mvvm-toolkit-messenger 技能

C# 是单继承:ObservableValidator 和 ObservableRecipient 都继承自 ObservableObject,因此组合它们需要采用组合方式 (例如,将 IMessenger 注入到 ObservableValidator 中)。


验证

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...
    }
}

其他入口点:TrySetProperty、ValidateProperty(value, name)、 ClearAllErrors()、GetErrors(propertyName)。自定义规则支持 [CustomValidation] 方法和自定义 ValidationAttribute 子类。

有关验证器的完整接口范围,请参见 references/validation.md。


常见陷阱

  1. 忘记 partial。 类(以及每一个外层类型)都必须是 partial。否则会出现编译错误 MVVMTK0008 / MVVMTK0042。
  2. 字段名使用 PascalCase。 [ObservableProperty] private string Name; 会与生成的属性冲突。请使用 name、_name 或 m_name。
  3. 在 [RelayCommand] 上使用 async void。 生成器只会将 返回 Task 的方法包装为 IAsyncRelayCommand。async void 会变成 同步的 RelayCommand,异常也不会被观察到。请始终返回 Task。
  4. 忘记 [NotifyCanExecuteChangedFor]。 即使 CanSave() 现在会返回 true,Save 按钮仍保持禁用。
  5. 修改 [ObservableProperty] 字段持有的同一引用。 EqualityComparer<T>.Default 返回 true,不会触发通知。 请替换该实例,而不是修改它。

有关完整的诊断表(MVVMTK0xxx)和更多陷阱,请参见 references/troubleshooting.md。


端到端迷你演练

一个双窗格 Notes 应用,演示生成器 + 命令 + [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);
}

有关完整示例(DI 接线、View 代码隐藏、XAML、单元测试),请参见 references/end-to-end-walkthrough.md。


参考资料与配套技能

主题位置
源生成器特性参考references/source-generators.md
RelayCommand 配方references/relaycommand-cookbook.md
验证深入解析references/validation.md
完整 Notes 应用演练references/end-to-end-walkthrough.md
MVVMTK0xxx 诊断与陷阱references/troubleshooting.md
Messenger 发布/订阅配套技能:mvvm-toolkit-messenger
Microsoft.Extensions.DependencyInjection 接线配套技能:mvvm-toolkit-di

外部来源:

评论

支持 Markdown