Goal
NativeControlHost, EmbeddableControlRoot, and platform services (IWindowingPlatform, tray icons, system dialogs) to build hybrid applications.Why this matters
Prerequisites
Avalonia abstracts windowing via interfaces in Avalonia.Controls.Platform and Avalonia.Platform:
| Interface | Location | Purpose |
|---|---|---|
IWindowingPlatform |
external/Avalonia/src/Avalonia.Controls/Platform/IWindowingPlatform.cs |
Creates windows, embeddable top levels, tray icons |
INativeControlHostImpl |
platform backends (Win32, macOS, iOS, Browser) | Hosts native HWND/NSView/UIViews inside Avalonia (NativeControlHost) |
ITrayIconImpl |
backend-specific | Implements tray icons (PlatformManager.CreateTrayIcon) |
IPlatformStorageProvider, ILauncher |
Avalonia.Platform.Storage |
File pickers, launchers across platforms |
IApplicationPlatformEvents |
Avalonia.Controls.Platform |
System-level events (activation, protocol handlers) |
PlatformManager coordinates these services and surfaces high-level helpers (tray icons, dialogs). Check TopLevel.PlatformImpl to access backend-specific features.
NativeControlHost (external/Avalonia/src/Avalonia.Controls/NativeControlHost.cs) lets you wrap native views:
CreateNativeControlCore(IPlatformHandle parent) to instantiate native widgets (Win32 HWND, NSView, Android View).INativeControlHostImpl from the current TopLevel.TryUpdateNativeControlPosition translates Avalonia bounds into platform coordinates and resizes the native child.Example (Win32 HWND):
public class Win32WebViewHost : NativeControlHost
{
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
var hwnd = Win32Interop.CreateWebView(parent.Handle);
return new PlatformHandle(hwnd, "HWND");
}
protected override void DestroyNativeControlCore(IPlatformHandle control)
{
Win32Interop.DestroyWindow(control.Handle);
}
}
Guidelines:
BoundsProperty) and calling the platform API to adjust scaling.NativeControlHandleChanged for interop with additional APIs (e.g., hooking message loops).NativeControlHostAutomationPeer helps but you may need custom peers.EmbeddableControlRoot (external/Avalonia/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs) wraps a TopLevel that can live in non-Avalonia environments:
ITopLevelImpl supplied by platform-specific hosts (WinFormsAvaloniaControlHost, X11 XEmbed, Android AvaloniaView, iOS AvaloniaView).Prepare() to initialize the logical tree and run the initial layout pass.StartRendering/StopRendering to control drawing when the host window shows/hides.EnforceClientSize ensures Avalonia matches the host surface size; disable for custom measure logic.Examples:
WinFormsAvaloniaControlHost hosts EmbeddableControlRoot inside Windows Forms. Remember to call InitAvalonia() before creating controls.XEmbedPlug uses EmbeddableControlRoot to embed into foreign X11 windows (tooling, remote previews).Avalonia.Android.AvaloniaView and Avalonia.iOS.AvaloniaView wrap EmbeddableControlRoot to integrate with native UI stacks.Interop tips:
Content property to your native layer for dynamic view injection.TabStop and forwards focus events to the Avalonia root.Avalonia relies on MicroCom to generate COM-compatible wrappers. When embedding on Windows (drag/drop, menus, Win32 interop):
Avalonia.MicroCom.CallbackBase as the base for custom COM callbacks; it handles reference counting and error reporting.OleDropTarget and native menu exporters in Avalonia.Win32 demonstrate wrapping Win32 interfaces without hand-written COM glue.You rarely need to touch MicroCom directly, but understanding it helps when diagnosing drag/drop or accessibility issues on Windows.
Avalonia's remote protocol (external/Avalonia/src/Avalonia.Remote.Protocol) powers the XAML previewer and custom remoting scenarios.
RemoteServer (external/Avalonia/src/Avalonia.Controls/Remote/RemoteServer.cs) wraps an EmbeddableControlRoot backed by RemoteServerTopLevelImpl. It responds to transport messages (layout updates, pointer events) from a remote client.BsonTcpTransport), streams (BsonStreamTransport), or custom IAvaloniaRemoteTransportConnection implementations.Avalonia.DesignerSupport components to spin up preview hosts; they bind to IWindowingPlatform stubs suitable for design-time.RemoteWidget hosts the mirrored visual tree. It pairs with RemoteServer to marshal input/output.ITransport when you need alternate channels (named pipes, WebSockets). The protocol is message-based, so you can plug in encryption or compression as needed.Potential use cases:
Security note: remote transports expose the UI tree—protect endpoints if you ship this beyond trusted tooling.
IWindowingPlatform.CreateTrayIcon() supplies backend-specific tray icon implementations. Use PlatformManager.CreateTrayIcon() to instantiate one:
var trayIcon = PlatformManager.CreateTrayIcon();
trayIcon.Icon = new WindowIcon("avares://Assets/tray.ico");
trayIcon.ToolTipText = "My App";
trayIcon.Menu = new NativeMenu
{
Items =
{
new NativeMenuItem("Show", (sender, args) => mainWindow.Show()),
new NativeMenuItem("Exit", (sender, args) => app.Shutdown())
}
};
trayIcon.IsVisible = true;
Other services:
StorageProvider (Chapter 16) uses platform storage APIs; embed scenarios must supply providers in DI.SystemDialog classes fallback to managed dialogs when native APIs are unavailable.IApplicationPlatformEvents handles activation (protocol URLs, file associations). Register via AppBuilder extensions.SystemNavigationManager handles back-button events; ensure UsePlatformDetect registers the appropriate lifetime.Window exposes SystemDecorations, ExtendClientAreaToDecorationsHint, WindowTransparencyLevel, and the Chrome.WindowChrome helpers so you can blend custom title bars with OS hit testing. Always provide resize grips and fall back to system chrome when composition is disabled.Avalonia.Browser.AvaloniaView hosts EmbeddableControlRoot atop WebAssembly; NativeControlHost implementations for the browser route to JS interop.AvaloniaView provides native controls (Android View, iOS UIView) embedding Avalonia UI. Use SingleViewLifetime to tie app lifetimes to host platforms.AppBuilder.AndroidLifecycleEvents / AppBuilder.iOS).OffscreenTopLevel (external/Avalonia/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs) allows rendering to a framebuffer without showing a window—useful for:
Pair with RenderTargetBitmap to save results.
NativeControlHost. Ensure resize and DPI updates work.EmbeddableControlRoot. Swap Avalonia content dynamically and synchronize focus/keyboard.RemoteServer with a TCP transport and connect using the Avalonia preview client to render a view remotely.OffscreenTopLevel + RenderTargetBitmap and compare results in a unit test.Document interop boundaries (threading, disposal, event forwarding) for your team.
EmbeddableControlRoot, tray icons, remote transports) to release native resources.BuildAvaloniaApp().SetupWithoutStarting()) before embedding in native shells.TopLevel.PlatformImpl?.TryGetFeature<IDpiProvider>() or subscribe to scaling changes.NativeControlHost, guard against parent changes; detach native handles during visual tree transitions to avoid orphaned HWNDs.NSApplication.ActivateIgnoringOtherApps when needed).external/Avalonia/src/Avalonia.Controls/NativeControlHost.csexternal/Avalonia/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.csexternal/Avalonia/src/Avalonia.Controls/Platform/PlatformManager.csexternal/Avalonia/src/Avalonia.Controls/Remote/RemoteServer.cs, external/Avalonia/src/Avalonia.Controls/Remote/RemoteWidget.cs, external/Avalonia/src/Avalonia.Remote.Protocol/*external/Avalonia/src/Windows/Avalonia.Win32/Win32Platform.csexternal/Avalonia/src/Browser/Avalonia.Browser/AvaloniaView.cs, external/Avalonia/src/Android/Avalonia.Android/AvaloniaView.cs, external/Avalonia/src/iOS/Avalonia.iOS/AvaloniaView.csexternal/Avalonia/src/Avalonia.MicroCom/CallbackBase.cs, external/Avalonia/src/Windows/Avalonia.Win32/OleDropTarget.csexternal/Avalonia/src/Avalonia.Controls/Chrome/WindowChrome.cs, external/Avalonia/src/Avalonia.Controls/Window.csNativeControlHost coordinate INativeControlHostImpl and what events trigger repositioning?IWindowingPlatform expose, and how do you use them to create tray icons or embeddable top levels?What's next