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
<PackageReference Include="SimpleViewModel" Version="0.9.7" />
- Create a ViewModel:
using SimpleViewModel;
using SimpleViewModel.BaseClasses;
[ViewModel]
public partial class MainViewModel
{
[Command]
public void SaveData()
{
// Your save logic here
MessageBox.Show("Data saved!");
}
[Command]
public void LoadData()
{
// Your load logic here
MessageBox.Show("Data loaded!");
}
}
- Bind to XAML:
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Button Content="Save" Command="{Binding SaveDataCommand}" />
<Button Content="Load" Command="{Binding LoadDataCommand}" />
</StackPanel>
</Window>
- Set DataContext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
That's it! The source generator automatically creates SaveDataCommand
and LoadDataCommand
properties for you.
Use the [Bind]
attribute to automatically generate observable properties:
[ViewModel]
public partial class UserViewModel
{
[Bind]
private string _firstName = "";
[Bind]
private string _lastName = "";
[Bind]
private int _age;
}
Generated code includes proper INotifyPropertyChanged
implementation:
public string FirstName
{
get => _firstName;
set => SetProperty(ref _firstName, value);
}
Add custom logic when properties change:
[ViewModel]
public partial class UserViewModel
{
[Bind(OnChangeMethodName = nameof(OnNameChanged))]
private string _name = "";
private void OnNameChanged()
{
// Custom logic when name changes
Console.WriteLine($"Name changed to: {_name}");
}
}
Commands can include conditional execution logic:
[ViewModel]
public partial class DocumentViewModel
{
[Bind]
private string _selectedItem = "";
[Command(CanExecuteMethodName = nameof(CanDelete))]
public void Delete()
{
// Delete logic here
Console.WriteLine($"Deleting {_selectedItem}");
}
public bool CanDelete()
{
return !string.IsNullOrEmpty(_selectedItem);
}
}
Commands automatically support parameters through the Execute method:
[ViewModel]
public partial class DocumentViewModel
{
[Command]
public void DeleteItem()
{
// 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
INotifyPropertyChanged
support for[Bind]
fields
All generated code is available in IntelliSense and can be debugged normally.
Your Code:
[ViewModel]
public partial class MyViewModel
{
[Command]
public void DoSomething() => Console.WriteLine("Done!");
}
Generated ViewModel Extension:
public partial class MyViewModel : BaseViewModel
{
private Command_DoSomething? _DoSomethingCommand { get; set; }
public Command_DoSomething DoSomethingCommand => _DoSomethingCommand ??= new(this);
}
Generated Command Class:
public sealed class Command_DoSomething : BaseCommand
{
private readonly MyViewModel vm;
public Command_DoSomething(MyViewModel vm)
{
this.vm = vm;
}
public override void Execute(object? parameter)
{
vm.DoSomething();
}
}
- Use Partial Classes: Always mark your view models as
partial
to 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.,_title
becomesTitle
property) - Async Commands: For async operations, use
async void
in 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
CanExecuteMethodName
exists 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