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
29 changes: 16 additions & 13 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@
<Net10PackageVersion>10.0.12</Net10PackageVersion>
<Net11PreviewPackageVersion>11.0.0-rc.1.26425.128</Net11PreviewPackageVersion>
<MauiNet9PackageVersion>9.0.120</MauiNet9PackageVersion>
<MauiNet10PackageVersion>10.0.101</MauiNet10PackageVersion>
<MauiNet10PackageVersion>10.0.110</MauiNet10PackageVersion>
<MauiNet11PackageVersion>11.0.0-rc.1.26451.6</MauiNet11PackageVersion>
<ReactiveUIPackageVersion>24.2.0</ReactiveUIPackageVersion>
<ReactiveUIPackageVersion>25.1.1</ReactiveUIPackageVersion>
<RoslynAnalysersVersion>5.1.2</RoslynAnalysersVersion>
<PrimitivesVersion>8.0.0</PrimitivesVersion>
<PrimitivesVersion>8.4.0</PrimitivesVersion>
<Net10BuildToolsVersion>18.9.6</Net10BuildToolsVersion>
<Net11BuildToolsVersion>18.10.1</Net11BuildToolsVersion>
</PropertyGroup>

<ItemGroup>
<PackageVersion Include="Avalonia" Version="12.1.2" />
<PackageVersion Include="Avalonia.Desktop" Version="12.1.2" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="12.1.2" />
<PackageVersion Include="Avalonia" Version="12.1.3" />
<PackageVersion Include="Avalonia.Desktop" Version="12.1.3" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="12.1.3" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="CP.Nuke.BuildTools" Version="3.0.2" />
<PackageVersion Include="log4net" Version="3.4.0" />
<PackageVersion Include="Microsoft.Build.Tasks.Core" Version="18.9.6" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="18.10.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.10.1" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.401" />
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.11.2" />
Expand All @@ -35,7 +35,7 @@
<!--Reactive UI with ReactiveUI.Primitives support-->
<PackageVersion Include="ReactiveUI.Primitives" Version="$(PrimitivesVersion)" />
<PackageVersion Include="ReactiveUI" Version="$(ReactiveUIPackageVersion)" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.1.2" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.1.5" />
<PackageVersion Include="ReactiveUI.Core" Version="$(ReactiveUIPackageVersion)" />
<PackageVersion Include="ReactiveUI.Disposables" Version="$(PrimitivesVersion)" />
<PackageVersion Include="ReactiveUI.Drawing" Version="$(ReactiveUIPackageVersion)" />
Expand All @@ -46,7 +46,7 @@
<PackageVersion Include="ReactiveUI.Reactive" Version="$(ReactiveUIPackageVersion)" />
<!--Reactive UI with System.Reactive support-->
<PackageVersion Include="ReactiveUI.Primitives.Reactive" Version="$(PrimitivesVersion)" />
<PackageVersion Include="ReactiveUI.Avalonia.Reactive" Version="12.1.2" />
<PackageVersion Include="ReactiveUI.Avalonia.Reactive" Version="12.1.5" />
<PackageVersion Include="ReactiveUI.Drawing.Reactive" Version="$(ReactiveUIPackageVersion)" />
<PackageVersion Include="ReactiveUI.Maui.Reactive" Version="$(ReactiveUIPackageVersion)" />
<PackageVersion Include="ReactiveUI.WinForms.Reactive" Version="$(ReactiveUIPackageVersion)" />
Expand All @@ -55,16 +55,17 @@
<PackageVersion Include="Roslynator.Analyzers" Version="5.0.0" />
<PackageVersion Include="Splat" Version="21.0.0" />
<PackageVersion Include="Splat.Microsoft.Extensions.DependencyInjection" Version="21.0.0" />
<PackageVersion Include="SQLitePCLRaw.lib.e_sqlite3" Version="3.50.3" />
<PackageVersion Include="StyleSharp.Analyzers" Version="$(RoslynAnalysersVersion)" />
<PackageVersion Include="PerformanceSharp.Analyzers" Version="$(RoslynAnalysersVersion)" />
<PackageVersion Include="SecuritySharp.Analyzers" Version="$(RoslynAnalysersVersion)" />
<PackageVersion Include="Tmds.DBus.Protocol" Version="0.95.1" />
<PackageVersion Include="TUnit" Version="1.68.17" />
<PackageVersion Include="Verify.TUnit" Version="32.0.1" />
<PackageVersion Include="TUnit" Version="1.72.0" />
<PackageVersion Include="SQLitePCLRaw.lib.e_sqlite3" Version="3.53.3" />
</ItemGroup>

<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net11.0')) != 'true'">
<PackageVersion Include="Microsoft.Build.Tasks.Core" Version="$(Net10BuildToolsVersion)" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="$(Net10BuildToolsVersion)" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="$(Net10PackageVersion)" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="$(Net10PackageVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(Net10PackageVersion)" />
Expand All @@ -77,6 +78,8 @@
</ItemGroup>

<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net11.0')) == 'true'">
<PackageVersion Include="Microsoft.Build.Tasks.Core" Version="$(Net11BuildToolsVersion)" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="$(Net11BuildToolsVersion)" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="$(Net11PreviewPackageVersion)" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="$(Net11PreviewPackageVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(Net11PreviewPackageVersion)" />
Expand Down
1 change: 0 additions & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage"/>
<PackageReference Include="PublicApiGenerator"/>
<PackageReference Include="Verify.TUnit"/>

<None Include="$(MSBuildThisFileDirectory)testconfig.json">
<Link>$(AssemblyName).testconfig.json</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using ReactiveMarbles.Extensions.Hosting.WinForms;
#if REACTIVE_SHIM
using ReactiveUI.Reactive.Builder;
#else
Expand Down Expand Up @@ -61,9 +64,15 @@ public IHostApplicationBuilder ConfigureSplatForMicrosoftDependencyResolver()
_ = hostBuilder ?? throw new ArgumentNullException(nameof(hostBuilder));

hostBuilder.Services.UseMicrosoftDependencyResolver();
hostBuilder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IWinFormsService>(new ReactiveUiWinFormsService()));
var reactiveUiBuilder = AppLocator.CurrentMutable.CreateReactiveUIBuilder()
.WithRegistration(static r => r.InitializeSplat())
.WithWinForms();
#if REACTIVE_SHIM
.WithPlatformModule<global::ReactiveUI.Reactive.Winforms.Registrations>();
#else
.WithPlatformModule<global::ReactiveUI.Winforms.Registrations>();
#endif
_ = reactiveUiBuilder.BuildApp();
return hostBuilder;
}
Expand All @@ -82,9 +91,15 @@ public IHostBuilder ConfigureSplatForMicrosoftDependencyResolver() =>
hostBuilder.ConfigureServices(static (serviceCollection) =>
{
serviceCollection.UseMicrosoftDependencyResolver();
serviceCollection.TryAddEnumerable(
ServiceDescriptor.Singleton<IWinFormsService>(new ReactiveUiWinFormsService()));
var reactiveUiBuilder = AppLocator.CurrentMutable.CreateReactiveUIBuilder()
.WithRegistration(static r => r.InitializeSplat())
.WithWinForms();
#if REACTIVE_SHIM
.WithPlatformModule<global::ReactiveUI.Reactive.Winforms.Registrations>();
#else
.WithPlatformModule<global::ReactiveUI.Winforms.Registrations>();
#endif
_ = reactiveUiBuilder.BuildApp();
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) 2019-2026 ReactiveUI Association Incorporated. All rights reserved.
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using ReactiveMarbles.Extensions.Hosting.WinForms;
#if REACTIVE_SHIM
using ControlSequencer = ReactiveUI.Primitives.Reactive.Concurrency.ControlSequencer;
using RxSchedulers = ReactiveUI.Reactive.RxSchedulers;
#else
using ControlSequencer = ReactiveUI.Primitives.Concurrency.ControlSequencer;
using RxSchedulers = ReactiveUI.RxSchedulers;
#endif

#if REACTIVE_SHIM
namespace ReactiveMarbles.Extensions.Hosting.Reactive.ReactiveUI;
#else
namespace ReactiveMarbles.Extensions.Hosting.ReactiveUI;
#endif

/// <summary>Initializes ReactiveUI on the hosted Windows Forms UI thread.</summary>
internal sealed class ReactiveUiWinFormsService : IWinFormsService
{
/// <inheritdoc />
public void Initialize() => RxSchedulers.MainThreadScheduler = ControlSequencer.Main;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
// See the LICENSE file in the project root for full license information.

using Microsoft.Extensions.Hosting;
using ReactiveUI.Builder;

#if REACTIVE_SHIM
using ReactiveUI.Reactive.Builder;
#else
using ReactiveUI.Builder;
#endif
using Splat;
using Splat.Microsoft.Extensions.DependencyInjection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ private static void ConfigureReactiveUi(IServiceCollection services)
ServiceDescriptor.Singleton<IWpfService>(new ReactiveUiWpfSchedulerService()));
var reactiveUiBuilder = AppLocator.CurrentMutable.CreateReactiveUIBuilder()
.WithRegistration(static r => r.InitializeSplat())
.WithWpf();
#if REACTIVE_SHIM
.WithPlatformModule<global::ReactiveUI.Reactive.Wpf.Registrations>()
#else
.WithPlatformModule<global::ReactiveUI.Wpf.Registrations>()
#endif
.WithWpfConverters();
_ = reactiveUiBuilder.BuildApp();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private ObservableAsPropertyHelper<IEnumerable<NugetDetailsViewModel>> EnsureSea
.Where(static term => !string.IsNullOrWhiteSpace(term))
.SelectMany(term => Signal.FromAsync(token => SearchNuGetPackages(term, token)))
.ObserveOn(RxSchedulers.MainThreadScheduler)
.ToProperty(this, x => x.SearchResults);
.ToProperty(this, static x => x.SearchResults);
_ = _searchResults.ThrownExceptions.Subscribe(static error => { });
return _searchResults;
}
Expand All @@ -116,7 +116,7 @@ private ObservableAsPropertyHelper<bool> EnsureAvailabilityInitialized()
_isAvailable = this
.WhenAnyValue(x => x.SearchResults)
.Select(static searchResults => searchResults is not null)
.ToProperty(this, x => x.IsAvailable);
.ToProperty(this, static x => x.IsAvailable);
return _isAvailable;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using ReactiveMarbles.Extensions.Hosting.AppServices;
using ReactiveMarbles.Extensions.Hosting.Avalonia;
using ReactiveMarbles.Extensions.Hosting.ReactiveUI;
using ReactiveUI;

namespace Extensions.Hosting.ReactiveAvalonia.Example;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using ReactiveMarbles.Extensions.Hosting.AppServices;
using ReactiveMarbles.Extensions.Hosting.ReactiveUI;
using ReactiveMarbles.Extensions.Hosting.Wpf;
using ReactiveUI;

namespace Extensions.Hosting.Reactive.Example;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private ObservableAsPropertyHelper<IEnumerable<NugetDetailsViewModel>> EnsureSea
.Where(static term => !string.IsNullOrWhiteSpace(term))
.SelectMany(term => Signal.FromAsync(token => SearchNuGetPackages(term, token)))
.ObserveOn(RxSchedulers.MainThreadScheduler)
.ToProperty(this, x => x.SearchResults);
.ToProperty(this, static x => x.SearchResults);
_ = _searchResults.ThrownExceptions.Subscribe(static error => { /* Handle errors here */ });
return _searchResults;
}
Expand All @@ -127,7 +127,7 @@ private ObservableAsPropertyHelper<bool> EnsureAvailabilityInitialized()
_isAvailable = this
.WhenAnyValue(x => x.SearchResults)
.Select(static searchResults => searchResults is not null)
.ToProperty(this, x => x.IsAvailable);
.ToProperty(this, static x => x.IsAvailable);
return _isAvailable;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ReactiveMarbles.Extensions.Hosting.WinForms;
#if REACTIVE_SHIM
using ReactiveMarbles.Extensions.Hosting.Reactive.ReactiveUI;
using ControlSequencer = ReactiveUI.Primitives.Reactive.Concurrency.ControlSequencer;
using RxSchedulers = ReactiveUI.Reactive.RxSchedulers;
#else
using ReactiveMarbles.Extensions.Hosting.ReactiveUI;
using ControlSequencer = ReactiveUI.Primitives.Concurrency.ControlSequencer;
using RxSchedulers = ReactiveUI.RxSchedulers;
#endif

namespace Extensions.Hosting.WinForms.Tests;
Expand All @@ -15,6 +21,36 @@ namespace Extensions.Hosting.WinForms.Tests;
[NotInParallel]
public sealed class ReactiveUiWinFormsExtensionsTests
{
/// <summary>Gets the maximum duration allowed for hosted UI startup and shutdown.</summary>
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(10);

/// <summary>Verifies scheduler binding on the hosted UI thread before its message loop starts.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Test]
public async Task ConfigureSplatForMicrosoftDependencyResolver_HostedUiBindsWinFormsScheduler()
{
var originalScheduler = RxSchedulers.MainThreadScheduler;
var captureService = new SchedulerCaptureService();
var hostBuilder = Host.CreateApplicationBuilder();
_ = hostBuilder.ConfigureSplatForMicrosoftDependencyResolver();
_ = hostBuilder.ConfigureWinForms(static context => context.EnableVisualStyles = false);
_ = hostBuilder.Services.AddSingleton<IWinFormsService>(captureService);
using var host = hostBuilder.Build();

try
{
await host.StartAsync().WaitAsync(Timeout);
var schedulerIsBound = await captureService.SchedulerIsBound.Task.WaitAsync(Timeout);

await Assert.That(schedulerIsBound).IsTrue();
}
finally
{
await host.StopAsync().WaitAsync(Timeout);
RxSchedulers.MainThreadScheduler = originalScheduler;
}
}

/// <summary>Verifies that application builders reject a null receiver.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Test]
Expand Down Expand Up @@ -82,4 +118,14 @@ public async Task MapSplatLocator_NullHost_ReturnsNullAndInvokesFactoryWithNull(
await Assert.That(mappedHost).IsNull();
await Assert.That(mappedProvider).IsNull();
}

/// <summary>Captures the scheduler after the ReactiveUI service initializes on the UI thread.</summary>
private sealed class SchedulerCaptureService : IWinFormsService
{
/// <summary>Gets the scheduler binding result.</summary>
public TaskCompletionSource<bool> SchedulerIsBound { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);

/// <inheritdoc />
public void Initialize() => SchedulerIsBound.SetResult(RxSchedulers.MainThreadScheduler is ControlSequencer);
}
}
Loading