我们有一个带有 Hwndhost 元素的 Wpf 应用程序,用于通过 DirectX 呈现 3d 内容。我们向包含 MouseEnter 和 MouseLeave 事件的客户端公开一个“Panel”接口。对于 Hwndhost“面板”,我们在第一次收到 HwndHost.WndProc 的 WM_MOUSEMOVE 事件时模拟 MouseEnter 事件,因为 win32 没有 WM_MOUSEENTER 事件。此外,对于 HwndHost,我们通过 TrackMouseEvent() 请求 WM_MOUSELEAVE 事件,该事件在我们的“面板”界面上模拟 MouseLeave 事件。
问题在于,当用户将鼠标快速移动到 HwndHost 面板或从 HwndHost 面板移动时,我们会从鼠标移动到的面板接收到 enter 事件,然后才会收到鼠标移动到的面板的离开事件。 当源面板和目标面板都是 wpf 元素时,这不会发生,只有在涉及 hwndhost 元素时才会发生。此外,如果这些 HwndHost 元素中有两个彼此相邻,则会发生相同的行为,因此看起来 win32 没有按照它们发生的顺序发送消息。
有谁知道解决这个问题的方法? 如果有办法让 Win32 按事件发生的顺序发送事件,那就太好了。
这是一个示例应用程序,它向调试器输出窗口展示了打印消息的问题:
// App.xaml.cs
using System;
using System.Windows;
using System.Runtime.InteropServices;
namespace WpfHostingWin32Control
{
public partial class App : Application
{
[DllImport("comctl32.dll", EntryPoint = "InitCommonControls", CharSet = CharSet.Auto)]
public static extern void InitCommonControls();
private void ApplicationStartup(object sender, StartupEventArgs args)
{
InitCommonControls();
HostWindow host = new HostWindow();
host.InitializeComponent();
}
}
}
// App.xaml
<Application x:Class="WpfHostingWin32Control.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
Startup="ApplicationStartup">
</Application>
// HostWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Input;
namespace WpfHostingWin32Control
{
public partial class HostWindow : Window
{
WpfHostingWin32Control.ControlHost ctrlHost;
public HostWindow()
{
InitializeComponent();
txtbox1.MouseEnter += MouseEnterHandler;
txtbox1.MouseLeave += MouseLeaveHandler;
}
void MouseEnterHandler(object sender, MouseEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Wpf MouseEnter");
}
void MouseLeaveHandler(object sender, MouseEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Wpf MouseLeave");
}
private void On_UIReady(object sender, EventArgs e)
{
ctrlHost = new ControlHost(600, 600);
ControlHostElement.Child = ctrlHost;
}
}
}
// HostWindow.xaml
<Window x:Class="WpfHostingWin32Control.HostWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="mainWindow"
Width="1250"
Height="700"
Background="LightGreen"
Loaded="On_UIReady">
<StackPanel Margin="20" Orientation="Horizontal">
<Border Name="ControlHostElement" Width="600" Height="600"/>
<TextBox Margin="5,0,0,0" x:Name="txtbox1" Width="600" Height="600"/>
</StackPanel>
</Window>
// ControlHost.cs
using System;
using System.Windows;
using System.Windows.Interop;
using System.Runtime.InteropServices;
namespace WpfHostingWin32Control
{
public class ControlHost : HwndHost
{
IntPtr hwndHost;
int hostHeight, hostWidth;
bool m_MouseHasEntered;
public ControlHost(double height, double width)
{
hostHeight = (int)height;
hostWidth = (int)width;
}
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
hwndHost = IntPtr.Zero;
hwndHost = CreateWindowEx(0, "listbox", "",WS_CHILD | WS_VISIBLE | WS_BORDER, 0, 0,
hostWidth, hostHeight, hwndParent.Handle, IntPtr.Zero, IntPtr.Zero, 0);
return new HandleRef(this, hwndHost);
}
protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WM_MOUSEMOVE:
{
if (m_MouseHasEntered == false)
{
RequestMouesLeaveNotification();
m_MouseHasEntered = true;
System.Diagnostics.Debug.WriteLine("WM_MOUSEMOVE/ENTER");
}
}
break;
case WM_MOUSELEAVE:
{
m_MouseHasEntered = false;
System.Diagnostics.Debug.WriteLine("WM_MOUSELEAVE");
}
break;
}
handled = false;
return IntPtr.Zero;
}
private void RequestMouesLeaveNotification()
{
var tme = new TRACKMOUSEEVENT()
{
cbSize = (uint)Marshal.SizeOf(typeof(TRACKMOUSEEVENT)),
dwFlags = TME_LEAVE,
hwndTrack = hwndHost,
dwHoverTime = 0
};
if (!TrackMouseEvent(out tme))
System.Diagnostics.Debug.WriteLine("TrackMouseEvent failed!");
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
DestroyWindow(hwnd.Handle);
}
//PInvoke declarations
[DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateWindowEx(int dwExStyle,
string lpszClassName,
string lpszWindowName,
int style,
int x, int y,
int width, int height,
IntPtr hwndParent,
IntPtr hMenu,
IntPtr hInst,
[MarshalAs(UnmanagedType.AsAny)] object pvParam);
[DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
internal static extern bool DestroyWindow(IntPtr hwnd);
private const int WM_MOUSEMOVE = 0x0200;
private const int WM_MOUSELEAVE = 0x02A3;
private const int TME_LEAVE = 0x00000002;
private const int WS_CHILD = 0x40000000;
private const int WS_VISIBLE = 0x10000000;
private const int WS_BORDER = 0x00800000;
[StructLayout(LayoutKind.Sequential)]
private struct TRACKMOUSEEVENT
{
public uint cbSize;
public uint dwFlags;
public IntPtr hwndTrack;
public uint dwHoverTime;
}
[DllImport("user32.dll")]
private static extern bool TrackMouseEvent(out TRACKMOUSEEVENT tme);
}
}