wpf-mvvm-scaffold
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.
Capabilities
- Generate ViewModelBase with INotifyPropertyChanged
- Create RelayCommand/AsyncRelayCommand implementations
- Set up dependency injection with Microsoft.Extensions.DependencyInjection
- Generate navigation service pattern
- Create messenger/event aggregator
- Set up design-time data support
- Generate unit test scaffolding for ViewModels
- Configure MVVM toolkit integration
Input Schema
{
"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"]
}
Output Schema
{
"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"]
}
Project Structure
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
Generated Code Examples
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;
}
// Design-time support
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 = "Design Time Title";
Items = new ObservableCollection<ItemV