RoyalApps.Community.ExternalApps.WinForms 2.0.0-beta.1

Prefix Reserved
This is a prerelease version of RoyalApps.Community.ExternalApps.WinForms.
dotnet add package RoyalApps.Community.ExternalApps.WinForms --version 2.0.0-beta.1
                    
NuGet\Install-Package RoyalApps.Community.ExternalApps.WinForms -Version 2.0.0-beta.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RoyalApps.Community.ExternalApps.WinForms" Version="2.0.0-beta.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RoyalApps.Community.ExternalApps.WinForms" Version="2.0.0-beta.1" />
                    
Directory.Packages.props
<PackageReference Include="RoyalApps.Community.ExternalApps.WinForms" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RoyalApps.Community.ExternalApps.WinForms --version 2.0.0-beta.1
                    
#r "nuget: RoyalApps.Community.ExternalApps.WinForms, 2.0.0-beta.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RoyalApps.Community.ExternalApps.WinForms@2.0.0-beta.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RoyalApps.Community.ExternalApps.WinForms&version=2.0.0-beta.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=RoyalApps.Community.ExternalApps.WinForms&version=2.0.0-beta.1&prerelease
                    
Install as a Cake Tool

External Apps Control

NuGet Version NuGet Downloads .NET

RoyalApps.Community.ExternalApps.WinForms provides a WinForms control that can host windows from external processes.

Version 2.x is a managed rewrite. The package no longer depends on WinEmbed.dll; embedding is implemented in C# with Win32 interop generated via CsWin32.

Screenshot

Warning Cross-process window parenting is not a Microsoft-supported application model. Read Raymond Chen's post about cross-process parent/child windows before shipping this in production.

Installation

Install-Package RoyalApps.Community.ExternalApps.WinForms
dotnet add package RoyalApps.Community.ExternalApps.WinForms

Quick Start

Call ExternalApps.Initialize() once during application startup, place an ExternalAppHost on a form, and start a session with ExternalAppOptions.

using System;
using System.Linq;
using System.Windows.Forms;
using RoyalApps.Community.ExternalApps.WinForms;
using RoyalApps.Community.ExternalApps.WinForms.Embedding;
using RoyalApps.Community.ExternalApps.WinForms.Options;

ExternalApps.Initialize();

var host = new ExternalAppHost
{
    Dock = DockStyle.Fill,
};

host.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);
};

host.Start(new ExternalAppOptions
{
    Launch =
    {
        Executable = @"C:\Windows\System32\notepad.exe",
        KillOnClose = true,
    },
    Embedding =
    {
        Mode = EmbedMethod.Window,
        StartEmbedded = true,
        IncludeWindowChromeDimensions = true,
    },
    Selection =
    {
        Timeout = TimeSpan.FromSeconds(10),
        PollInterval = TimeSpan.FromMilliseconds(250),
    },
});

Call ExternalApps.Cleanup() during application shutdown to release process-tracking resources.

API Model

ExternalAppOptions is split into three areas:

  • Launch: executable path or command name, arguments, working directory, environment variables, credential options, elevation, existing-process reuse, hidden startup, and close behavior.
  • Embedding: whether the selected window should start embedded, whether it should be embedded as a full Window or as a client-area Control, and whether title bar and frame dimensions should be accounted for during sizing.
  • Selection: polling interval and timeout for runtime window discovery.

The host no longer performs built-in title, class, process, or command-line matching from configuration. Instead, it repeatedly raises WindowSelectionRequested with the current list of ExternalWindowCandidate values until your code selects a window or the timeout expires.

Each ExternalWindowCandidate also carries compatibility hints for modern or packaged desktop apps:

  • PrefersExternalHosting: indicates that the window looks like a poor candidate for Win32 reparenting.
  • EmbeddingCompatibilityWarning: explains why Control or Window embedding may be unstable.
  • WindowSelectionRequestEventArgs.RequestedExecutablePath: helps correlate windows when the original launch target hands off to another process.
  • WindowSelectionRequestEventArgs.NewlyDiscoveredCandidates: helps prefer windows that appeared during the current start attempt, which is especially useful when multiple instances of the same app are already open.

If embedding still fails for a selected window, the library leaves the window external, logs a warning, and continues the session instead of failing startup.

If no window is selected before the timeout elapses, the process is left running externally and the session stays unattached.

ExternalAppHost.AttachmentState reports whether the current session is None, External, Detached, or Embedded.

When Launch.Executable is just a command name such as notepad.exe or pwsh, the launcher starts it shell-style when possible. If direct process creation is required, for example because credentials or custom environment variables are configured, the library resolves the executable against PATH.

Launch.Executable and Launch.WorkingDirectory also support %ENVIRONMENT_VARIABLE% expansion. Custom values from Launch.EnvironmentVariables override the current process environment during expansion.

When Launch.UseExistingProcess is enabled, the library skips process creation and only performs discovery/selection against windows that are already present.

Key Events

  • ApplicationStarted: raised when startup completed and any initial embedding work finished.
  • ApplicationClosed: raised when the session closes, the process exits, or startup fails.
  • ApplicationActivated: raised when the tracked window receives focus.
  • WindowSelectionRequested: raised while candidate discovery is running so the consumer can select a window.
  • WindowTitleChanged: raised when the tracked window caption changes.

Host Operations

  • CloseApplication(): closes or terminates the tracked process according to the launch options.
  • DetachApplication(): removes the tracked window from the host control.
  • EmbedApplication(): re-embeds a detached window.
  • FocusApplication(bool force): focuses the tracked window.
  • MaximizeApplication(): maximizes the tracked window.
  • SetWindowPosition(): syncs the tracked window to the host bounds.
  • ShowSystemMenu(Point location): shows the tracked window's system menu.
  • GetWindowScreenshot(): captures the current tracked window as a bitmap.

Embedding Modes

  • EmbedMethod.Control: embeds only the client area. This is often visually cleaner but some applications may not paint focus state correctly.
  • EmbedMethod.Window: embeds the complete native window including its frame and menu. This usually preserves application chrome better, but ALT+TAB behavior can still be imperfect.

Embedding.IncludeWindowChromeDimensions controls whether the embedded window's title bar and frame dimensions are included when the library sizes the window to the host bounds. Leave it enabled when you want the hosted client area to fill the control. Disable it when the target app behaves better with direct bounds applied.

There is no dedicated External embed mode in v2. If a selected window cannot be reparented safely, the session leaves the window external and reports that through logging and session state.

Breaking Changes from v1

  • ExternalAppConfiguration was removed and replaced by ExternalAppOptions.
  • Static matching properties such as title, class, process, and command-line match strings were removed from the library API.
  • Window identification is now host-driven through WindowSelectionRequested.
  • WinEmbed.dll and native packaging assets were removed.
  • The old idea of a dedicated EmbedMethod.External mode is not part of the v2 API. Reparenting failures fall back to leaving the selected window external.

Demo Application

The demo app in src/RoyalApps.Community.ExternalApps.WinForms.Demo shows both embedding modes and logs selection and lifecycle events while hosting multiple external applications.

Testing

Run the unit tests with:

dotnet test src/RoyalApps.Community.ExternalApps.WinForms.Tests/RoyalApps.Community.ExternalApps.WinForms.Tests.csproj

The initial suite covers the selection loop, selection request event args, and the explicit launch/selection result types that drive session startup behavior.

Documentation

The library produces XML documentation for all public APIs. The documentation site is built with VitePress, and the API reference pages are generated from the XML docs during the site build before publishing to GitHub Pages.

Guide pages:

  • docs/articles/getting-started.md
  • docs/articles/selection-strategies.md
  • docs/articles/migrating-from-v1.md
Product Compatible and additional computed target framework versions.
.NET net10.0-windows7.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
2.0.0-beta.1 24 3/14/2026
1.2.1 101 2/12/2026
1.2.0 372 11/21/2025
1.1.2 234 2/12/2025
1.1.1 213 1/20/2025
1.1.0 218 11/14/2024
1.1.0-beta2 107 10/9/2024
1.1.0-beta1 121 9/19/2024
0.3.5 315 6/22/2023
0.3.4 559 9/16/2022
0.3.3 551 9/15/2022
0.3.2 551 9/15/2022
0.3.1 556 6/20/2022
0.3.0 549 5/27/2022
0.2.10 558 5/23/2022
0.1.0 568 5/18/2022