Pamba.WinUI
0.3.0
dotnet add package Pamba.WinUI --version 0.3.0
NuGet\Install-Package Pamba.WinUI -Version 0.3.0
<PackageReference Include="Pamba.WinUI" Version="0.3.0" />
<PackageVersion Include="Pamba.WinUI" Version="0.3.0" />
<PackageReference Include="Pamba.WinUI" />
paket add Pamba.WinUI --version 0.3.0
#r "nuget: Pamba.WinUI, 0.3.0"
#:package Pamba.WinUI@0.3.0
#addin nuget:?package=Pamba.WinUI&version=0.3.0
#tool nuget:?package=Pamba.WinUI&version=0.3.0
Pamba.WinUI
WinUI 3 integration for the Pamba MVU runtime.
dotnet add package Pamba.WinUI
Requires .NET 10 / C# 14, Pamba, and Microsoft.WindowsAppSDK. Minimum platform: Windows 11 22H2.
Starting the runtime
WinUIMvuRuntime builds and starts the MVU loop. The builder enforces the correct
construction order at compile time. Projection is optional - call .Start() directly
after .WithSubscriptionStarter() if you have no UI projection.
var projection = new AppProjection(mainWindow);
MvuRuntime<AppState, Msg, Cmd, Sub> runtime = WinUIMvuRuntime
.Create(program, mainWindow.DispatcherQueue)
.WithCommandExecutor(commandExecutor.Execute)
.WithSubscriptionStarter(subscriptionStarter.Start)
.WithProjection(projection)
.Start();
State projection
StateProjectionBase<TState> maps state changes to UI updates. Subclass it and
register segments in the constructor. Each segment receives a selector that identifies
a slice of state, and an action that updates the UI for that slice. Only segments whose
selected value has changed are called on each transition.
public sealed class AppProjection : StateProjectionBase<AppState>
{
public AppProjection(MainWindow window)
{
Segment(s => s.Auth, auth => ProjectAuth(window, auth));
Segment(s => s.CurrentModule, mod => ProjectNavigation(window, mod));
Segment(s => s.Items, items => ProjectItems(window, items));
}
}
All segments run against the initial state once at startup via ProjectInitial.
The selector return type must implement IEquatable<TSegment>. C# records satisfy this automatically.
Transition-aware segments
When a segment needs the previous value to determine transition behaviour (e.g. animation direction), use the three-parameter overload:
Segment(
s => s.CurrentModule,
mod => SetModuleWithoutAnimation(mod), // initial projection
(old, @new) => AnimateModuleSwitch(old, @new)); // transition projection
projectInitial fires once at startup. projectTransition fires on each state change
that alters the selected value, receiving both old and new values.
Timer Subscriptions
Pre-built helpers for timer-based subscriptions. Use them inside your
SubscriptionStarter delegate to handle specific subscription types:
IDisposable StartSubscription(Sub subscription, Dispatch<Msg> dispatch) =>
subscription switch
{
Sub.RefreshTimer t => TimerSubscription.Start(
interval: t.Interval,
createMessage: () => new Msg.RefreshTick(),
dispatch: dispatch,
dispatcherQueue: _dispatcherQueue),
Sub.SearchDebounce d => DelayedSubscription.Start(
delay: d.Delay,
createMessage: () => new Msg.DebounceComplete(),
dispatch: dispatch,
dispatcherQueue: _dispatcherQueue),
_ => throw new InvalidOperationException($"Unknown subscription: {subscription}")
};
Both return IDisposable. The runtime manages their lifecycle via subscription
diffing - you do not need to dispose them manually.
Property Changed Subscription
Bridges INotifyPropertyChanged events into the MVU loop. Use this to subscribe to
external observable state such as Lugha's LocaleHost or system theme providers.
Sub.LocaleChanged s => PropertyChangedSubscription.Start(
source: localeHost,
propertyName: "Current",
createMessage: () => new Msg.LocaleChanged(localeHost.Current.Culture.Name),
dispatch: dispatch,
dispatcherQueue: _dispatcherQueue),
The dispatcherQueue parameter ensures createMessage runs on the UI thread regardless
of which thread raised the property change event.
Command Debouncer
Wraps a CommandExecutor to debounce high-frequency commands. Each invocation cancels the previous pending execution.
var debounced = new CommandDebouncer<Cmd, Msg>(
delay: TimeSpan.FromMilliseconds(300),
inner: actualExecutor,
dispatcherQueue: dispatcherQueue,
onError: (cmd, ex) => new Msg.CommandFailed(cmd, ex.Message));
// Pass debounced.Execute as the command executor
Call FlushAsync during graceful shutdown to execute any pending debounced command
before disposing. Without this, the last debounced command is lost on disposal.
await debounced.FlushAsync();
debounced.Dispose();
Localisation with Lugha
Lugha is a typed localisation library for .NET 10 with
compile-time-enforced text contracts and CLDR pluralisation. The active locale lives in MVU state;
a projection segment keeps the Lugha LocaleHost in sync so that XAML text bindings update
automatically on locale switch.
Add the locale to your state type:
public sealed record AppState
{
public required IAppLocale Locale { get; init; }
// ...
}
Handle locale switching in Update. Resolve is total — returns the registry's default
locale when no match is found:
Msg.LocaleSwitched m =>
(state with { Locale = registry.Resolve(m.LanguageTag) }, []),
Register a segment in your projection that calls SetLocale when the locale changes:
public sealed class AppProjection : StateProjectionBase<AppState>
{
public AppProjection(MainWindow window, WinUILocaleHost<IAppLocale> localeHost)
{
Segment(s => s.Locale, locale => localeHost.SetLocale(locale));
Segment(s => s.Auth, auth => ProjectAuth(window, auth));
// ...
}
}
For runtime locale changes originating externally (e.g. system language change), use
PropertyChangedSubscription to bridge LocaleHost.PropertyChanged into the MVU loop.
Wire everything at startup:
LocaleRegistry<IAppLocale> registry = LocaleRegistry<IAppLocale>
.Create(new EnGbLocale(), new ArSaLocale())
.Match(ok => ok, err => throw new InvalidOperationException($"Duplicate: {err.Tag}"));
WinUILocaleHost<IAppLocale> localeHost =
LocaleHostFactory.Create(new EnGbLocale(), mainWindow.DispatcherQueue);
var projection = new AppProjection(mainWindow, localeHost);
_runtime = WinUIMvuRuntime
.Create(program, mainWindow.DispatcherQueue)
.WithCommandExecutor(executor)
.WithSubscriptionStarter(starter)
.WithProjection(projection)
.Start();
Bind text and RTL flow direction in XAML:
<Grid FlowDirection="{x:Bind localeHost.FlowDirection, Mode=OneWay}">
<TextBlock Text="{x:Bind localeHost.Current.Navigation.Dashboard, Mode=OneWay}" />
<TextBlock Text="{x:Bind localeHost.Current.Connection.Connected(ViewModel.Host), Mode=OneWay}" />
</Grid>
For system language synchronisation (persistent PrimaryLanguageOverride), call
SystemLanguageSync.TryApply inside the locale segment action. See the
Lugha.WinUI documentation for details.
Related Packages
- Pamba - Framework-agnostic core: contracts, dispatch loop, command/subscription infrastructure.
- Pamba.Testing - Test utilities:
MvuTestRunner.UpdateAndValidateandMvuScenariofor multi-step flow testing.
Licence
Apache License 2.0
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-windows10.0.26100 is compatible. |
-
net10.0-windows10.0.26100
- Microsoft.Windows.SDK.BuildTools (>= 10.0.26100.7705)
- Microsoft.WindowsAppSDK (>= 1.8.260317003)
- Pamba (>= 0.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.