Runic.Application.Views.Wpf
0.8.0-preview.6
dotnet add package Runic.Application.Views.Wpf --version 0.8.0-preview.6
NuGet\Install-Package Runic.Application.Views.Wpf -Version 0.8.0-preview.6
<PackageReference Include="Runic.Application.Views.Wpf" Version="0.8.0-preview.6" />
<PackageVersion Include="Runic.Application.Views.Wpf" Version="0.8.0-preview.6" />
<PackageReference Include="Runic.Application.Views.Wpf" />
paket add Runic.Application.Views.Wpf --version 0.8.0-preview.6
#r "nuget: Runic.Application.Views.Wpf, 0.8.0-preview.6"
#:package Runic.Application.Views.Wpf@0.8.0-preview.6
#addin nuget:?package=Runic.Application.Views.Wpf&version=0.8.0-preview.6&prerelease
#tool nuget:?package=Runic.Application.Views.Wpf&version=0.8.0-preview.6&prerelease
Runic.Application.Views.Wpf
Experimental (RUNICWPF001)
optional WPF child presentation for a Runic web View.
Targets .NET 10 and Windows. The containing WPF application owns its dispatcher,
shell, navigation engine, ViewModels, services and native dialogs. Toolkit,
ReactiveUI, or plain observable models can use the same presentation adapter.
Suppress RUNICWPF001 where you use it; the package suppresses it in the code
that the WPF markup compiler generates for a named RunicWebView.
The XAML namespace is https://runic-artifex.eu/xaml/views, with the prefix rv.
Host a web View in XAML
RunicViewHost presents an existing model through its generated web View:
<UserControl xmlns:rv="https://runic-artifex.eu/xaml/views"
xmlns:local="clr-namespace:MyApp">
<rv:RunicViewHost Model="{Binding}">
<local:NativeEditorView />
</rv:RunicViewHost>
</UserControl>
Give it the application's service provider once, on the window, or on the host
from a View that received its provider (host.Services = services):
RunicViewHost.SetServices(mainWindow, services);
The host owns the hosting lifecycle:
- It opens when it is loaded with a
ModelandServices, and waits for the native child'sLoadedbefore starting the presentation. - It closes when it is unloaded or
Modelbecomesnull. ChangingModel,Services,ModelContext,ContentRoot,EntryPageorSurfaceOptionscloses the session and opens a fresh one. - Changes run one at a time. A newer change cancels an open in progress, and changes that queue behind a running one collapse into the latest.
- Closing releases the Bridge session first, which drains its accepted
operations, then the
DesktopHost, and only then removes and disposes the native child. - When the presentation can't open (no WebView2 Runtime, no Bridge registered
for the model, a missing frontend), the host shows its content, the
fallback, sets
StatetoFailedand keeps the exception inError.ReloadAsync()retries, and also reconnects an open View through a fresh session.
ContentRoot (default www) is the built frontend's directory relative to the
application's base directory, and EntryPage the page to open in it (default
index.html). Set SurfaceOptions for anything else. ModelContext defaults
to the IRunicModelContext registered in Services; pass the navigator's
dispatcher context when navigation and the web View present the same model.
State, Error and StateChanged report progress; Surface is the open
presentation's DesktopSurface, and WaitForPresentationAsync() returns when
no change is pending. The host borrows the model, services and context: it
never creates or disposes a ViewModel, a scope or a navigation entry.
Lower-level API
RunicViewHost composes RunicWebView, DesktopHost and
CreateWpfViewAsync. Use them directly only when you need another lifecycle.
The view and host live as long as the presentation, so keep them in fields and
dispose them in this order when the control unloads, rather than with
await using in the method that opens them:
var webView = new RunicWebView();
editorContainer.Content = webView;
// The native child exists only after layout: open from its Loaded event.
webView.Loaded += async (_, _) =>
{
_desktop = await DesktopHost.StartAsync(new DesktopHostOptions
{
WindowHostFactory = webView.CreateWindowHostFactory(),
});
_view = await services.CreateWpfViewAsync(_desktop,
new DesktopSurfaceOptions { Content = new DesktopContent.Directory(Path.Combine(AppContext.BaseDirectory, "www")) },
existingEditorViewModel, modelContext: existingDispatcherContext);
await _view.OpenAsync();
};
// On unload: session, then host, then the native child.
await _view.DisposeAsync();
await _desktop.DisposeAsync();
editorContainer.Content = null;
webView.Dispose();
You then also serialize swaps, cancel an open on unload and fall back yourself.
Register generated Bridges using AddRunicViews() from the application's
generated composition. A logical RunicView<TModel> is sufficient; no additional
Runic Window or navigation engine is required. The frontend uses the existing
Desktop Views client and generated connect...() function.
Referencing this package turns Bridge generation on, as the other host adapters
do. In a single-project application, declare the View next to the WPF code and
keep the frontend in Frontend/: the build writes the client to
Frontend/src/generated, builds the frontend and copies Frontend/dist to
www/. WPF's temporary *_wpftmp markup-compile project reuses the generated
code instead of generating again. Views and ViewModels cannot depend on WPF
types; the generator skips such types. A ViewModel may still have WPF-typed
members and methods that the Bridge does not expose, such as a [RunicIgnore]
ImageSource; one that the Bridge would expose is reported as
RUNICBRIDGE005
at that member. When the Views live in a WPF-free model
project that references Runic.Application.Views, that project generates and builds
the frontend, and the WPF application copies its dist to www/. The
application can then set RunicApplicationFrontendGenerationEnabled to false. If
the model's composition has the application's default name
(<application project>.RunicBridgeComposition, common in apps from
0.7.0-preview.6), the application's AddRunicViews() call uses it. An application
that also declares Views gets
RUNICBRIDGE018
until one of the two compositions is renamed. See the
Runic.Application.Views README's build properties for the frontend folder and
diagnostics.
The adapter uses DesktopHost's existing asset server, authenticated surface,
WebSocket transport and Views session. Its HwndHost reuses Runic Desktop's
native WebView2 controller and native loader, including origin-bound document
start credentials and permission policy. Install Microsoft Edge WebView2 Runtime
on the Windows machine. It creates no separate WPF window or UI thread.
CreateWpfViewAsync borrows the exact supplied model and service provider. It
does not create or dispose a DI scope, ViewModel, navigator, or application-owned
model context. Supply the shared dispatcher model context when navigation and
web bindings present the same model. Disposing the child or unloading its
control closes presentation admission promptly and drains accepted operations
before releasing its session. Keep application services alive until that
completion. Reloading the page reconnects within the same presentation; a new
view after unload uses a fresh surface/session.
Each factory presents one surface at a time in its control. WPF owns layout,
DPI, visibility, focus routing and the containing window. The child advertises
focus only; it does not expose a top-level native handle or minimize, maximize,
move, or resize the shell. Window layout options, including
DesktopWindowOptions.Placement, report warnings and are configured on the WPF
shell instead; the WPF Window saves and restores its own placement with
RestoreBounds, WindowState and its LocationChanged, SizeChanged and
StateChanged events, and the child presentation reports no
DesktopWindow.State. Files dropped on the child WebView do not reach the WPF
shell's Drop event, which the WebView's window covers; the borrowed ViewModel
receives them, with their paths, when it implements IRunicFileDropTarget. Browser fallback is not supported by
WpfBridgeView.OpenAsync. Standard HWND airspace constraints apply; WPF overlays
cannot draw over the child WebView. Tab enters the document's first or last tab
stop. WebView2 moves that focus asynchronously: the document's focused element
changes shortly after TabInto returns, not during the call. Returning from the document's tab boundary to the surrounding WPF controls
is not yet integrated; mouse focus and explicit shell focus actions remain
available. Native authentication, reload, focus and unload checks run on Windows;
Linux can compile the Windows target and run the linked portable session checks.
Bring the window forward for a second launch
A WPF application that keeps one copy running with
ApplicationInstance.ClaimAsync brings its window forward when another launch
arrives with WpfPlatformServices.PresentAsync(window), from any thread:
// For each launch the running instance receives:
var result = await WpfPlatformServices.PresentAsync(mainWindow);
It shows a hidden window, or one not shown yet, with Window.Show() and
restores a minimized one to its previous size, then asks Windows to put it in
the foreground on the window's dispatcher, as
PlatformServices.PresentAsync does for Runic Desktop windows. Windows lets the
running instance take the foreground because the forwarding launch allows it
(AllowSetForegroundWindow); otherwise the taskbar button flashes and the
result is Failed with PermissionDenied. A closed window, or one whose
dispatcher shut down, reports OwnerClosed. Window.Activate() alone neither
restores the window nor reports a refusal.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-windows7.0 is compatible. |
-
net10.0-windows7.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.12)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.12)
- Microsoft.Web.WebView2 (>= 1.0.4258.31)
- Runic.Application.Views.Desktop (= 0.8.0-preview.6)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.8.0-preview.6 | 191 | 10/19/2026 |
| 0.8.0-preview.5 | 170 | 10/17/2026 |
| 0.8.0-preview.4 | 464 | 10/17/2026 |
| 0.8.0-preview.3 | 444 | 10/15/2026 |
| 0.8.0-preview.2 | 79 | 10/15/2026 |
| 0.8.0-preview.1 | 111 | 10/15/2026 |
| 0.7.0-preview.9 | 368 | 10/14/2026 |
| 0.7.0-preview.8 | 86 | 10/12/2026 |
| 0.7.0-preview.7 | 53 | 10/12/2026 |