Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WebViewDevKit

A reusable WinForms (.NET Framework) UI dev kit that hosts a single, shared Chromium instance (WebView2) inside a legacy WinForms app, for declarative HTML/CSS/JS screens with modern layout and hardware acceleration — without the memory cost of a WebView2 control per screen.

This repository grew out of a real migration (ComicRack CE — see docs/ARCHITECTURE.md and src/DevKit.ComicRackConsumer), but src/DevKit itself has no ComicRack-specific code, references, or assumptions. If you're bringing this to a different legacy WinForms app, everything below applies to you; skip src/DevKit.ComicRackConsumer entirely.

Project layout

Project Assembly What it is
src/DevKit WebViewDevKit.dll The dev kit itself. No app-specific references. This is what you reference from a new host app.
src/DevKit.TestHarness DevKit.TestHarness.exe Standalone WinForms exe exercising the two hardest sharing cases (route-swapping within one Form, reparenting out to a second Form and back). Good starting point to see the API in use outside any host app.
src/DevKit.BridgeTests DevKit.BridgeTests.exe Headless (off-screen Form) integration test for the native↔web bridge round trip. Exit code 0/1, safe for CI.
src/DevKit.ComicRackConsumer ComicRackConsumer.dll The first real consumer: wires the dev kit into ComicRack CE's live Pages and Library tabs via reflection. The one project allowed to reference ComicRack assemblies. Read this for a worked example, not as part of the kit.

The core pattern

1. One shared host, many "screens." SharedWebViewHost.Instance is a process-wide singleton wrapping exactly one WebView2 control. A "screen" in your app calls AttachAndNavigateAsync(container, route) (or AttachAndNavigateToPathAsync(container, path) to serve your own HTML instead of the core SPA's hash router) to reparent the single control into its own container and navigate it. Never construct a second WebView2 — that's the whole discipline this kit exists to enforce. WebViewEnvironmentService guarantees exactly one CoreWebView2Environment per process and logs a warning (DevKitLog) if that's ever violated.

2. Multiple independently-toggleable screens need the Reparented event. If screen A is showing the host and the user switches to screen B, the host silently moves away from A. Subscribe to SharedWebViewHost.Instance.Reparented (fired with the old container) to restore your own native fallback control's visibility when you lose the host. And always call AttachAndNavigateAsync/AttachAndNavigateToPathAsync again whenever your screen turns itself back on — it's idempotent and cheap, but skipping it on re-toggle is a real bug (the shared host doesn't auto-reattach on its own). This is the single most common bug class once a second screen exists — see docs/ARCHITECTURE.md's Phase 4 notes for the two real bugs this caused and how they were fixed.

3. Native fallback is a feature, not an afterthought. Every migrated screen in DevKit.ComicRackConsumer keeps its original native control alive as a sibling inside the same container — Controls.Add + Dock=Fill, native control's own .Visible flag flipped, never removed/recreated. Toggling "off" just means .Visible = true on the native control again. This is the dev-kit's whole answer to "leave WinForms consumers an opt-out from the Chromium dependency": there is no separate abstraction to learn, just don't tear down what was there before you attach.

4. The bridge is thin and consumer-defined. WebBridge (SharedWebViewHost.Instance.Bridge) only understands {channel, payload|items} envelopes and raw COM host objects — it has zero knowledge of your app's data shapes.

  • AddHostObject(name, obj) / RemoveHostObject(name) — expose a [ComVisible(true)], ClassInterfaceType.AutoDual object as chrome.webview.hostObjects.sync.<name> in JS. Define your own interface first (IYourBridge), per IComicPagesBridge/IComicLibraryBridge for the worked example — this keeps the COM-visible surface intentional.
  • PostBatch<T>(channel, items) — send many items in ONE call. Prefer this over one bridge call per row; it's cheap to get right up front and expensive to retrofit.
  • PostMessage(channel, payload) / MessageReceived event — single native→web push, and web→native messages respectively.
  • On the JS side, Web/bridge.js provides DevKitBridge.on(channel, fn)/send/sendBatch. Real gotcha already hit twice: bridge.js's own devkit.ready send only pings native — nothing posts a matching message back to the page, so DevKitBridge.on('devkit.ready', render) never fires. Call your page's render() directly instead of waiting on that round trip (see library.html/ comicpages.html for the fixed pattern), and register host objects before navigating, not after (a race otherwise lets page script run before the host object exists).

5. Diagnostics. DevKitLog is an in-memory + Debug.WriteLine + event logger with no file I/O by design (a WinForms-embedded plugin often has nowhere sane to assume a log file lives). Add your own file sink by subscribing DevKitLog.Logged — see ConsumerDiagnostics.EnsureInstalled() for a worked example, including the idempotent-guard-called-from-every-entry-point pattern (a static constructor only runs the first time that type is touched, which silently produces zero log output for every other screen if you rely on it — call EnsureInstalled() explicitly instead). MemoryDiagnosticPanel reports Process.WorkingSet64 + live msedgewebview2.exe count; wire it into a debug-only panel to catch a leaked second instance early.

6. User-data folder isolation. WebViewEnvironmentService.HostAppName (default "WebViewDevKit") names the folder under %LocalAppData% where the shared browser profile lives. If more than one app on the same machine embeds this dev kit, set HostAppName to something app-specific before your first call that touches the environment (EnsureInitializedAsync/AttachAndNavigateAsync/etc.) so the two apps don't share a WebView2 profile.

Getting started in a new host app

  1. Reference src/DevKit/DevKit.csproj (or the built WebViewDevKit.dll + its Microsoft.Web.WebView2 NuGet dependency).
  2. Put your HTML/CSS/JS under a Web/ folder with CopyToOutputDirectory content items (see DevKit.csproj's own Content Include="Web\**\*.*" — cross-project content items merge into one output folder automatically if a consumer project does the same, no manual step needed).
  3. Call await SharedWebViewHost.Instance.AttachAndNavigateToPathAsync(yourContainer, "yourpage.html") from wherever your screen becomes visible.
  4. Define a [ComVisible(true)] interface + class for whatever native data/actions your page needs, register it via SharedWebViewHost.Instance.Bridge.AddHostObject(...) before navigating.
  5. Keep your native control (if any) as a sibling, subscribe Reparented to restore it, re-attach on every re-toggle.

src/DevKit.TestHarness is the smallest working example of steps 1–3 with no app integration at all — read HarnessForm.cs/SettingsHarnessForm.cs first if you want to see the pattern in isolation before reading the ComicRack-specific consumer.

About

Reusable WinForms + WebView2 UI hosting kit: a single shared Chromium instance, JS-side route switching, and a thin native<->web bridge, for legacy .NET Framework WinForms apps.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages