Vetuviem is a toolkit to support View to View Model binding (MVVM -> V2VM -> Ve-Tu-Viem) aimed at offering a structure to get more re-usability out of ReactiveUI.
- To give a mechanism to reduce the amount of boiler plate code being produced, by allowing some of the ReactiveUI specific logic to be hidden away
- Allow the developer to think along the lines of standard behaviours for controls by offering a way to produce re-usable behaviours through a class and\or function design pattern
- Allow the developer to focus on what matters on the ViewModel
- Reduce the cognitive load by
- Removing the risk of misusing 1 way or 2 way binding
- Remove the need for the user to think about having to cater for Bind vs BindCommand
- Offer a structure that allows for more work to be done potentially with Source Generators to reduce reflection and improve the build time developer experience.
This is currently a proof of concept alpha. For understanding of the design reasoning please see https://www.dpvreony.com/articles/designing-vetuviem/
| Purpose | Package | NuGet |
|---|---|---|
| Command Line Generation | Coming soon | Coming Soon |
| Visual Studio Integration | Vetuviem.SourceGenerator | |
| Core Functionality | Vetuviem.Core |
Currently to write binding logic in the codebehind you have to write something similar to this for a single control
// Traditional ReactiveUI binding approach
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.Forename, v => v.Forename.Text)
.DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ForenameLengthRemaining, v => v.ForenameLengthRemaining.Content)
.DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ForenameLengthRemaining, v => v.ForenameLengthRemaining.Foreground,
lengthRemaining => GetBrushForLengthRemaining(lengthRemaining))
.DisposeWith(disposables);
});But what if you have a way to simplify logic and offer a way to even make it reusable without all the boilerplate leg work?
// Vetuviem approach using ViewBindingModels
public sealed class QuestionnaireViewBindingModels : AbstractEnableViewToViewModelBindings<QuestionnaireView, QuestionnaireViewModel>
{
protected override IEnumerable<IControlBindingModel<QuestionnaireView, QuestionnaireViewModel>> GetBindings()
{
yield return new TextBoxControlBindingModel<QuestionnaireView, QuestionnaireViewModel>(vw => vw.Forename)
{
Text = new TwoWayBinding<QuestionnaireViewModel, string>(vm => vm.Forename),
};
yield return new LabelControlBindingModel<QuestionnaireView, QuestionnaireViewModel>(vw => vw.ForenameLengthRemaining)
{
Content = new OneWayBindingOnOneOrTwoWayBind<QuestionnaireViewModel, object>(vm => vm.ForenameLengthRemaining, o => o?.ToString() ?? string.Empty),
Foreground = new OneWayBindingWithConversionOnOneOrTwoWayBind<QuestionnaireViewModel, Brush, int>(vm => vm.ForenameLengthRemaining, lengthRemaining => GetBrushForLengthRemaining(lengthRemaining))
};
}
}Vetuviem source generators can be configured through MSBuild properties in your project file (.csproj). Add these properties to a <PropertyGroup> section to customize code generation behavior.
| Property | Description | Default Value |
|---|---|---|
Vetuviem_Root_Namespace |
Override the root namespace for generated code | (Project's root namespace) |
Vetuviem_Make_Classes_Public |
Make generated classes public instead of internal | false |
Vetuviem_Assemblies |
Comma-separated list of assemblies to scan for controls | (Platform-specific defaults) |
Vetuviem_Assembly_Mode |
How to use custom assemblies: Replace or Extend |
Replace |
Vetuviem_Base_Namespace |
Base namespace when using custom assemblies | (none) |
Vetuviem_Include_Obsolete_Items |
Include properties marked with ObsoleteAttribute |
false |
Vetuviem_Allow_Experimental_Properties |
Include properties marked with ExperimentalAttribute |
false |
Vetuviem_Logging_Implementation_Mode |
Logging implementation: None or SplatViaServiceLocator |
SplatViaServiceLocator |
Override the default root namespace for generated code. This is useful when you want generated binding models to reside in a specific namespace.
Example:
<PropertyGroup>
<Vetuviem_Root_Namespace>MyApp.Generated</Vetuviem_Root_Namespace>
</PropertyGroup>Controls the visibility of generated binding model classes. By default, classes are generated as internal. Set this to true to make them public.
Example:
<PropertyGroup>
<Vetuviem_Make_Classes_Public>true</Vetuviem_Make_Classes_Public>
</PropertyGroup>Specify a comma-separated list of assemblies to scan for control types. Use in conjunction with Vetuviem_Assembly_Mode to either replace or extend the default platform assemblies.
Example:
<PropertyGroup>
<Vetuviem_Assemblies>CustomControls.dll,ThirdPartyControls.dll</Vetuviem_Assemblies>
</PropertyGroup>Determines how the assemblies specified in Vetuviem_Assemblies are used:
Replace(default): Replace platform defaults with your custom assembliesExtend: Add custom assemblies in addition to platform defaults
Example:
<PropertyGroup>
<Vetuviem_Assembly_Mode>Extend</Vetuviem_Assembly_Mode>
</PropertyGroup>Used with custom assemblies to specify a base namespace. This allows third parties to use the generator and produce custom namespaces that inherit from the root or use a custom namespace.
Example:
<PropertyGroup>
<Vetuviem_Base_Namespace>MyCompany.Controls</Vetuviem_Base_Namespace>
</PropertyGroup>Controls whether properties marked with ObsoleteAttribute are included in code generation. By default, obsolete items are excluded.
Example:
<PropertyGroup>
<Vetuviem_Include_Obsolete_Items>true</Vetuviem_Include_Obsolete_Items>
</PropertyGroup>Starting with .NET 10, properties can be marked with ExperimentalAttribute to indicate they are experimental APIs. This property controls how these are handled:
When false or not set (default):
- Properties marked with
ExperimentalAttributeare excluded from code generation - No warnings are generated
- Experimental properties are silently skipped
- Ensures backward compatibility
When true:
- Properties marked with
ExperimentalAttributeare included in code generation - A
SuppressMessageattribute is automatically added to suppress experimental warnings - The diagnostic ID from the
ExperimentalAttributeis extracted and used in the suppression
Example:
<PropertyGroup>
<Vetuviem_Allow_Experimental_Properties>true</Vetuviem_Allow_Experimental_Properties>
</PropertyGroup>Example Control Property:
[Experimental("WFDEV001")]
public ThemeMode ThemeMode { get; set; }Generated Binding Property (when enabled):
[SuppressMessage("Usage", "WFDEV001")]
public IOneOrTwoWayBind<TViewModel, ThemeMode>? ThemeMode { get; init; }Notes:
- The diagnostic ID is read from the first constructor argument of
ExperimentalAttribute - Only properties with
publicaccessibility are considered for generation (standard behavior)
Controls the logging implementation generated in binding code:
None: No logging implementationSplatViaServiceLocator(default): Uses Splat logging via service locator
Example:
<PropertyGroup>
<Vetuviem_Logging_Implementation_Mode>None</Vetuviem_Logging_Implementation_Mode>
</PropertyGroup>For support, please:
- Check the design article for understanding the design reasoning
- Report bugs or request features via GitHub Issues
- Ask questions in GitHub Discussions
- Review the sample applications in the
srcdirectory for practical examples
Contributions are welcome! Please:
- Read the Code of Conduct
- Fork the repository and create a feature branch
- Write tests for your changes
- Ensure all tests pass and code follows the existing style
- Submit a Pull Request with a clear description of your changes
This project is licensed under the MIT License - see the LICENSE file for details.