WPFMVVM架构脚手架生成器Skill wpf-mvvm-scaffold

这是一个用于自动生成 WPF 桌面应用程序 MVVM 架构基础代码的技能。它能快速创建 ViewModelBase、RelayCommand、依赖注入、导航服务等核心组件,帮助开发者搭建符合 MVVM 设计模式的、可维护的、生产就绪的 WPF 应用程序框架。关键词:WPF, MVVM, 架构设计, 代码生成, .NET, C#, 依赖注入, 导航服务, ViewModel, 桌面开发。

架构设计 0 次安装 0 次浏览 更新于 2/25/2026

名称: wpf-mvvm-脚手架 description: 生成包含 ViewModelBase、RelayCommand、INotifyPropertyChanged 和依赖注入设置的 WPF MVVM 架构 允许使用的工具: Read, Write, Edit, Bash, Glob, Grep tags: [wpf, mvvm, dotnet, csharp, 架构]

wpf-mvvm-脚手架

生成包含 ViewModelBase、RelayCommand、INotifyPropertyChanged 实现和依赖注入设置的 WPF MVVM 架构脚手架。此技能为 WPF 应用程序创建生产就绪的 MVVM 基础。

功能

  • 生成包含 INotifyPropertyChanged 的 ViewModelBase
  • 创建 RelayCommand/AsyncRelayCommand 实现
  • 使用 Microsoft.Extensions.DependencyInjection 设置依赖注入
  • 生成导航服务模式
  • 创建消息/事件聚合器
  • 设置设计时数据支持
  • 为 ViewModels 生成单元测试脚手架
  • 配置 MVVM 工具包集成

输入模式

{
  "type": "object",
  "properties": {
    "projectPath": {
      "type": "string",
      "description": "WPF 项目路径"
    },
    "projectName": {
      "type": "string",
      "description": "项目名称"
    },
    "mvvmFramework": {
      "enum": ["custom", "mvvm-toolkit", "prism", "caliburn"],
      "default": "mvvm-toolkit"
    },
    "features": {
      "type": "array",
      "items": {
        "enum": ["navigation", "messenger", "validation", "dialogs", "design-time"]
      },
      "default": ["navigation", "validation"]
    },
    "diFramework": {
      "enum": ["microsoft-di", "autofac", "ninject"],
      "default": "microsoft-di"
    },
    "generateViewModels": {
      "type": "array",
      "items": { "type": "string" },
      "description": "要生成的初始 ViewModels"
    }
  },
  "required": ["projectPath", "projectName"]
}

输出模式

{
  "type": "object",
  "properties": {
    "success": { "type": "boolean" },
    "files": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "path": { "type": "string" },
          "type": { "enum": ["base", "viewmodel", "service", "command"] }
        }
      }
    },
    "nugetPackages": {
      "type": "array",
      "items": { "type": "string" }
    }
  },
  "required": ["success"]
}

项目结构

MyApp/
├── App.xaml
├── App.xaml.cs
├── ViewModels/
│   ├── Base/
│   │   ├── ViewModelBase.cs
│   │   └── RelayCommand.cs
│   ├── MainViewModel.cs
│   ├── ShellViewModel.cs
│   └── Settings/
│       └── SettingsViewModel.cs
├── Views/
│   ├── MainView.xaml
│   ├── ShellView.xaml
│   └── Settings/
│       └── SettingsView.xaml
├── Services/
│   ├── INavigationService.cs
│   ├── NavigationService.cs
│   ├── IDialogService.cs
│   └── DialogService.cs
├── Models/
│   └── ...
└── Infrastructure/
    ├── Bootstrapper.cs
    ├── ServiceLocator.cs
    └── Messenger.cs

生成代码示例

ViewModelBase.cs

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace MyApp.ViewModels.Base;

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
            return false;

        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected bool SetProperty<T>(ref T field, T value, Action onChanged,
        [CallerMemberName] string? propertyName = null)
    {
        if (SetProperty(ref field, value, propertyName))
        {
            onChanged?.Invoke();
            return true;
        }
        return false;
    }

    // 设计时支持
    public static bool IsInDesignMode =>
        DesignerProperties.GetIsInDesignMode(new DependencyObject());
}

RelayCommand.cs

using System.Windows.Input;

namespace MyApp.ViewModels.Base;

public class RelayCommand : ICommand
{
    private readonly Action<object?> _execute;
    private readonly Predicate<object?>? _canExecute;

    public RelayCommand(Action<object?> execute, Predicate<object?>? canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public RelayCommand(Action execute, Func<bool>? canExecute = null)
        : this(_ => execute(), canExecute != null ? _ => canExecute() : null)
    {
    }

    public event EventHandler? CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    public bool CanExecute(object? parameter) => _canExecute?.Invoke(parameter) ?? true;

    public void Execute(object? parameter) => _execute(parameter);

    public void RaiseCanExecuteChanged() => CommandManager.InvalidateRequerySuggested();
}

public class AsyncRelayCommand : ICommand
{
    private readonly Func<object?, Task> _execute;
    private readonly Predicate<object?>? _canExecute;
    private bool _isExecuting;

    public AsyncRelayCommand(Func<object?, Task> execute, Predicate<object?>? canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public AsyncRelayCommand(Func<Task> execute, Func<bool>? canExecute = null)
        : this(_ => execute(), canExecute != null ? _ => canExecute() : null)
    {
    }

    public event EventHandler? CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    public bool CanExecute(object? parameter) =>
        !_isExecuting && (_canExecute?.Invoke(parameter) ?? true);

    public async void Execute(object? parameter)
    {
        if (!CanExecute(parameter)) return;

        _isExecuting = true;
        RaiseCanExecuteChanged();

        try
        {
            await _execute(parameter);
        }
        finally
        {
            _isExecuting = false;
            RaiseCanExecuteChanged();
        }
    }

    public void RaiseCanExecuteChanged() => CommandManager.InvalidateRequerySuggested();
}

MainViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace MyApp.ViewModels;

public partial class MainViewModel : ViewModelBase
{
    private readonly INavigationService _navigationService;
    private readonly IDataService _dataService;

    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(SaveCommand))]
    private string _title = string.Empty;

    [ObservableProperty]
    private bool _isLoading;

    [ObservableProperty]
    private ObservableCollection<ItemViewModel> _items = new();

    public MainViewModel(INavigationService navigationService, IDataService dataService)
    {
        _navigationService = navigationService;
        _dataService = dataService;

        if (IsInDesignMode)
        {
            LoadDesignTimeData();
        }
    }

    public ICommand SaveCommand => new RelayCommand(
        async () => await SaveAsync(),
        () => !string.IsNullOrEmpty(Title) && !IsLoading);

    public ICommand NavigateToSettingsCommand => new RelayCommand(
        () => _navigationService.NavigateTo<SettingsViewModel>());

    private async Task SaveAsync()
    {
        IsLoading = true;
        try
        {
            await _dataService.SaveAsync(Title);
        }
        finally
        {
            IsLoading = false;
        }
    }

    public async Task LoadDataAsync()
    {
        IsLoading = true;
        try
        {
            var data = await _dataService.GetItemsAsync();
            Items = new ObservableCollection<ItemViewModel>(data.Select(d => new ItemViewModel(d)));
        }
        finally
        {
            IsLoading = false;
        }
    }

    private void LoadDesignTimeData()
    {
        Title = "设计时标题";
        Items = new ObservableCollection<ItemViewModel>
        {
            new("项目 1"),
            new("项目 2"),
            new("项目 3")
        };
    }
}

App.xaml.cs with DI

using Microsoft.Extensions.DependencyInjection;

namespace MyApp;

public partial class App : Application
{
    private readonly IServiceProvider _serviceProvider;

    public App()
    {
        var services = new ServiceCollection();
        ConfigureServices(services);
        _serviceProvider = services.BuildServiceProvider();
    }

    private void ConfigureServices(IServiceCollection services)
    {
        // 服务
        services.AddSingleton<INavigationService, NavigationService>();
        services.AddSingleton<IDialogService, DialogService>();
        services.AddTransient<IDataService, DataService>();

        // ViewModels
        services.AddTransient<MainViewModel>();
        services.AddTransient<SettingsViewModel>();
        services.AddSingleton<ShellViewModel>();

        // 视图
        services.AddTransient<MainView>();
        services.AddTransient<SettingsView>();
        services.AddSingleton<ShellView>();
    }

    protected override void OnStartup(StartupEventArgs e)
    {
        var shell = _serviceProvider.GetRequiredService<ShellView>();
        shell.DataContext = _serviceProvider.GetRequiredService<ShellViewModel>();
        shell.Show();

        base.OnStartup(e);
    }
}

NavigationService.cs

namespace MyApp.Services;

public interface INavigationService
{
    void NavigateTo<TViewModel>() where TViewModel : ViewModelBase;
    void NavigateTo<TViewModel>(object parameter) where TViewModel : ViewModelBase;
    void GoBack();
    bool CanGoBack { get; }
}

public class NavigationService : ViewModelBase, INavigationService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly Stack<ViewModelBase> _navigationStack = new();

    public NavigationService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    private ViewModelBase? _currentViewModel;
    public ViewModelBase? CurrentViewModel
    {
        get => _currentViewModel;
        private set => SetProperty(ref _currentViewModel, value);
    }

    public bool CanGoBack => _navigationStack.Count > 1;

    public void NavigateTo<TViewModel>() where TViewModel : ViewModelBase
    {
        NavigateTo<TViewModel>(null);
    }

    public void NavigateTo<TViewModel>(object? parameter) where TViewModel : ViewModelBase
    {
        var viewModel = _serviceProvider.GetRequiredService<TViewModel>();

        if (viewModel is INavigationAware navigationAware)
        {
            navigationAware.OnNavigatedTo(parameter);
        }

        if (CurrentViewModel is INavigationAware currentNavigationAware)
        {
            currentNavigationAware.OnNavigatedFrom();
        }

        _navigationStack.Push(viewModel);
        CurrentViewModel = viewModel;
    }

    public void GoBack()
    {
        if (!CanGoBack) return;

        if (CurrentViewModel is INavigationAware currentNavigationAware)
        {
            currentNavigationAware.OnNavigatedFrom();
        }

        _navigationStack.Pop();
        CurrentViewModel = _navigationStack.Peek();

        if (CurrentViewModel is INavigationAware navigationAware)
        {
            navigationAware.OnNavigatedTo(null);
        }
    }
}

NuGet 包

<ItemGroup>
  <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
  <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>

最佳实践

  1. 保持 ViewModels 与 UI 无关:不引用 WPF 类型
  2. 使用异步命令:用于长时间运行的操作
  3. 实现 INotifyDataErrorInfo:用于验证
  4. 设计时数据:支持 Blend/VS 设计器
  5. 单一职责:一个 View 对应一个 ViewModel
  6. 单元测试 ViewModels:模拟服务

相关技能

  • wpf-xaml-style-generator - UI 样式
  • msix-package-generator - 打包
  • desktop-unit-testing 流程 - 测试

相关代理

  • wpf-dotnet-expert - WPF 专业知识
  • architecture-pattern-advisor - MVVM 模式