Browse Source

Add WinForms message filter for Avalonia windows (#20814)

* Add WinForms message filter for Avalonia windows

* Do not use filter messages for WinFormsAvaloniaControlHost
pull/17825/merge
Julien Lebosquain 1 week ago
committed by GitHub
parent
commit
e9dbf08c84
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 121
      samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.Designer.cs
  2. 25
      samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.cs
  3. 3
      samples/interop/WindowsInteropTest/Program.cs
  4. 1
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  5. 48
      src/Windows/Avalonia.Win32.Interoperability/WinForms/WinFormsAvaloniaMessageFilter.cs
  6. 8
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

121
samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.Designer.cs

@ -30,87 +30,88 @@ namespace WindowsInteropTest
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
this.button1 = new System.Windows.Forms.Button(); OpenWindowButton = new System.Windows.Forms.Button();
this.monthCalendar1 = new System.Windows.Forms.MonthCalendar(); monthCalendar1 = new System.Windows.Forms.MonthCalendar();
this.groupBox1 = new System.Windows.Forms.GroupBox(); groupBox1 = new System.Windows.Forms.GroupBox();
this.groupBox2 = new System.Windows.Forms.GroupBox(); groupBox2 = new System.Windows.Forms.GroupBox();
this.avaloniaHost = new WinFormsAvaloniaControlHost(); avaloniaHost = new Avalonia.Win32.Interoperability.WinFormsAvaloniaControlHost();
this.groupBox1.SuspendLayout(); groupBox1.SuspendLayout();
this.groupBox2.SuspendLayout(); groupBox2.SuspendLayout();
this.SuspendLayout(); SuspendLayout();
// //
// button1 // OpenWindowButton
// //
this.button1.Location = new System.Drawing.Point(28, 29); OpenWindowButton.Location = new System.Drawing.Point(33, 33);
this.button1.Name = "button1"; OpenWindowButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.button1.Size = new System.Drawing.Size(164, 73); OpenWindowButton.Name = "OpenWindowButton";
this.button1.TabIndex = 0; OpenWindowButton.Size = new System.Drawing.Size(191, 84);
this.button1.Text = "button1"; OpenWindowButton.TabIndex = 0;
this.button1.UseVisualStyleBackColor = true; OpenWindowButton.Text = "Open Avalonia Window";
OpenWindowButton.UseVisualStyleBackColor = true;
OpenWindowButton.Click += OpenWindowButton_Click;
// //
// monthCalendar1 // monthCalendar1
// //
this.monthCalendar1.Location = new System.Drawing.Point(28, 114); monthCalendar1.Location = new System.Drawing.Point(33, 132);
this.monthCalendar1.Name = "monthCalendar1"; monthCalendar1.Margin = new System.Windows.Forms.Padding(10);
this.monthCalendar1.TabIndex = 1; monthCalendar1.Name = "monthCalendar1";
monthCalendar1.TabIndex = 1;
// //
// groupBox1 // groupBox1
// //
this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left));
| System.Windows.Forms.AnchorStyles.Left))); groupBox1.Controls.Add(OpenWindowButton);
this.groupBox1.Controls.Add(this.button1); groupBox1.Controls.Add(monthCalendar1);
this.groupBox1.Controls.Add(this.monthCalendar1); groupBox1.Location = new System.Drawing.Point(14, 14);
this.groupBox1.Location = new System.Drawing.Point(12, 12); groupBox1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.groupBox1.Name = "groupBox1"; groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(227, 418); groupBox1.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.groupBox1.TabIndex = 2; groupBox1.Size = new System.Drawing.Size(265, 482);
this.groupBox1.TabStop = false; groupBox1.TabIndex = 2;
this.groupBox1.Text = "WinForms"; groupBox1.TabStop = false;
groupBox1.Text = "WinForms";
// //
// groupBox2 // groupBox2
// //
this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 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));
| System.Windows.Forms.AnchorStyles.Left) groupBox2.Controls.Add(avaloniaHost);
| System.Windows.Forms.AnchorStyles.Right))); groupBox2.Location = new System.Drawing.Point(286, 14);
this.groupBox2.Controls.Add(this.avaloniaHost); groupBox2.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.groupBox2.Location = new System.Drawing.Point(245, 12); groupBox2.Name = "groupBox2";
this.groupBox2.Name = "groupBox2"; groupBox2.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.groupBox2.Size = new System.Drawing.Size(501, 418); groupBox2.Size = new System.Drawing.Size(584, 482);
this.groupBox2.TabIndex = 3; groupBox2.TabIndex = 3;
this.groupBox2.TabStop = false; groupBox2.TabStop = false;
this.groupBox2.Text = "Avalonia"; groupBox2.Text = "Avalonia";
// //
// avaloniaHost // avaloniaHost
// //
this.avaloniaHost.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 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));
| System.Windows.Forms.AnchorStyles.Left) avaloniaHost.Location = new System.Drawing.Point(7, 22);
| System.Windows.Forms.AnchorStyles.Right))); avaloniaHost.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.avaloniaHost.Content = null; avaloniaHost.Name = "avaloniaHost";
this.avaloniaHost.Location = new System.Drawing.Point(6, 19); avaloniaHost.Size = new System.Drawing.Size(570, 453);
this.avaloniaHost.Name = "avaloniaHost"; avaloniaHost.TabIndex = 0;
this.avaloniaHost.Size = new System.Drawing.Size(489, 393); avaloniaHost.Text = "avaloniaHost";
this.avaloniaHost.TabIndex = 0;
this.avaloniaHost.Text = "avaloniaHost";
// //
// EmbedToWinFormsDemo // EmbedToWinFormsDemo
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(758, 442); ClientSize = new System.Drawing.Size(884, 510);
this.Controls.Add(this.groupBox2); Controls.Add(groupBox2);
this.Controls.Add(this.groupBox1); Controls.Add(groupBox1);
this.MinimumSize = new System.Drawing.Size(600, 400); Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.Name = "EmbedToWinFormsDemo"; MinimumSize = new System.Drawing.Size(697, 456);
this.Text = "EmbedToWinFormsDemo"; Text = "EmbedToWinFormsDemo";
this.groupBox1.ResumeLayout(false); groupBox1.ResumeLayout(false);
this.groupBox2.ResumeLayout(false); groupBox2.ResumeLayout(false);
this.ResumeLayout(false); ResumeLayout(false);
} }
#endregion #endregion
private System.Windows.Forms.Button button1; private System.Windows.Forms.Button OpenWindowButton;
private System.Windows.Forms.MonthCalendar monthCalendar1; private System.Windows.Forms.MonthCalendar monthCalendar1;
private System.Windows.Forms.GroupBox groupBox1; private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.GroupBox groupBox2; private System.Windows.Forms.GroupBox groupBox2;

25
samples/interop/WindowsInteropTest/EmbedToWinFormsDemo.cs

@ -1,5 +1,10 @@
using System.Windows.Forms; using System;
using System.Windows.Forms;
using ControlCatalog; using ControlCatalog;
using AvaloniaButton = Avalonia.Controls.Button;
using AvaloniaStackPanel = Avalonia.Controls.StackPanel;
using AvaloniaTextBox = Avalonia.Controls.TextBox;
using AvaloniaWindow = Avalonia.Controls.Window;
namespace WindowsInteropTest namespace WindowsInteropTest
{ {
@ -10,5 +15,23 @@ namespace WindowsInteropTest
InitializeComponent(); InitializeComponent();
avaloniaHost.Content = new MainView(); 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();
}
} }
} }

3
samples/interop/WindowsInteropTest/Program.cs

@ -1,6 +1,7 @@
using System; using System;
using ControlCatalog; using ControlCatalog;
using Avalonia; using Avalonia;
using Avalonia.Win32.Interoperability;
namespace WindowsInteropTest namespace WindowsInteropTest
{ {
@ -14,9 +15,11 @@ namespace WindowsInteropTest
{ {
System.Windows.Forms.Application.EnableVisualStyles(); System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false); System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
System.Windows.Forms.Application.AddMessageFilter(new WinFormsAvaloniaMessageFilter());
AppBuilder.Configure<App>() AppBuilder.Configure<App>()
.UseWin32() .UseWin32()
.UseSkia() .UseSkia()
.UseHarfBuzz()
.SetupWithoutStarting(); .SetupWithoutStarting();
System.Windows.Forms.Application.Run(new EmbedToWinFormsDemo()); System.Windows.Forms.Application.Run(new EmbedToWinFormsDemo());
} }

1
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

@ -8,6 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\src\HarfBuzz\Avalonia.HarfBuzz\Avalonia.HarfBuzz.csproj" />
<ProjectReference Include="..\..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> <ProjectReference Include="..\..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32.Interoperability\Avalonia.Win32.Interoperability.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32.Interoperability\Avalonia.Win32.Interoperability.csproj" />
<ProjectReference Include="..\..\ControlCatalog\ControlCatalog.csproj" /> <ProjectReference Include="..\..\ControlCatalog\ControlCatalog.csproj" />

48
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;
/// <summary>
/// Provides a message filter for integrating Avalonia within a WinForms application.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public class WinFormsAvaloniaMessageFilter : IMessageFilter
{
/// <inheritdoc />
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;
}
}

8
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -1004,6 +1004,14 @@ namespace Avalonia.Win32
if (hwnd == _hwnd) if (hwnd == _hwnd)
return true; return true;
return IsOurWindowGlobal(hwnd);
}
internal static bool IsOurWindowGlobal(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero)
return false;
lock (s_instances) lock (s_instances)
for (int i = 0; i < s_instances.Count; i++) for (int i = 0; i < s_instances.Count; i++)
if (s_instances[i]._hwnd == hwnd) if (s_instances[i]._hwnd == hwnd)

Loading…
Cancel
Save