Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Generate WPF MVVM architecture with ViewModelBase, RelayCommand, INotifyPropertyChanged, and dependency injection setup
.claude/skills/a5c-ai-wpf-mvvm-scaffold/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 54% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 10% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 97% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 77% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 69% | 0% |
Generate WPF MVVM architecture scaffolding with ViewModelBase, RelayCommand, INotifyPropertyChanged implementation, and dependency injection setup. This skill creates a production-ready MVVM foundation for WPF applications.
json{ "type": "object", "properties": { "projectPath": { "type": "string", "description": "Path to the WPF project" }, "projectName": { "type": "string", "description": "Project name" }, "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": "Initial ViewModels to generate" } }, "required": ["projectPath", "projectName"] }
json{ "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.cscsharpusing 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; } // Design-time support public static bool IsInDesignMode => DesignerProperties.GetIsInDesignMode(new DependencyObject()); }
csharpusing 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(); }
csharpusing 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 = "Design Time Title"; Items = new ObservableCollection<ItemViewModel> { new("Item 1"), new("Item 2"), new("Item 3") }; } }
csharpusing 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 services.AddSingleton<INavigationService, NavigationService>(); services.AddSingleton<IDialogService, DialogService>(); services.AddTransient<IDataService, DataService>(); // ViewModels services.AddTransient<MainViewModel>(); services.AddTransient<SettingsViewModel>(); services.AddSingleton<ShellViewModel>(); // Views 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); } }
csharpnamespace 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); } } }
xml<ItemGroup> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> </ItemGroup>
wpf-xaml-style-generator - UI stylingmsix-package-generator - Packagingdesktop-unit-testing process - Testingwpf-dotnet-expert - WPF expertisearchitecture-pattern-advisor - MVVM patterns| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 18,412 | 16,886 | -8% | 1 | 1 | 0% | 4,839 | 7,442 | +54% | 0 | 0 | — |
case-02 | fail→pass | 24,691 | 14,964 | -39% | 1 | 1 | 0% | 6,223 | 6,874 | +10% | 0 | 0 | — |
case-03 | fail→pass | 15,855 | 18,119 | +14% | 1 | 1 | 0% | 3,940 | 7,756 | +97% | 0 | 0 | — |
case-04 | pass→pass | 9,637 | 7,875 | -18% | 1 | 1 | 0% | 1,981 | 4,793 | +142% | 0 | 0 | — |
case-05 | fail→pass | 12,707 | 9,058 | -29% | 1 | 1 | 0% | 2,651 | 4,698 | +77% | 0 | 0 | — |
case-06 | pass→pass | 12,256 | 9,060 | -26% | 1 | 1 | 0% | 2,519 | 4,710 | +87% | 0 | 0 | — |
case-07 | pass→pass | 14,920 | 9,729 | -35% | 1 | 1 | 0% | 3,214 | 5,175 | +61% | 0 | 0 | — |
case-08 | fail→fail | 15,979 | 12,602 | -21% | 1 | 1 | 0% | 3,375 | 5,765 | +71% | 0 | 0 | — |
case-09 | pass→pass | 14,094 | 13,430 | -5% | 1 | 1 | 0% | 2,806 | 5,270 | +88% | 0 | 0 | — |
case-10 | pass→pass | 11,248 | 9,430 | -16% | 1 | 1 | 0% | 2,331 | 5,070 | +118% | 0 | 0 | — |
case-11 | pass→pass | 11,461 | 12,226 | +7% | 1 | 1 | 0% | 2,304 | 5,454 | +137% | 0 | 0 | — |
case-12 | pass→pass | 5,108 | 2,257 | -56% | 1 | 1 | 0% | 969 | 3,492 | +260% | 0 | 0 | — |
case-13 | fail→pass | 11,203 | 3,889 | -65% | 1 | 1 | 0% | 2,317 | 3,908 | +69% | 0 | 0 | — |
case-14 | pass→pass | 11,104 | 8,324 | -25% | 1 | 1 | 0% | 2,277 | 4,690 | +106% | 0 | 0 | — |
case-15 | fail→pass | 12,952 | 8,822 | -32% | 1 | 1 | 0% | 2,397 | 4,689 | +96% | 0 | 0 | — |
case-16 | pass→pass | 9,467 | 4,140 | -56% | 1 | 1 | 0% | 1,893 | 3,764 | +99% | 0 | 0 | — |
case-17 | pass→pass | 11,820 | 6,159 | -48% | 1 | 1 | 0% | 1,951 | 4,450 | +128% | 0 | 0 | — |
case-18 | pass→pass | 11,382 | 7,415 | -35% | 1 | 1 | 0% | 2,281 | 4,635 | +103% | 0 | 0 | — |
case-19 | pass→pass | 14,321 | 7,499 | -48% | 1 | 1 | 0% | 3,424 | 4,677 | +37% | 0 | 0 | — |
case-20 | pass→pass | 12,244 | 12,002 | -2% | 1 | 1 | 0% | 2,573 | 5,474 | +113% | 0 | 0 | — |
case-21 | pass→pass | 11,380 | 10,656 | -6% | 1 | 1 | 0% | 2,252 | 5,524 | +145% | 0 | 0 | — |
case-22 | fail→pass | 5,810 | 4,194 | -28% | 1 | 1 | 0% | 935 | 3,769 | +303% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.