Nuri.WPF
0.3.0
dotnet add package Nuri.WPF --version 0.3.0
NuGet\Install-Package Nuri.WPF -Version 0.3.0
<PackageReference Include="Nuri.WPF" Version="0.3.0" />
<PackageVersion Include="Nuri.WPF" Version="0.3.0" />
<PackageReference Include="Nuri.WPF" />
paket add Nuri.WPF --version 0.3.0
#r "nuget: Nuri.WPF, 0.3.0"
#:package Nuri.WPF@0.3.0
#addin nuget:?package=Nuri.WPF&version=0.3.0
#tool nuget:?package=Nuri.WPF&version=0.3.0
Nuri
Nuri is a C# MVU UI library. Components describe UI with platform-neutral virtual elements, and renderer adapters materialize those descriptions into native controls.
The supported renderer paths are WPF through Nuri.WPF and Duxel on Windows through Nuri.Duxel.Windows. The platform-neutral immediate-mode Duxel renderer is packaged as Nuri.Duxel and is referenced by the Windows host.
Duxel remains the primary backend parity and expansion priority. The existing Avalonia adapter remains available as a regression baseline.
What Nuri Does
- Component
Render()methods return virtual UI descriptions, not native WPF controls. - State changes re-render the dirty component subtree when possible.
- Virtual trees are diffed into patch operations and applied by the renderer.
- Keys preserve row/component identity across list insert, remove, move, and replace.
- Events, values, animation descriptions, routing, and lifecycle hooks live in Core.
- WPF-specific control creation, property mapping, event mapping, and animation materialization stay in
Nuri.WPF.
Project Layout
src/Nuri: platform-neutral runtime, DSL, virtual DOM, diffing, patch operations, values, events, routing, and lifecycle hooks.src/Nuri.WPF: WPF renderer adapter, WPF control registry, WPF property/event mapping, WPF animation materialization, and application host.src/Nuri.WPF.Diagnostics: WPF runtime inspector package andUseAttachDevTools()integration.src/Nuri.Avalonia.Diagnostics: Avalonia runtime inspector package andUseAttachDevTools()integration.src/Nuri.Duxel/Nuri.Duxel: Duxel immediate-mode renderer adapter overDuxel.App.src/Nuri.Duxel/Nuri.Duxel.Windows: Windows application/frame integration overDuxel.Windows.App.src/Nuri.Duxel/Nuri.Duxel.Diagnostics: Duxel runtime inspector package andUseAttachDevTools()integration.src/Nuri.WPF.PreviewHostandsrc/Nuri.Duxel/Nuri.Duxel.PreviewHost: out-of-process preview hosts.src/Nuri.Avalonia: existing Avalonia renderer adapter retained as a regression baseline rather than the next backend expansion target.src/Nuri.Formatter: conservative C# formatter used by the Visual Studio integration.samples/WPF: focused WPF samples that exercise concrete behavior.samples/Duxel: focused Duxel samples used to drive the next backend implementation slices.tests/Nuri.Tests: lightweight Core behavior tests.perf: performance sanity harnesses.
Basic WPF App
Start a Nuri WPF app through NuriApplication:
using Nuri.WPF;
namespace NuriSample;
internal static class Program
{
private static void Main()
{
NuriApplication.Run<CounterComponent>("Nuri Sample", width: 480, height: 320);
}
}
NuriApplication.Run<TComponent> uses the current thread when it is already STA. Otherwise it creates and owns a WPF STA application thread, so a Program.cs entry point does not need [STAThread] or manual SetApartmentState calls. APIs that return native WPF objects, such as Show and Attach, still need to be called from their owning WPF STA thread.
Create components by inheriting Component and returning IElement from Render():
using Nuri.UI.Dsl;
namespace NuriSample;
public sealed class CounterComponent : Component
{
public override IElement Render()
{
var (count, setCount) = useState(0);
return Div(
Button($"Count: {count}", () => setCount(current => current + 1)),
Button("Reset", () => setCount(_ => 0))
);
}
}
useState setters receive the current stored value. Use setCount(current => current + 1) for updates based on existing state, and _ => value to assign a specific value.
WPF-familiar factory aliases are available while still producing platform-neutral Nuri elements:
Button("Save", Save);
TextBox().OnTextChanged(value => setText(_ => value));
CheckBox("Enabled", value => setEnabled(_ => value));
RadioButton("Option A", value => setSelected(_ => value));
ToggleButton("Pinned", value => setPinned(_ => value));
PasswordBox();
State And Hooks
Use useState for local component state:
var (text, setText) = useState(string.Empty);
return TextBox(text, value => setText(_ => value));
Use useEffect for post-render effects. Omitting dependencies runs after every render. Passing [] runs on mount and cleans up on unmount.
If no cleanup is needed, use the Action overload and do not return anything:
useEffect(() =>
{
TrackRender();
}, [route]);
Return a cleanup action only when the effect owns something that should be disposed or unsubscribed:
useEffect(() =>
{
StartSubscription();
return StopSubscription;
}, []);
Track dependencies with C# collection expressions:
useEffect(() =>
{
Refresh(route);
return null;
}, [route]);
Routing
Use useNavigation when a component owns local navigation state:
var (navigation, navigator) = useNavigation("overview");
return Div(
Button("Overview", () => navigator.Navigate("overview")),
Button("Details", () => navigator.Navigate("details")),
Button("Back", navigator.GoBack),
Router(navigation,
Route("overview", () => Text("Overview")),
Route("details", () => Text("Details")))
);
Navigator supports:
Navigate(route): push current route and move toroute.Replace(route): change route without adding history.GoBack(): return to the previous route when available.CanGoBack: inspect whether the back stack has entries.
Layout
Use Grid(...) with fluent .Rows(...) and .Columns(...) for layout:
return Grid(
Header().Row(0).ColumnSpan(2),
Sidebar().Row(1).Column(0),
Content().Row(1).Column(1)
)
.Rows("Auto,*")
.Columns(240, Star);
Numeric row and column values use pixels. A comma-separated string can combine
pixel values with Auto, *, and weighted star values such as 2*.
The explicit Pixels(240) form remains available for compatibility.
Scroll is a single-content viewport. Put vertical layout and spacing on its one Column child:
return Grid(Rows(Auto, Star),
Toolbar().Row(0),
Div(DivTypes.Scroll,
Div(rows).Spacing(8))
.Row(1));
Keys
Use explicit keys for rows and components whose identity must survive reorder, filter, edit, or remove operations:
Div(items.Select(item =>
(IElement)new TodoItemComponent(item).Key(item.Id)
).ToArray());
Name remains a key fallback for compatibility, but new code should prefer .Key("...").
Animation
Call .Transition(...) after the property setter that should animate:
Text("Play")
.Margin(30, isPlaying ? 0 : 100, 0, 0)
.Transition(500, EasingValue.CubicInOut);
Use .Transitions("Margin", ...) only when the animated property needs to be selected explicitly.
Basic Duxel App
Run a Duxel application on Windows through the Nuri.Duxel.Windows host:
using Nuri.Duxel;
var app = NuriApplication.Create<CounterComponent>(
title: "Nuri Duxel",
width: 720,
height: 480);
app.Run();
Nuri.Duxel owns immediate-mode projection, while Nuri.Duxel.Windows owns the native window, input bridge, and frame-loop integration.
Runtime Diagnostics
Reference the diagnostics package that matches the renderer: Nuri.WPF.Diagnostics, Nuri.Avalonia.Diagnostics, or Nuri.Duxel.Diagnostics. All expose UseAttachDevTools(), default to F12, and compile the same platform-neutral inspector UI into the renderer-specific package.
var app = NuriApplication.Create<AppComponent>("Nuri App", 940, 620);
#if DEBUG
app.UseAttachDevTools();
#endif
app.Run();
Import Nuri.WPF.Diagnostics for WPF, Nuri.Avalonia.Diagnostics for Avalonia, or Nuri.Duxel.Diagnostics for Duxel. The matching samples are Nuri.WPFDiagnosticsSample and Nuri.DuxelDiagnosticsSample.
WPF Samples
Run samples with dotnet run --project ... -c Release.
dotnet run --project "samples\WPF\Nuri.TodoValidationSample\Nuri.TodoValidationSample.csproj" -c Release
dotnet run --project "samples\WPF\Nuri.SettingsPreferencesSample\Nuri.SettingsPreferencesSample.csproj" -c Release
dotnet run --project "samples\WPF\Nuri.ModalDialogSample\Nuri.ModalDialogSample.csproj" -c Release
dotnet run --project "samples\WPF\Nuri.CommandPaletteSample\Nuri.CommandPaletteSample.csproj" -c Release
dotnet run --project "samples\WPF\Nuri.ExplorerTreeSample\Nuri.ExplorerTreeSample.csproj" -c Release
dotnet run --project "samples\WPF\Nuri.VirtualExplorerTreeSample\Nuri.VirtualExplorerTreeSample.csproj" -c Release
dotnet run --project "samples\WPF\Nuri.WPFDiagnosticsSample\Nuri.WPFDiagnosticsSample.csproj" -c Debug
dotnet run --project "samples\WPF\RouterSample\RouterSample.csproj" -c Release
Sample coverage:
Nuri.TodoValidationSample: controlled input, list diff, filter, item edit, remove, keyed rows, scroll layout.Nuri.SettingsPreferencesSample: checkbox, radio, toggle, form grouping, validation.Nuri.ModalDialogSample: mount/unmount, effect cleanup, overlay layering.Nuri.CommandPaletteSample: controlled search input, keyboard events, filtered keyed list, selection, command execution.Nuri.ExplorerTreeSample: recursive keyed components, subtree lifecycle cleanup, rename, add, and delete behavior.Nuri.VirtualExplorerTreeSample: 10,101 generated tree nodes flattened into a fixed-extent WPF recycling viewport.RouterSample: router, nested router,useNavigation, effects, keyed list behavior.Nuri.WPFDiagnosticsSample: WPF runtime inspector, hooks, stores, patch counts, console capture, and component highlighting.
Duxel Samples
dotnet run --project "samples\Duxel\Nuri.DuxelSample\Nuri.DuxelSample.csproj" -c Release
dotnet run --project "samples\Duxel\Nuri.DuxelExplorerTreeSample\Nuri.DuxelExplorerTreeSample.csproj" -c Release
dotnet run --project "samples\Duxel\Nuri.DuxelVirtualExplorerTreeSample\Nuri.DuxelVirtualExplorerTreeSample.csproj" -c Release
dotnet run --project "samples\Duxel\Nuri.DuxelDiagnosticsSample\Nuri.DuxelDiagnosticsSample.csproj" -c Debug
Validation
Build the solution after meaningful changes:
dotnet build "Nuri.sln" -c Release
Run the Core, renderer, and diagnostics tests:
dotnet run --project "tests\Nuri.Tests\Nuri.Tests.csproj" -c Release
dotnet run --project "tests\Nuri.RendererTests\Nuri.RendererTests.csproj" -c Release
dotnet run --project "tests\Nuri.DuxelRendererTests\Nuri.DuxelRendererTests.csproj" -c Release
dotnet run --project "tests\Nuri.DevToolsTests\Nuri.DevToolsTests.csproj" -c Release
dotnet run --project "tests\Nuri.DuxelDiagnosticsTests\Nuri.DuxelDiagnosticsTests.csproj" -c Release
dotnet run --project "tests\Nuri.FormatterTests\Nuri.FormatterTests.csproj" -c Release
Performance sanity checks:
dotnet run --project "perf\Nuri.Performance\Nuri.Performance.csproj" -c Release -- --label after
dotnet run --project "perf\Nuri.WPFPerformance\Nuri.WpfPerformance.csproj" -c Release -- --label after
dotnet run --project "perf\Nuri.DuxelPerformance\Nuri.DuxelPerformance.csproj" -c Release -- --label after
Patch count matters, especially for keyed reconciliation and reorder behavior.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0-windows7.0 is compatible. net7.0-windows was computed. net7.0-windows7.0 is compatible. net8.0-windows was computed. net8.0-windows7.0 is compatible. net9.0-windows was computed. net9.0-windows7.0 is compatible. net10.0-windows was computed. |
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Nuri.WPF:
| Package | Downloads |
|---|---|
|
Nuri.WPF.Diagnostics
Runtime inspector and diagnostics window for Nuri WPF applications. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Moves the UI/runtime model into the platform-neutral Nuri package and keeps WPF materialization in Nuri.WPF.