This document provides guidance for AI agents and contributors working on the RFBCodeWorks.MvvmControls repository.
MvvmControls is a WPF MVVM helper library that consolidates boiler-plate ViewModel interactions for common WPF controls into pre-built classes. It ships alongside a set of Roslyn source generators that reduce the amount of code a consumer needs to write.
The solution (RFBCodeWorks.Mvvm.sln) is organized into the following logical components:
| Component | Path / Projects | Status |
|---|---|---|
| Mvvm.Controls | src/Mvvm.Controls/ |
Primary library – active development |
| Source generators | src/SourceGenerators.Roslyn311/, src/SourceGenerators.Roslyn410/, src/SourceGenerators.Roslyn500/ |
Roslyn-version-specific generator projects – active development |
| Mvvm.IViewModel | src/Mvvm.IViewModel/ |
Interface-only library – largely complete |
| Mvvm.Dialogs | src/Mvvm.Dialogs/ |
Dialog helpers – largely complete |
| Mvvm.WebView2Integration | src/Mvvm.WebView2Integration/ |
WebView2 helpers – largely complete |
| ExampleWPF | src/ExampleWPF/ |
Demo application – reference only |
| TestWebView2 | src/TestWebView2/ |
WebView2 sample/test application |
| Mvvm.Controls.Tests | tests/Mvvm.Controls.Tests/ |
Test project |
Focus your work on
Mvvm.Controlsand theSourceGenerators.Roslyn311/410/500projects. The other libraries are considered feature-complete and should not need significant changes.
- .NET SDK 8.0 and 10.0 (both required — CI installs both via
actions/setup-dotnet) - Windows Desktop runtime (WPF). Tests cannot run on Linux/macOS.
dotnet build -p:EnableWindowsTargeting=trueEnableWindowsTargeting=true is required when building on non-Windows hosts (e.g. Linux CI agents running without the Windows Desktop workload).
dotnet testTests target net48, net8.0-windows, and net10.0-windows. Each TFM exercises a different Roslyn version for the source generators.
dotnet pack ./src/Mvvm.Controls/Mvvm.Controls.csproj --configuration ReleaseThe GitHub Actions workflow (.github/workflows/build-and-test.yml) runs on windows-latest, restores, builds in Release, runs all tests, and (on pushes to master) packs and uploads NuGet artifacts.
Target frameworks: net462, net48, net8.0-windows, net10.0-windows.
Key namespaces and directories:
src/Mvvm.Controls/
├── Mvvm/
│ ├── Attributes.cs – All source-generator attributes ([Button], [Selector], [ComboBox], etc.)
│ ├── Primitives/ – Abstract base classes (ControlBase, CommandBase, SelectorDefinition, …)
│ ├── Specialized/ – Concrete specializations (DateTimePicker, TreeView items, TwoStateButton, …)
│ ├── ComponentModel/ – Additional observable/component model helpers
│ ├── DragAndDrop/ – Drag-and-drop helpers
│ ├── EventArgs/ – Custom EventArgs types
│ ├── Helpers/ – Utility helpers
│ ├── Input/ – ICommand implementations (RelayCommand, AsyncRelayCommand, …)
│ ├── XmlLinq/ – XDocument/XElement TreeView view-models
│ └── ViewModelBase.cs – Abstract base ViewModel (extends ObservableObject, implements IDisposable + IViewModel)
├── WPF/
│ ├── Behaviors/ – Attached behaviors (ControlDefinitions binding, MultiItemSelection, …)
│ ├── Controls/ – Custom WPF controls (NumericUpDown, IPv4 textbox, …)
│ └── Converters/ – WPF value converters
└── Themes/ – Resource dictionaries / control templates
Class hierarchy (primitives → concrete)
ObservableObject (CommunityToolkit.Mvvm)
└── ControlBase – IsEnabled, IsVisible, Visibility
└── ItemSource<T,E> – Items, DisplayMemberPath, ItemSourceChanged
└── SelectorDefinition<T,E,V> – SelectedItem, SelectedValue, SelectedValuePath
Command hierarchy:
AbstractCommand / AbstractCommand<T>
AbstractAsyncCommand / AbstractAsyncCommand<T>
AbstractButtonDefinition / AbstractButtonDefinition<T> (also ICommand)
AbstractAsyncButtonDefinition / AbstractAsyncButtonDefinition<T>
└── ButtonDefinition / AsyncButtonDefinition (sealed concrete)
RelayCommand / AsyncRelayCommand (sealed, thin wrappers)
The source generator code is compiled three times against different Roslyn SDK versions:
| Project file | Roslyn version | Used by VS/compiler |
|---|---|---|
SourceGenerators.Roslyn311.csproj |
3.11 | VS2019, .NET Framework / older toolchains |
SourceGenerators.Roslyn410.csproj |
4.10 | VS2022 (current) |
SourceGenerators.Roslyn500.csproj |
5.x | VS2026 / .NET 10 toolchain |
All three projects share the same source files under src/. Version-specific files use the naming convention *Roslyn311.cs / *Roslyn3*.cs (excluded from Roslyn4+ builds).
Source generator structure (one pattern repeated for each generator):
src/
├── ButtonGenerator/
│ ├── ButtonAttributeData.cs – Readonly struct holding parsed attribute data
│ ├── ButtonParser.cs – Roslyn syntax/semantic parsing
│ ├── ButtonEmitter.cs – Source code emission (SourceWriter calls)
│ ├── _ButtonGenerator.Roslyn311.cs – ISourceGenerator entry point (legacy)
│ └── _ButtonGenerator.Roslyn40.cs – IIncrementalGenerator entry point (preferred)
├── Refreshable/ – [Selector] / [ComboBox] / [ListBox] generators
├── TriggersRefresh/ – [TriggersRefresh] generator
└── IViewModelGenerator/ – [IViewModel] generator
Each generator follows the Parse → Data → Emit pattern:
- Parser –
NodeSelectorfilters candidate syntax nodes;GetInfoOrDiagnostictransforms them into an immutable data struct. - Data struct – An
IEquatablereadonly struct or record that holds all information needed for code generation (no Roslyn symbols at emit time). - Emitter – Produces source text strings via
SourceWriter.
DataOrDiagnostics<T> is the return type for parse results that may produce diagnostics instead of data.
Diagnostic IDs (defined in Diagnostics.cs):
| ID | Meaning |
|---|---|
RFB_MVVM_000 |
Unhandled generator exception |
RFB_MVVM_001 |
Unknown target framework |
RFB_MVVM_002 |
Unsupported language (non-C#) |
RFB_MVVM_003 |
C# language version too low |
RFB_MVVM_004 |
Containing class is not partial |
RFB_MVVM_005 |
Invalid method signature for [Button] |
RFB_MVVM_006 |
Return type does not implement IList<T> (for selector generators) |
RFB_MVVM_007 |
Unable to generate a fully-qualified name |
RFB_MVVM_008 |
Unable to determine collection type |
RFB_MVVM_009 |
[TriggersRefresh] used without [ObservableProperty] |
RFB_MVVM_010 |
[ObservableProperty] backing field is not private |
RFB_MVVM_011 |
Too many parameters on refreshable method |
RFB_MVVM_012 |
Async refreshable parameter must be CancellationToken or none |
RFB_MVVM_013 |
Class does not implement required interface |
All attributes live in the RFBCodeWorks.Mvvm namespace and are defined in src/Mvvm.Controls/Mvvm/Attributes.cs.
| Attribute | Target | Generates |
|---|---|---|
[IViewModel] |
Class | Adds IViewModel interface implementation |
[Button] |
Method | ButtonDefinition or AsyncButtonDefinition property |
[Selector] |
Method returning IList<T> |
RefreshableSelector<T,TList,TValue> property |
[ComboBox] |
Method returning IList<T> |
RefreshableComboBoxDefinition<T,TValue> property |
[ListBox] |
Method returning IList<T> |
RefreshableListBoxDefinition<T,TValue> property |
[OnSelectionChanged] |
Method (with selector attr) | Hook to fire when SelectedItem changes |
[OnCollectionChanged] |
Method (with selector attr) | Hook to fire when Items changes |
[TriggersRefresh] |
Method or [ObservableProperty] field |
Refreshes named selectors on change |
Generated property naming convention: Strip leading/trailing _, strip On/Get/Refresh prefixes, strip Async/Command/Func suffixes, then append the type suffix (e.g. Button, Selector, ComboBox, ListBox). The class containing these methods must be partial.
The test project uses MSTest (MSTest.TestAdapter + MSTest.TestFramework).
Sub-directories:
Mvvm.SourceGenerators.Tests/– Source generator unit tests. Each test compiles a small C# input file embedded as a resource (underGeneratorInputs/) and asserts on the generated output.Mvvm.Tests/– Runtime tests for Mvvm.Controls classes.WPF.Behaviors.Tests/– Tests for WPF attached behaviors.WPF.Controls.Tests/– Tests for custom WPF controls.
When adding or modifying a source generator, add or update the corresponding generator input file in Mvvm.SourceGenerators.Tests/GeneratorInputs/ and add it as an <EmbeddedResource> in Mvvm.Controls.Tests.csproj. Then add or update the test in the appropriate *GeneratorTests.cs file.
- Namespace root:
RFBCodeWorks(assembly name prefix) /RFBCodeWorks.Mvvm(runtime namespace). - C# language version:
LatestinMvvm.Controls. TheCSharp9_MissingComponents.csshim is included forinit-setter support on older targets. - Nullability:
#nullable enableis used in new source files. partialrequirement: Any class using source-generator attributes must be declaredpartial. The generator emits aRFB_MVVM_004error diagnostic otherwise.- Async selectors: Methods decorated with
[Selector]/[ComboBox]/[ListBox]may returnIList<T>,Task<IList<T>>, orValueTask<IList<T>>. Async methods may optionally accept a singleCancellationTokenparameter. - Strong naming: The test assembly is strong-named using
MvvmControls.snk. - XAML namespaces (for use in consumer XAML):
- Converters:
https://github.com/RFBCodeWorks/MvvmControls/WPF.Converters - Controls:
https://github.com/RFBCodeWorks/MvvmControls/WPF.Controls - Behaviors:
https://github.com/RFBCodeWorks/MvvmControls/WPF.Behaviors - Specialized ViewModels:
https://github.com/RFBCodeWorks/MvvmControls/Mvvm/Specialized
- Converters:
Use the GitHub issue templates:
- Bug Report – general bugs (
.github/ISSUE_TEMPLATE/bug-report.md) - Source Generator Issue – generator bugs including the input code, what was generated, and what was expected (
.github/ISSUE_TEMPLATE/source-generator-issue.md)