Hosting Avalonia User Controls in Windows Forms on DotNet 7+
See original GitHub issueIs your feature request related to a problem? Please describe.
There is a need to gradually migrate some of our WinForms apps to Avalonia. To that end, we need to slowly, piece-by-piece replace WinForms user controls with Avalonia alternatives, eventually replacing the whole main window with one based on Avalonia. This approach requires us to be able to host Avalonia controls within WInForms Forms / UserControls.
So, I was trying to figure out a way to do it. The reverse (hosting WinForms control inside of Avalonia app) seems pretty straight forward. Not so however with the other way around. I stumbled across Avalonia.Win32.Interoperability
nuget package, however it was for an older Avalonia version and more crucially seemed to be targeting .Net Framework (as opposed to DotNet 7+). Researching a bit more I came across: WinFormsAvaloniaControlHost from that same package.
So, I tried using it directly in my DotNet7 project. It was pretty easy to port. However there were 2 remaining issues:
private IntPtr WindowHandle => ((WindowImpl) _root?.PlatformImpl)?.Handle?.Handle ?? IntPtr.Zero;
could not be ported because apparently recentlyWindowImpl
was madeinternal
, nor was there a public interface on it that exposes the neededIPlatformHandle Handle
propety.EmbeddableControlRoot.FocusManager
was not there which I needed to portif (_root.IsFocused) _root.FocusManager.ClearFocus();
, though this seemed less important.
I resolved the first issue with an ugly reflection hack, basically gaining access to ((WindowImpl) _root?.PlatformImpl)?.Handle?.Handle
through reflection, which for the purposes of testing sufficed.
So then I proceeded with a simple code to host my Avalonia control inside of WinForms:
public Form1() {
InitializeComponent();
var avahost = new WinFormsAvaloniaControlHost(); // <-- Implemented in my project as I ported it from https://github.com/AvaloniaUI/Avalonia/blob/master/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs
panel1.Controls.Add(avahost);
avahost.Dock = DockStyle.Fill;
avahost.Content = new UserControl1();
}
However once I ran my program, I would always be getting a pure white background in place where my Avalonia control should be.
Describe the solution you’d like Basically I should just be able to host Avalonia control within any WinForms Form or User Control, so that we can migrate large WinForms app slowly over time, and this scenario is important not just for .Net Framework, but for modern DotNet (i.e. 7+)
- As an aside, for such scenarios it is especially important to be able to access Avalonia control’s native handles (when they exist), such that there is no need to use Reflection hacks. These handles should be part of a public surface APIs.
- Also how should Avalonia be properly initialized for such a scenario (i.e. where the main program / app is WInForms). Do we just use
BuildAvaloniaApp().SetupWithoutStarting();
in our porogram main before we doSystem.Windows.Forms.Application.Run(new Form1());
, or do we do something else?
Describe alternatives you’ve considered There are really no feasible alternatives. Hosting WinForms inside Avalonia - doing it the other way around in other words is not really possible since the whole form would somehow need to be hosted inside of Avalonia. Not to mention there will still be a need to host Avalonia controls inside of WinForms container controls (until the entire UI was migrated to Avalonia).
Additional context Using DotNet 7+ in VS 2022.
Issue Analytics
- State:
- Created 4 months ago
- Comments:19 (15 by maintainers)
Top GitHub Comments
@fitdev thanks! I will do the rest with updating interop package and making it publish to the nuget again.
@fitdev: Doesnt work that easily 😉
PlatformImpl
on TopLevel is anITopLevelImpl
, which itself does not contain the handle. OnlyIWindowBaseImpl
contains the required handle, so the minimum would be to dopublic IPlatformHandle? TryGetPlatformHandle() => ((IWindowBaseImpl?) PlatformImpl)?.Handle;
(trying to getPlatformImpl
as aIWindowBaseImpl
).