Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,23 @@ ReactiveUI Source Generators automatically generate ReactiveUI objects to stream
- `[ReactiveCommand(OutputScheduler = nameof(_isheduler))]` using a Scheduler defined in the class
- `[ReactiveCommand][property: AttributeToAddToCommand]` with Attribute passthrough
- `[IViewFor(nameof(ViewModelName))]`
- `[IViewFor<YourViewModelType>]`
- `[IViewFor("YourNameSpace.YourGenericViewModel<int>")]` Generic
- `[IViewFor<YourViewModelType>(RegistrationType = SplatRegistrationType.PerRequest)]` with Splat Registration Type for IViewFor registration.
- `[IViewFor<YourViewModelType>(RegistrationType = SplatRegistrationType.LazySingleton)]` Generic with Splat Registration Type for IViewFor registration.
- `[IViewFor<YourViewModelType>(RegistrationType = SplatRegistrationType.Constant)]` Generic with Splat Registration Type for IViewFor registration.
- `[RoutedControlHost("YourNameSpace.CustomControl")]`
- `[ViewModelControlHost("YourNameSpace.CustomControl")]`
- `[BindableDerivedList]` Generates a derived list from a ReadOnlyObservableCollection backing field
- `[ReactiveCollection]` Generates property changed notifications on add, remove, new actions on a ObservableCollection backing field

#### IViewFor Registration generator

To register all views for view models registered via the IViewFor Source Generator with a specified `RegistrationType`, call the following method during application startup:
```csharp
Splat.Locator.CurrentMutable.RegisterViewsForViewModelsSourceGenerated();
```

### Compatibility Notes
- For ReactiveUI versions **older than V19.5.31**, all `[ReactiveCommand]` options are supported except for async methods with a `CancellationToken`.
- For **.NET Framework 4.8 and older**, add [Polyfill by Simon Cropp](https://github.com/SimonCropp/Polyfill) or [PolySharp by Sergio Pedri](https://github.com/Sergio0694/PolySharp) to your project and set the `LangVersion` to 12.0 or later in your project file.
Expand All @@ -63,11 +75,17 @@ Generates read-only properties backed by an `ObservableAsPropertyHelper` based o
Generates commands, with options to add attributes or enable `CanExecute` functionality.

### `[IViewFor]`
Links a view to a view model for data binding.
Links a view to a view model for data binding. Supports generic types and Splat registration.

### `[RoutedControlHost]` and `[ViewModelControlHost]`
Platform-specific attributes for control hosting in WinForms applications.

### `[BindableDerivedList]`
Generates a derived list from a `ReadOnlyObservableCollection` backing field.

### `[ReactiveCollection]`
Generates property changed notifications on add, remove, and new actions on an `ObservableCollection` backing field.

## Historical Approach

### Read-Write Properties
Expand Down Expand Up @@ -525,6 +543,35 @@ The class must inherit from a UI Control from any of the following platforms and
- Avalonia (Avalonia)
- Uno (Windows.UI.Xaml).

### IViewFor with Splat Registration Type

Choose from the following Splat Registration Types:
- `SplatRegistrationType.PerRequest`
- `SplatRegistrationType.LazySingleton`
- `SplatRegistrationType.Constant`
- `SplatRegistrationType.None` (Default if not specified - no registration is performed)

```csharp
using ReactiveUI.SourceGenerators;
using Splat;
[IViewFor<MyReactiveClass>(RegistrationType = SplatRegistrationType.PerRequest)]
public partial class MyReactiveControl : UserControl
{
public MyReactiveControl()
{
InitializeComponent();
ViewModel = Locator.Current.GetService<MyReactiveClass>();
}
}
```

this will generate the following code to enable you register the marked Views as `IViewFor<ViewModel>` with Splat:
```csharp
using ReactiveUI.SourceGenerators;

Splat.Locator.CurrentMutable.RegisterViewsForViewModelsSourceGenerated();
```

### Usage IViewFor with ViewModel Name - Generic Types should be used with the fully qualified name, otherwise use nameof(ViewModelTypeName)
```csharp
using ReactiveUI.SourceGenerators;
Expand Down
3 changes: 2 additions & 1 deletion src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.8.118" />
<PackageVersion Include="NuGet.Common" Version="6.14.0" />
<PackageVersion Include="NuGet.Protocol" Version="6.14.0" />
<PackageVersion Include="Splat" Version="16.0.1" />
<PackageVersion Include="stylecop.analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="Roslynator.Analyzers" Version="4.14.0" />
<PackageVersion Include="ReactiveUI" Version="21.0.1" />
Expand Down Expand Up @@ -45,4 +46,4 @@
<PackageVersion Include="Basic.Reference.Assemblies.Net80" Version="1.8.3" />
<PackageVersion Include="Basic.Reference.Assemblies.Net80Windows" Version="1.8.3" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//HintName: ReactiveUI.ReactiveUISourceGeneratorsExtensions.g.cs
// <auto-generated/>
#pragma warning disable
#nullable enable

using global::ReactiveUI;
using global::Splat;

namespace ReactiveUI.SourceGenerators
{
/// <summary>
/// Source-generated registration extensions for ReactiveUI views.
/// </summary>
internal static class ReactiveUISourceGeneratorsExtensions
{
public static void RegisterViewsForViewModelsSourceGenerated(this global::Splat.IMutableDependencyResolver resolver)
{
if (resolver is null) throw new global::System.ArgumentNullException(nameof(resolver));
}
}
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ namespace ReactiveUI.SourceGenerators;
/// </remarks>
/// <param name="viewModelType">Type of the view model.</param>
[global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
internal sealed class IViewForAttribute<T> : global::System.Attribute;
internal sealed class IViewForAttribute<T> : global::System.Attribute
{
/// <summary>
/// Gets the Splat registration type for Splat registration.
/// Registers IViewFor<T> in the Splat service locator.
/// </summary>
public SplatRegistrationType RegistrationType { get; init; } = SplatRegistrationType.None;
}

/// <summary>
/// IViewForAttribute.
Expand All @@ -27,8 +34,15 @@ internal sealed class IViewForAttribute<T> : global::System.Attribute;
/// <remarks>
/// Initializes a new instance of the <see cref="IViewForAttribute"/> class.
/// </remarks>
/// <param name="viewModelType">Type of the view model.</param>
/// <param name="viewModelType">Type of the view model, ensure to use the full type name including namespace.</param>
[global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
internal sealed class IViewForAttribute(string? viewModelType) : global::System.Attribute;
internal sealed class IViewForAttribute(string? viewModelType) : global::System.Attribute
{
/// <summary>
/// Gets the Splat registration type for Splat registration.
/// Registers IViewFor<T> in the Splat service locator.
/// </summary>
public SplatRegistrationType RegistrationType { get; init; } = SplatRegistrationType.None;
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,13 @@ internal enum InheritanceModifier
Override,
New,
}

internal enum SplatRegistrationType
{
None,
LazySingleton,
Constant,
PerRequest,
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,13 @@ internal enum InheritanceModifier
Override,
New,
}

internal enum SplatRegistrationType
{
None,
LazySingleton,
Constant,
PerRequest,
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,13 @@ internal enum InheritanceModifier
Override,
New,
}

internal enum SplatRegistrationType
{
None,
LazySingleton,
Constant,
PerRequest,
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,13 @@ internal enum InheritanceModifier
Override,
New,
}

internal enum SplatRegistrationType
{
None,
LazySingleton,
Constant,
PerRequest,
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,13 @@ internal enum InheritanceModifier
Override,
New,
}

internal enum SplatRegistrationType
{
None,
LazySingleton,
Constant,
PerRequest,
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,13 @@ internal enum InheritanceModifier
Override,
New,
}

internal enum SplatRegistrationType
{
None,
LazySingleton,
Constant,
PerRequest,
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,13 @@ internal enum InheritanceModifier
Override,
New,
}

internal enum SplatRegistrationType
{
None,
LazySingleton,
Constant,
PerRequest,
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,13 @@ internal enum InheritanceModifier
Override,
New,
}

internal enum SplatRegistrationType
{
None,
LazySingleton,
Constant,
PerRequest,
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,13 @@ internal enum InheritanceModifier
Override,
New,
}

internal enum SplatRegistrationType
{
None,
LazySingleton,
Constant,
PerRequest,
}
#nullable restore
#pragma warning restore
9 changes: 8 additions & 1 deletion src/ReactiveUI.SourceGenerators.Execute/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// The ReactiveUI and contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using ReactiveUI.SourceGenerators;
using Splat;

namespace SGReactiveUI.SourceGenerators.Test;

/// <summary>
Expand All @@ -13,5 +16,9 @@ public static class Program
/// <summary>
/// Defines the entry point of the application.
/// </summary>
public static void Main() => Application.Run(new TestViewWinForms());
public static void Main()
{
AppLocator.CurrentMutable.RegisterViewsForViewModelsSourceGenerated();
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use Locator.CurrentMutable instead of AppLocator.CurrentMutable to match the Splat API and be consistent with other usages in the codebase.

Suggested change
AppLocator.CurrentMutable.RegisterViewsForViewModelsSourceGenerated();
Locator.CurrentMutable.RegisterViewsForViewModelsSourceGenerated();

Copilot uses AI. Check for mistakes.

Application.Run(new TestViewWinForms());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

<ItemGroup>
<PackageReference Include="ReactiveUI" />
<PackageReference Include="Splat" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace SGReactiveUI.SourceGenerators.Test
/// TestViewWinForms.
/// </summary>
/// <seealso cref="System.Windows.Forms.Form" />
[IViewFor<TestViewModel>]
[IViewFor<TestViewModel>(RegistrationType = SplatRegistrationType.LazySingleton)]
public partial class TestViewWinForms : Form
{
/// <summary>
Expand Down
9 changes: 7 additions & 2 deletions src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@
using System.Windows;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using Splat;

namespace SGReactiveUI.SourceGenerators.Test;

/// <summary>
/// TestView.
/// </summary>
[IViewFor<TestViewModel>]
[IViewFor<TestViewModel>(RegistrationType = SplatRegistrationType.PerRequest)]
public partial class TestViewWpf : Window
{
/// <summary>
/// Initializes a new instance of the <see cref="TestViewWpf"/> class.
/// </summary>
public TestViewWpf() => ViewModel = TestViewModel.Instance;
public TestViewWpf()
{
Locator.CurrentMutable.RegisterLazySingleton<IViewFor<TestViewModel>>(() => new TestViewWpf());
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This manual registration should be removed since the PR introduces automatic registration via the generated extension method, making this redundant.

Suggested change
Locator.CurrentMutable.RegisterLazySingleton<IViewFor<TestViewModel>>(() => new TestViewWpf());

Copilot uses AI. Check for mistakes.

ViewModel = TestViewModel.Instance;
}

/// <summary>
/// Gets or sets the test property.
Expand Down
2 changes: 1 addition & 1 deletion src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace SGReactiveUI.SourceGenerators.Test;
/// TestViewWpf2.
/// </summary>
/// <seealso cref="System.Windows.Window" />
[IViewFor("TestViewModel2<int>")]
[IViewFor("SGReactiveUI.SourceGenerators.Test.TestViewModel2<int>", RegistrationType = SplatRegistrationType.PerRequest)]
public partial class TestViewWpf2 : Window
{
/// <summary>
Expand Down
Loading