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. 145
      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

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

@ -30,87 +30,88 @@ namespace WindowsInteropTest
/// </summary>
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;

25
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();
}
}
}

3
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<App>()
.UseWin32()
.UseSkia()
.UseHarfBuzz()
.SetupWithoutStarting();
System.Windows.Forms.Application.Run(new EmbedToWinFormsDemo());
}

1
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

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

Loading…
Cancel
Save