From e9dbf08c84003fbd8e3c845bca220b1c6c592a90 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Mon, 16 Mar 2026 15:05:17 +0100 Subject: [PATCH] Add WinForms message filter for Avalonia windows (#20814) * Add WinForms message filter for Avalonia windows * Do not use filter messages for WinFormsAvaloniaControlHost --- .../EmbedToWinFormsDemo.Designer.cs | 145 +++++++++--------- .../WindowsInteropTest/EmbedToWinFormsDemo.cs | 25 ++- samples/interop/WindowsInteropTest/Program.cs | 3 + .../WindowsInteropTest.csproj | 1 + .../WinForms/WinFormsAvaloniaMessageFilter.cs | 48 ++++++ .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 8 + 6 files changed, 157 insertions(+), 73 deletions(-) create mode 100644 src/Windows/Avalonia.Win32.Interoperability/WinForms/WinFormsAvaloniaMessageFilter.cs diff --git a/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.Designer.cs b/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.Designer.cs index d8b0724520..48087a9058 100644 --- a/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.Designer.cs +++ b/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.Designer.cs @@ -30,87 +30,88 @@ namespace WindowsInteropTest /// private void InitializeComponent() { - this.button1 = new System.Windows.Forms.Button(); - this.monthCalendar1 = new System.Windows.Forms.MonthCalendar(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.groupBox2 = new System.Windows.Forms.GroupBox(); - this.avaloniaHost = new WinFormsAvaloniaControlHost(); - this.groupBox1.SuspendLayout(); - this.groupBox2.SuspendLayout(); - this.SuspendLayout(); - // - // button1 - // - this.button1.Location = new System.Drawing.Point(28, 29); - this.button1.Name = "button1"; - this.button1.Size = new System.Drawing.Size(164, 73); - this.button1.TabIndex = 0; - this.button1.Text = "button1"; - this.button1.UseVisualStyleBackColor = true; - // + OpenWindowButton = new System.Windows.Forms.Button(); + monthCalendar1 = new System.Windows.Forms.MonthCalendar(); + groupBox1 = new System.Windows.Forms.GroupBox(); + groupBox2 = new System.Windows.Forms.GroupBox(); + avaloniaHost = new Avalonia.Win32.Interoperability.WinFormsAvaloniaControlHost(); + groupBox1.SuspendLayout(); + groupBox2.SuspendLayout(); + SuspendLayout(); + // + // OpenWindowButton + // + OpenWindowButton.Location = new System.Drawing.Point(33, 33); + OpenWindowButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + OpenWindowButton.Name = "OpenWindowButton"; + OpenWindowButton.Size = new System.Drawing.Size(191, 84); + OpenWindowButton.TabIndex = 0; + OpenWindowButton.Text = "Open Avalonia Window"; + OpenWindowButton.UseVisualStyleBackColor = true; + OpenWindowButton.Click += OpenWindowButton_Click; + // // monthCalendar1 - // - this.monthCalendar1.Location = new System.Drawing.Point(28, 114); - this.monthCalendar1.Name = "monthCalendar1"; - this.monthCalendar1.TabIndex = 1; - // + // + monthCalendar1.Location = new System.Drawing.Point(33, 132); + monthCalendar1.Margin = new System.Windows.Forms.Padding(10); + monthCalendar1.Name = "monthCalendar1"; + monthCalendar1.TabIndex = 1; + // // groupBox1 - // - this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left))); - this.groupBox1.Controls.Add(this.button1); - this.groupBox1.Controls.Add(this.monthCalendar1); - this.groupBox1.Location = new System.Drawing.Point(12, 12); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(227, 418); - this.groupBox1.TabIndex = 2; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "WinForms"; - // + // + groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left)); + groupBox1.Controls.Add(OpenWindowButton); + groupBox1.Controls.Add(monthCalendar1); + groupBox1.Location = new System.Drawing.Point(14, 14); + groupBox1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBox1.Name = "groupBox1"; + groupBox1.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBox1.Size = new System.Drawing.Size(265, 482); + groupBox1.TabIndex = 2; + groupBox1.TabStop = false; + groupBox1.Text = "WinForms"; + // // groupBox2 - // - this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupBox2.Controls.Add(this.avaloniaHost); - this.groupBox2.Location = new System.Drawing.Point(245, 12); - this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(501, 418); - this.groupBox2.TabIndex = 3; - this.groupBox2.TabStop = false; - this.groupBox2.Text = "Avalonia"; - // + // + groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)); + groupBox2.Controls.Add(avaloniaHost); + groupBox2.Location = new System.Drawing.Point(286, 14); + groupBox2.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBox2.Name = "groupBox2"; + groupBox2.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBox2.Size = new System.Drawing.Size(584, 482); + groupBox2.TabIndex = 3; + groupBox2.TabStop = false; + groupBox2.Text = "Avalonia"; + // // avaloniaHost - // - this.avaloniaHost.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.avaloniaHost.Content = null; - this.avaloniaHost.Location = new System.Drawing.Point(6, 19); - this.avaloniaHost.Name = "avaloniaHost"; - this.avaloniaHost.Size = new System.Drawing.Size(489, 393); - this.avaloniaHost.TabIndex = 0; - this.avaloniaHost.Text = "avaloniaHost"; - // + // + avaloniaHost.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)); + avaloniaHost.Location = new System.Drawing.Point(7, 22); + avaloniaHost.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + avaloniaHost.Name = "avaloniaHost"; + avaloniaHost.Size = new System.Drawing.Size(570, 453); + avaloniaHost.TabIndex = 0; + avaloniaHost.Text = "avaloniaHost"; + // // EmbedToWinFormsDemo - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(758, 442); - this.Controls.Add(this.groupBox2); - this.Controls.Add(this.groupBox1); - this.MinimumSize = new System.Drawing.Size(600, 400); - this.Name = "EmbedToWinFormsDemo"; - this.Text = "EmbedToWinFormsDemo"; - this.groupBox1.ResumeLayout(false); - this.groupBox2.ResumeLayout(false); - this.ResumeLayout(false); - + // + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + ClientSize = new System.Drawing.Size(884, 510); + Controls.Add(groupBox2); + Controls.Add(groupBox1); + Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + MinimumSize = new System.Drawing.Size(697, 456); + Text = "EmbedToWinFormsDemo"; + groupBox1.ResumeLayout(false); + groupBox2.ResumeLayout(false); + ResumeLayout(false); } #endregion - private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button OpenWindowButton; private System.Windows.Forms.MonthCalendar monthCalendar1; private System.Windows.Forms.GroupBox groupBox1; private System.Windows.Forms.GroupBox groupBox2; diff --git a/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.cs b/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.cs index d37ed13559..69dfcb1bbc 100644 --- a/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.cs +++ b/samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.cs @@ -1,5 +1,10 @@ -using System.Windows.Forms; +using System; +using System.Windows.Forms; using ControlCatalog; +using AvaloniaButton = Avalonia.Controls.Button; +using AvaloniaStackPanel = Avalonia.Controls.StackPanel; +using AvaloniaTextBox = Avalonia.Controls.TextBox; +using AvaloniaWindow = Avalonia.Controls.Window; namespace WindowsInteropTest { @@ -10,5 +15,23 @@ namespace WindowsInteropTest InitializeComponent(); avaloniaHost.Content = new MainView(); } + + private void OpenWindowButton_Click(object sender, EventArgs e) + { + var window = new AvaloniaWindow + { + Width = 300, + Height = 300, + Content = new AvaloniaStackPanel + { + Children = + { + new AvaloniaButton { Content = "Button" }, + new AvaloniaTextBox { Text = "Text" } + } + } + }; + window.Show(); + } } } diff --git a/samples/interop/WindowsInteropTest/Program.cs b/samples/interop/WindowsInteropTest/Program.cs index 4ebb88642b..8ef01523d9 100644 --- a/samples/interop/WindowsInteropTest/Program.cs +++ b/samples/interop/WindowsInteropTest/Program.cs @@ -1,6 +1,7 @@ using System; using ControlCatalog; using Avalonia; +using Avalonia.Win32.Interoperability; namespace WindowsInteropTest { @@ -14,9 +15,11 @@ namespace WindowsInteropTest { System.Windows.Forms.Application.EnableVisualStyles(); System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false); + System.Windows.Forms.Application.AddMessageFilter(new WinFormsAvaloniaMessageFilter()); AppBuilder.Configure() .UseWin32() .UseSkia() + .UseHarfBuzz() .SetupWithoutStarting(); System.Windows.Forms.Application.Run(new EmbedToWinFormsDemo()); } diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj index 576910ca3d..e282d93121 100644 --- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj +++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Windows/Avalonia.Win32.Interoperability/WinForms/WinFormsAvaloniaMessageFilter.cs b/src/Windows/Avalonia.Win32.Interoperability/WinForms/WinFormsAvaloniaMessageFilter.cs new file mode 100644 index 0000000000..3df4b89ce9 --- /dev/null +++ b/src/Windows/Avalonia.Win32.Interoperability/WinForms/WinFormsAvaloniaMessageFilter.cs @@ -0,0 +1,48 @@ +using System; +using System.Windows.Forms; +using static Avalonia.Win32.Interop.UnmanagedMethods; + +namespace Avalonia.Win32.Interoperability; + +/// +/// Provides a message filter for integrating Avalonia within a WinForms application. +/// +/// +/// This filter ensures that key messages, which are typically handled specially by WinForms, +/// are intercepted and routed to Avalonia's windows. This is necessary to preserve proper input handling +/// in mixed WinForms and Avalonia application scenarios. +/// +public class WinFormsAvaloniaMessageFilter : IMessageFilter +{ + /// + public bool PreFilterMessage(ref Message m) + { + // WinForms handles key messages specially, preventing them from reaching Avalonia's windows. + // Handle them first. + if (m.Msg >= (int)WindowsMessage.WM_KEYFIRST && + m.Msg <= (int)WindowsMessage.WM_KEYLAST && + WindowImpl.IsOurWindowGlobal(m.HWnd) && + !IsInsideWinForms(m.HWnd)) + { + var msg = new MSG + { + hwnd = m.HWnd, + message = (uint)m.Msg, + wParam = m.WParam, + lParam = m.LParam + }; + + TranslateMessage(ref msg); + DispatchMessage(ref msg); + return true; + } + + return false; + } + + private static bool IsInsideWinForms(IntPtr hwnd) + { + var parentHwnd = GetParent(hwnd); + return parentHwnd != IntPtr.Zero && Control.FromHandle(parentHwnd) is WinFormsAvaloniaControlHost; + } +} diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 5295e2c03a..82aaac226c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -1004,6 +1004,14 @@ namespace Avalonia.Win32 if (hwnd == _hwnd) return true; + return IsOurWindowGlobal(hwnd); + } + + internal static bool IsOurWindowGlobal(IntPtr hwnd) + { + if (hwnd == IntPtr.Zero) + return false; + lock (s_instances) for (int i = 0; i < s_instances.Count; i++) if (s_instances[i]._hwnd == hwnd)