A lightweight WPF ViewModel framework with automatic source generation that eliminates boilerplate code while maintaining full control over your view models.
- Zero Boilerplate Commands: Transform methods into ICommand properties with a simple
[Command]attribute - Automatic Property Binding: Generate observable properties from fields using
[Bind]attribute - Source Generation: All code generation happens at compile time with no runtime reflection
- Lightweight: Minimal dependencies and overhead
- Type Safe: Full IntelliSense support and compile-time validation
- WPF Optimized: Built specifically for WPF applications with proper CommandManager integration
- Quick Start
- Advanced Features
- How It Works
- Generated Code Example
- Best Practices
- Troubleshooting
- Requirements
- Contributing
<PackageReferenceInclude="SimpleViewModel"Version="0.9.7" />- Create a ViewModel:
usingSimpleViewModel;usingSimpleViewModel.BaseClasses;[ViewModel]publicpartialclassMainViewModel{[Command]publicvoidSaveData(){// Your save logic hereMessageBox.Show("Data saved!");}[Command]publicvoidLoadData(){// Your load logic hereMessageBox.Show("Data loaded!");}}- Bind to XAML:
<Windowx:Class="MyApp.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<ButtonContent="Save"Command="{Binding SaveDataCommand}" />
<ButtonContent="Load"Command="{Binding LoadDataCommand}" />
</StackPanel>
</Window>- Set DataContext:
publicpartialclassMainWindow:Window{publicMainWindow(){InitializeComponent();DataContext=newMainViewModel();}}That's it! The source generator automatically creates SaveDataCommand and LoadDataCommand properties for you.
Use the [Bind] attribute to automatically generate observable properties:
[ViewModel]publicpartialclassUserViewModel{[Bind]privatestring_firstName="";[Bind]privatestring_lastName="";[Bind]privateint_age;}Generated code includes proper INotifyPropertyChanged implementation:
publicstringFirstName{
get =>_firstName;
set =>SetProperty(ref_firstName,value);}Add custom logic when properties change:
[ViewModel]publicpartialclassUserViewModel{[Bind(OnChangeMethodName=nameof(OnNameChanged))]privatestring_name="";privatevoidOnNameChanged(){// Custom logic when name changesConsole.WriteLine($"Name changed to: {_name}");}}Commands can include conditional execution logic:
[ViewModel]publicpartialclassDocumentViewModel{[Bind]privatestring_selectedItem="";[Command(CanExecuteMethodName=nameof(CanDelete))]publicvoidDelete(){// Delete logic hereConsole.WriteLine($"Deleting {_selectedItem}");}publicboolCanDelete(){return!string.IsNullOrEmpty(_selectedItem);}}Commands automatically support parameters through the Execute method:
[ViewModel]publicpartialclassDocumentViewModel{[Command]publicvoidDeleteItem(){// Note: Parameter handling is done in the generated command class// The method itself doesn't need to accept parameters}}SimpleViewModel uses Roslyn source generators to analyze your code at compile time and automatically generate:
- Partial ViewModel Class: Extends your class to inherit from
BaseViewModel - Command Classes: Each
[Command]method gets a corresponding command class that inherits fromBaseCommand - Command Properties: Properties that expose the commands for data binding (lazily initialized)
- Observable Properties: Properties with
INotifyPropertyChangedsupport for[Bind]fields
All generated code is available in IntelliSense and can be debugged normally.
Your Code:
[ViewModel]publicpartialclassMyViewModel{[Command]publicvoidDoSomething()=>Console.WriteLine("Done!");}Generated ViewModel Extension:
publicpartialclassMyViewModel:BaseViewModel{privateCommand_DoSomething?_DoSomethingCommand{get;set;}publicCommand_DoSomethingDoSomethingCommand=>_DoSomethingCommand??=new(this);}Generated Command Class:
publicsealedclassCommand_DoSomething:BaseCommand{privatereadonlyMyViewModelvm;publicCommand_DoSomething(MyViewModelvm){this.vm=vm;}publicoverridevoidExecute(object?parameter){vm.DoSomething();}}- Use Partial Classes: Always mark your view models as
partialto allow source generation - Apply ViewModel Attribute: Use
[ViewModel]on your class - the generator automatically makes it inherit fromBaseViewModel - Field Naming: Use underscore prefixes for fields marked with
[Bind](e.g.,_titlebecomesTitleproperty) - Async Commands: For async operations, use
async voidin command methods - Parameter Validation: Commands receive parameters through the
Execute(object? parameter)method in the generated class - Dependency Injection: Use constructor injection for services and dependencies
If the source generator isn't creating commands:
- Ensure you have the
[ViewModel]attribute on your class - Make sure the class is marked as
partial - Check that methods have the
[Command]attribute and are public - Verify fields have the
[Bind]attribute for observable properties - Clean and rebuild your solution
If commands aren't appearing in XAML IntelliSense:
- Rebuild the project to trigger source generation
- Check that the generated files are created (enable
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>to see them) - Ensure proper namespace imports in XAML
- Fields not generating properties: Make sure fields are marked with
[Bind]and are private - Commands not working: Ensure methods are public and marked with
[Command] - CanExecute not working: Verify the method name in
CanExecuteMethodNameexists and is public with bool return type
- .NET 8.0 or .NET 9.0 (Windows targets only)
- Windows (WPF applications only)
- C# 10.0 or later
MIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit issues and pull requests.
- 🏠 GitHub: https://github.com/DerekGooding/SimpleViewModel
- 📦 NuGet: https://www.nuget.org/packages/SimpleViewModel/
- 🐛 Issues: https://github.com/DerekGooding/SimpleViewModel/issues
Built with ❤️ for the WPF community