Getting Started
Install the package
Install the library from NuGet before wiring it into your application.
Install-Package RoyalApps.Community.ExternalApps.WinFormsdotnet add package RoyalApps.Community.ExternalApps.WinFormsInitialize the library
Call ExternalApps.Initialize() once when your application starts and ExternalApps.Cleanup() once during shutdown.
Add the host control
Add ExternalAppHost to a form or container and size it like any other WinForms control.
Start a session
Before you call Start(...), subscribe to WindowSelectionRequested so your code can choose the correct window as soon as selection begins.
Create ExternalAppOptions and call Start(...) on the host.
using System;
using RoyalApps.Community.ExternalApps.WinForms.Embedding;
using RoyalApps.Community.ExternalApps.WinForms.Options;
var options = new ExternalAppOptions
{
Launch =
{
Executable = @"%windir%\system32\cmd.exe",
KillOnClose = true,
},
Embedding =
{
Mode = EmbedMethod.Window,
StartEmbedded = true,
IncludeWindowChromeDimensions = true,
},
Selection =
{
Timeout = TimeSpan.FromSeconds(10),
PollInterval = TimeSpan.FromMilliseconds(250),
},
};
externalAppHost.Start(options);Embedding.IncludeWindowChromeDimensions controls whether the embedded window's title bar and frame dimensions are included when sizing it to the host. Leave it enabled when you want the target app's client area to fill the host bounds. Disable it if the target window behaves better when the requested bounds are applied directly.
Launch.Executable can be either:
- a full path such as
C:\Windows\System32\notepad.exe - a command name such as
notepad.exeorpwsh
When the library can use shell execution, command names are launched the same way they would be from Windows shell resolution. When direct process creation is required, for example because credentials or custom environment variables are configured, the library resolves the executable against PATH.
You can also provide custom environment variables:
options.Launch.EnvironmentVariables["MY_TOOL_MODE"] = "demo";Launch.Executable and Launch.WorkingDirectory support %VAR% expansion, and Launch.EnvironmentVariables participate in that expansion.
When Launch.UseExistingProcess is enabled, the library skips process creation and only performs candidate discovery against windows that already exist.
Select the correct window
The library no longer auto-matches windows from configuration. Handle WindowSelectionRequested and choose a window from the provided candidate list.
using System.Linq;
using RoyalApps.Community.ExternalApps.WinForms.Embedding;
using RoyalApps.Community.ExternalApps.WinForms.Options;
externalAppHost.WindowSelectionRequested += (_, e) =>
{
var candidate = e.NewlyDiscoveredCandidates
.Concat(e.Candidates)
.Where(window => window.IsVisible && window.IsTopLevel)
.FirstOrDefault(window =>
window.ProcessId == e.StartedProcessId ||
string.Equals(
System.IO.Path.GetFileName(window.ExecutablePath),
System.IO.Path.GetFileName(e.RequestedExecutablePath),
StringComparison.OrdinalIgnoreCase));
if (candidate?.PrefersExternalHosting == true)
{
Console.WriteLine(candidate.EmbeddingCompatibilityWarning);
}
if (candidate is not null)
e.SelectWindow(candidate);
};The event is raised repeatedly until your code selects a window or the configured timeout expires.
For modern or packaged desktop apps, inspect:
ExternalWindowCandidate.PrefersExternalHostingExternalWindowCandidate.EmbeddingCompatibilityWarningWindowSelectionRequestEventArgs.RequestedExecutablePathWindowSelectionRequestEventArgs.NewlyDiscoveredCandidates
When those values are populated, the library is warning you that Win32 reparenting may be unstable and that the selected window may need to remain external.
If reparenting still fails after selection, the library leaves the window external, logs a warning, and keeps the session alive instead of treating startup as a fatal error.
There is no dedicated EmbedMethod.External mode. If selection succeeds but reparenting fails, the library automatically keeps the selected window external.
Use ExternalAppHost.AttachmentState to distinguish:
None: no window is currently attachedExternal: a window was selected but is not currently embeddedDetached: a selected window was detached from the hostEmbedded: the selected window is currently embedded
React to lifecycle events
ApplicationStartedindicates startup and initial embedding completed.ApplicationClosedindicates the tracked process exited or startup failed.ApplicationActivatedindicates the tracked window received focus.WindowTitleChangedindicates the tracked window caption changed.WindowSelectionRequestedis raised until your code selects a candidate or the timeout elapses.
