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
25 changes: 25 additions & 0 deletions src/ReactiveUI/Activation/IActivationForViewFetcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,31 @@ namespace ReactiveUI;
/// View is activated or deactivated. This is usually only used when porting
/// ReactiveUI to a new UI framework.
/// </summary>
/// <remarks>
/// <para>
/// Activation fetchers translate framework-specific signals (such as page navigation, focus, or visibility
/// changes) into the cross-platform <see cref="IActivatableView"/> semantics used by ReactiveUI. Multiple
/// fetchers can exist, each advertising an affinity for a given view type.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// <![CDATA[
/// public sealed class WinFormsActivationFetcher : IActivationForViewFetcher
/// {
/// public int GetAffinityForView(Type view) => typeof(Form).IsAssignableFrom(view) ? 10 : 0;
///
/// public IObservable<bool> GetActivationForView(IActivatableView view)
/// {
/// var form = (Form)view;
/// return Observable.Merge(
/// Observable.FromEventPattern(form, nameof(form.Load)).Select(_ => true),
/// Observable.FromEventPattern(form, nameof(form.FormClosed)).Select(_ => false));
/// }
/// }
/// ]]>
/// </code>
/// </example>
public interface IActivationForViewFetcher
{
/// <summary>
Expand Down
18 changes: 18 additions & 0 deletions src/ReactiveUI/Bindings/Interaction/InteractionBindingMixins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ namespace ReactiveUI;
/// <summary>
/// This class provides extension methods for the ReactiveUI view binding mechanism.
/// </summary>
/// <remarks>
/// <para>
/// Interaction bindings are usually established within a view's activation block to ensure registrations are disposed
/// when the view is no longer visible. The helpers resolve the <see cref="IInteraction{TInput,TOutput}"/> on the view
/// model via an expression and hook it to a handler that can await UI prompts such as dialogs.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// <![CDATA[
/// this.WhenActivated(disposables =>
/// {
/// this.BindInteraction(ViewModel, vm => vm.ShowDialog, HandleDialogAsync)
/// .DisposeWith(disposables);
/// });
/// ]]>
/// </code>
/// </example>
public static class InteractionBindingMixins
{
private static readonly InteractionBinderImplementation _binderImplementation = new();
Expand Down
22 changes: 22 additions & 0 deletions src/ReactiveUI/Bindings/Property/PropertyBindingMixins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,28 @@ namespace ReactiveUI;
/// <summary>
/// This class provides extension methods for the ReactiveUI view binding mechanism.
/// </summary>
/// <remarks>
/// <para>
/// The helpers in this class are typically consumed within a view's <c>WhenActivated</c> block to connect a view model
/// property to a control and automatically dispose the binding when the view deactivates. Converters can be supplied via
/// <see cref="IBindingTypeConverter"/> instances or delegates, and <see cref="TriggerUpdate"/> indicates whether bindings
/// push values from the view model, the view, or both.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// <![CDATA[
/// this.WhenActivated(disposables =>
/// {
/// this.Bind(ViewModel, vm => vm.UserName, v => v.UserNameTextBox.Text)
/// .DisposeWith(disposables);
///
/// this.OneWayBind(ViewModel, vm => vm.IsBusy, v => v.Spinner.IsRunning)
/// .DisposeWith(disposables);
/// });
/// ]]>
/// </code>
/// </example>
public static class PropertyBindingMixins
{
private static readonly PropertyBinderImplementation _binderImplementation;
Expand Down
18 changes: 18 additions & 0 deletions src/ReactiveUI/Bindings/Reactive/IReactiveBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ namespace ReactiveUI;
/// </summary>
/// <typeparam name="TView">The view type.</typeparam>
/// <typeparam name="TValue">The value type.</typeparam>
/// <remarks>
/// <para>
/// <see cref="IReactiveBinding{TView, TValue}"/> instances are returned from helpers like <c>Bind</c>,
/// <c>OneWayBind</c>, and <c>BindCommand</c>. Holding onto the binding allows you to inspect the original
/// property expressions, observe the <see cref="Changed"/> stream, or dispose the binding manually when a
/// different lifecycle than <c>WhenActivated</c> is required.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// <![CDATA[
/// var binding = this.Bind(ViewModel, vm => vm.UserName, v => v.UserNameTextBox.Text);
/// binding.Changed.Subscribe(value => logger.LogInformation("UserName changed to {Value}", value));
/// // Later
/// binding.Dispose();
/// ]]>
/// </code>
/// </example>
public interface IReactiveBinding<out TView, out TValue> : IDisposable
where TView : IViewFor
{
Expand Down
25 changes: 24 additions & 1 deletion src/ReactiveUI/Builder/IReactiveUIBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,31 @@
namespace ReactiveUI.Builder;

/// <summary>
/// IReactiveUIBuilder.
/// Fluent builder that configures ReactiveUI platform services, registrations, and schedulers before building an application instance.
/// </summary>
/// <remarks>
/// <para>
/// The builder wraps <see cref="Splat.Builder"/> primitives so apps can register views, view models, and platform modules using
/// a single fluent API. Most hosts call <c>UseReactiveUI</c> (MAUI) or <c>services.AddReactiveUI()</c> (generic host) internally, which
/// creates an <see cref="IReactiveUIBuilder"/> and then applies platform-specific extensions.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// <![CDATA[
/// builder.UseReactiveUI(config =>
/// config
/// .WithPlatformServices()
/// .RegisterView<LoginView, LoginViewModel>()
/// .RegisterSingletonViewModel<AppShellViewModel>()
/// .WithRegistration(resolver =>
/// {
/// resolver.RegisterLazySingleton(() => new ApiClient(), typeof(IApiClient));
/// })
/// .BuildApp());
/// ]]>
/// </code>
/// </example>
/// <seealso cref="Splat.Builder.IAppBuilder" />
public interface IReactiveUIBuilder : IAppBuilder
{
Expand Down
25 changes: 25 additions & 0 deletions src/ReactiveUI/Interactions/IInteraction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,31 @@ namespace ReactiveUI;
/// then provides the interaction with an output as the answer to the question.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// <![CDATA[
/// public class ExportViewModel : ReactiveObject
/// {
/// public Interaction<ExportRequest, bool> ConfirmExport { get; } = new();
///
/// public Task<bool> TryExportAsync(ExportRequest request) => ConfirmExport.Handle(request).ToTask();
/// }
///
/// public partial class ExportView : ReactiveUserControl<ExportViewModel>
/// {
/// public ExportView()
/// {
/// this.WhenActivated(disposables =>
/// ViewModel!.ConfirmExport.RegisterHandler(async context =>
/// {
/// var decision = await dialogService.ShowAsync(context.Input);
/// context.SetOutput(decision);
/// }).DisposeWith(disposables));
/// }
/// }
/// ]]>
/// </code>
/// </example>
/// <typeparam name="TInput">
/// The interaction's input type.
/// </typeparam>
Expand Down
34 changes: 30 additions & 4 deletions src/ReactiveUI/Interactions/Interaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,41 @@ namespace ReactiveUI;
/// <see cref="UnhandledInteractionException{TInput, TOutput}"/> if no handler handles the interaction.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// <![CDATA[
/// public class DeleteCustomerViewModel : ReactiveObject
/// {
/// public Interaction<string, bool> ConfirmDelete { get; } = new();
///
/// public async Task<bool> TryDeleteAsync(string customerName)
/// {
/// var approved = await ConfirmDelete.Handle($"Delete {customerName}?");
/// return approved;
/// }
/// }
///
/// public partial class DeleteCustomerView : ReactiveUserControl<DeleteCustomerViewModel>
/// {
/// public DeleteCustomerView()
/// {
/// this.WhenActivated(disposables =>
/// ViewModel!.ConfirmDelete.RegisterHandler(async context =>
/// {
/// var approved = await dialogService.ShowAsync(context.Input);
/// context.SetOutput(approved);
/// }).DisposeWith(disposables));
/// }
/// }
/// ]]>
/// </code>
/// </example>
/// <typeparam name="TInput">
/// The interaction's input type.
/// </typeparam>
/// <typeparam name="TOutput">
/// The interaction's output type.
/// </typeparam>
/// <remarks>
/// Initializes a new instance of the <see cref="Interaction{TInput, TOutput}"/> class.
/// </remarks>
/// <param name="handlerScheduler">
/// The scheduler to use when invoking handlers, which defaults to <c>CurrentThreadScheduler.Instance</c> if <see langword="null"/>.
/// </param>
Expand Down Expand Up @@ -117,7 +143,7 @@ protected Func<IInteractionContext<TInput, TOutput>, IObservable<Unit>>[] GetHan
}

/// <summary>
/// Gets a interaction context which is used to provide information about the interaction.
/// Gets an interaction context which is used to provide information about the interaction.
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The article should be "an" instead of "a" before "interaction" since "interaction" starts with a vowel sound.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

/// </summary>
/// <param name="input">The input that is being passed in.</param>
/// <returns>The interaction context.</returns>
Expand Down
16 changes: 16 additions & 0 deletions src/ReactiveUI/Interactions/InteractionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,23 @@ namespace ReactiveUI;
/// the input to the interaction, whilst the <see cref="SetOutput"/> method allows a handler to provide the
/// output.
/// </para>
/// <para>
/// Calling <see cref="SetOutput"/> more than once throws an <see cref="InvalidOperationException"/>, ensuring the
/// handler's reply remains deterministic even when multiple handlers run concurrently. Use <see cref="IsHandled"/>
/// to guard logic that should only execute once.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// <![CDATA[
/// viewModel.ConfirmDelete.RegisterHandler(async ctx =>
/// {
/// var approved = await dialogService.ShowAsync(ctx.Input);
/// ctx.SetOutput(approved);
/// });
/// ]]>
/// </code>
/// </example>
/// <typeparam name="TInput">
/// The type of the interaction's input.
/// </typeparam>
Expand Down
4 changes: 2 additions & 2 deletions src/ReactiveUI/Interactions/UnhandledInteractionException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ public class UnhandledInteractionException<TInput, TOutput> : Exception
/// <summary>
/// Initializes a new instance of the <see cref="UnhandledInteractionException{TInput, TOutput}"/> class.
/// </summary>
/// <param name="interaction">The interaction that doesn't have a input handler.</param>
/// <param name="interaction">The interaction that doesn't have an input handler.</param>
/// <param name="input">The input into the interaction.</param>
public UnhandledInteractionException(Interaction<TInput, TOutput> interaction, TInput input)
: this("Failed to find a registration for a Interaction.")
: this("Failed to find a registration for an Interaction.")
{
_interaction = interaction;
Input = input;
Expand Down
28 changes: 27 additions & 1 deletion src/ReactiveUI/Interfaces/IActivatableViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,36 @@ namespace ReactiveUI;
/// the View is activated. See the documentation for ViewModelActivator to
/// read more about Activation.
/// </summary>
/// <remarks>
/// <para>
/// Typical usage involves creating a <see cref="ViewModelActivator"/> field and calling <c>WhenActivated</c>
/// in the constructor to compose subscriptions that should live only while the view is displayed.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// <![CDATA[
/// public sealed class DetailsViewModel : ReactiveObject, IActivatableViewModel
/// {
/// public DetailsViewModel()
/// {
/// Activator = new ViewModelActivator();
///
/// this.WhenActivated(disposables =>
/// {
/// LoadCommand.Execute().Subscribe().DisposeWith(disposables);
/// });
/// }
///
/// public ViewModelActivator Activator { get; }
/// }
/// ]]>
/// </code>
/// </example>
public interface IActivatableViewModel
{
/// <summary>
/// Gets the Activator which will be used by the View when Activation/Deactivation occurs.
/// </summary>
ViewModelActivator Activator { get; }
}
}
31 changes: 25 additions & 6 deletions src/ReactiveUI/Interfaces/IRoutableViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,40 @@
namespace ReactiveUI;

/// <summary>
/// Implement this interface for ViewModels that can be navigated to.
/// Defines the minimum contract for view models that participate in <see cref="RoutingState"/> navigation.
/// </summary>
/// <remarks>
/// <para>
/// Routable view models expose a user-readable <see cref="UrlPathSegment"/> used for diagnostics / navigation breadcrumbs
/// and keep a reference to the owning <see cref="IScreen"/> so that downstream navigation commands can be issued.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// <![CDATA[
/// public class SettingsViewModel : ReactiveObject, IRoutableViewModel
/// {
/// public SettingsViewModel(IScreen hostScreen) => HostScreen = hostScreen;
///
/// public string? UrlPathSegment => "settings";
///
/// public IScreen HostScreen { get; }
/// }
/// ]]>
/// </code>
/// </example>
public interface IRoutableViewModel : IReactiveObject
{
/// <summary>
/// Gets a string token representing the current ViewModel, such as 'login' or 'user'.
/// Gets a string token representing the current view model, such as "login" or "user".
/// </summary>
#pragma warning disable CA1056 // URI-like properties should not be strings
string? UrlPathSegment { get; }
#pragma warning restore CA1056 // URI-like properties should not be strings

/// <summary>
/// Gets the IScreen that this ViewModel is currently being shown in. This
/// is usually passed into the ViewModel in the Constructor and saved
/// as a ReadOnly Property.
/// Gets the <see cref="IScreen"/> instance that hosts this view model. Use this reference to access the
/// shared <see cref="RoutingState"/> when chaining navigation from child view models.
/// </summary>
IScreen HostScreen { get; }
}
}
33 changes: 29 additions & 4 deletions src/ReactiveUI/Interfaces/IScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,38 @@
namespace ReactiveUI;

/// <summary>
/// IScreen represents any object that is hosting its own routing -
/// usually this object is your AppViewModel or MainWindow object.
/// Represents any object capable of hosting its own navigation stack via <see cref="RoutingState"/>.
/// </summary>
/// <remarks>
/// <para>
/// Most applications expose a single implementation of <see cref="IScreen"/> (for example a shell or app view model)
/// that owns the global router. Individual view models can accept an <see cref="IScreen"/> via constructor
/// injection so they can request navigation without directly referencing UI types.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// <![CDATA[
/// public class AppViewModel : ReactiveObject, IScreen
/// {
/// public RoutingState Router { get; } = new();
///
/// public ReactiveCommand<Unit, IRoutableViewModel> ShowSettings { get; }
///
/// public AppViewModel()
/// {
/// ShowSettings = ReactiveCommand.CreateFromObservable(
/// () => Router.Navigate.Execute(new SettingsViewModel(this)));
/// }
/// }
/// ]]>
/// </code>
/// </example>
public interface IScreen
{
/// <summary>
/// Gets the Router associated with this Screen.
/// Gets the router associated with this screen. The router coordinates navigation requests for
/// all child view models attached to the screen.
/// </summary>
RoutingState Router { get; }
}
}
Loading
Loading