.net 如何将Win32鼠标消息转换为WPF鼠标事件?

.net 如何将Win32鼠标消息转换为WPF鼠标事件?,.net,wpf,winapi,hwndhost,.net,Wpf,Winapi,Hwndhost,我有一个Win32(OpenGL)控件,我需要将它嵌入到我们的WPF应用程序中。它必须响应并传播鼠标和键盘事件 我已经创建了一个HwndHost派生实例来承载本机窗口,并且已经重写了此类中的WndProc函数。为了将win32消息传播到WPF,我处理特定的鼠标消息并将它们映射到WPF事件,然后使用静态InputManager类来引发它们 问题是,当我去处理它们时,鼠标坐标被弄乱了。 下面是我用来引发事件的代码示例: IntPtr MyHwndHost::WndProc(IntPtr hwnd,

我有一个Win32(OpenGL)控件,我需要将它嵌入到我们的WPF应用程序中。它必须响应并传播鼠标和键盘事件

我已经创建了一个HwndHost派生实例来承载本机窗口,并且已经重写了此类中的WndProc函数。为了将win32消息传播到WPF,我处理特定的鼠标消息并将它们映射到WPF事件,然后使用静态InputManager类来引发它们

问题是,当我去处理它们时,鼠标坐标被弄乱了。

下面是我用来引发事件的代码示例:

IntPtr MyHwndHost::WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, bool% handled)
{
  switch (msg)
  {
    case WM_LBUTTONDOWN:
    {
        MouseButtonEventArgs^ eventArgs = gcnew MouseButtonEventArgs(
          Mouse::PrimaryDevice,
          Environment::TickCount,
          MouseButton::Left);

        eventArgs->RoutedEvent = UIElement::PreviewMouseDownEvent;
        eventArgs->Source = this;

        // Raise the WPF event from the specified WPF event source.
        InputManager::Current->ProcessInput(eventArgs);

        eventArgs->RoutedEvent = UIElement::MouseDownEvent;
        InputManager::Current->ProcessInput(eventArgs);

        CommandManager::InvalidateRequerySuggested();

        handled = true;
        return IntPtr::Zero;
    }
    break;
  }

  return HwndHost::WndProc(hwnd, msg, wParam, lParam, handled);
}
当触发WPF事件处理程序并尝试检索鼠标坐标(例如
e.GetPosition((IInputElement)sender)
)时,我接收到错误的值(无论原始事件在我的控件中发生在何处,它们始终是相同的值)

我得到的值似乎与承载应用程序的WPF窗口的屏幕位置有关,因为它们随着窗口位置的变化而变化,但它们也与应用程序窗口的实际位置不对应

我认为这可能与WPF InputManager的内部状态有关,并且在引发事件时,私有字段
MouseDevice.\u inputSource
null
这一事实也与此有关,但是我对.net反射的实验没有产生任何结果


我真的不知道还能尝试什么。键盘支持开箱即用,只是鼠标位置支持无法正常工作。

您可以随时使用p/Invoke函数获取鼠标的绝对位置(在屏幕坐标中):

[StructLayout(LayoutKind.Sequential)]
private struct point {
    public int x;
    public int y;
}

[DllImport("user32.dll", EntryPoint="GetCursorPos")]
private static extern void GetCursorPos(ref point pt);

从那里,您应该能够轻松地计算相对坐标。

又花了一天时间分析有关的.net源代码,我找到了解决方案。必须首先通过引发
PreviewInputReportEventArgs
routed事件来初始化WPF InputManager

不幸的是,此事件和引发此事件所需的事件参数结构都标记为
internal
,因此反射是引发此事件的唯一方法。对于任何有相同问题的人,这里有这样做所需的C代码:

    void RaiseMouseInputReportEvent(Visual eventSource, int timestamp, int pointX, int pointY, int wheel)
    {
        Assembly targetAssembly = Assembly.GetAssembly(typeof(InputEventArgs));
        Type mouseInputReportType = targetAssembly.GetType("System.Windows.Input.RawMouseInputReport");

        Object mouseInputReport = mouseInputReportType.GetConstructors()[0].Invoke(new Object[] {
            InputMode.Foreground,
            timestamp,
            PresentationSource.FromVisual(eventSource),
            RawMouseActions.AbsoluteMove | RawMouseActions.Activate,
            pointX,
            pointY,
            wheel,
            IntPtr.Zero });

        mouseInputReportType
            .GetField("_isSynchronize", BindingFlags.NonPublic | BindingFlags.Instance)
            .SetValue(mouseInputReport, true);

        InputEventArgs inputReportEventArgs = (InputEventArgs) targetAssembly
            .GetType("System.Windows.Input.InputReportEventArgs")
            .GetConstructors()[0]
            .Invoke(new Object[] {
                Mouse.PrimaryDevice,
                mouseInputReport });

        inputReportEventArgs.RoutedEvent = (RoutedEvent) typeof(InputManager)
            .GetField("PreviewInputReportEvent", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
            .GetValue(null);

        InputManager.Current.ProcessInput((InputEventArgs) inputReportEventArgs);
    }
其中:

  • timestamp
    是鼠标事件发生时的系统计时计数(通常为
    Environment.TickCount
  • pointX
    pointY
    是相对于顶级应用程序窗口的客户端区域的鼠标坐标
  • wheel
    是鼠标滚轮

请注意,只需要引发“预览”事件。引发此事件后,可以引发标准鼠标事件,当调用
e.GetPosition()
时,WPF将返回正确的鼠标位置坐标。

我的方式是使用不同的方法。我使用
allowTransparency
属性将透明的WPF窗口覆盖在HwndHost上。我在overlay窗口上连接有趣的事件,并使用
RaiseEvent
将它们重定向到我的控件,如下所示:

//in HwndHost code
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
    this.RaiseEvent(e);
}

您需要做的唯一一件事是同步HwndHost和overlay窗口位置,这并不难。

在转发之前,我看不出您在哪里将鼠标坐标(如果内存允许,则在LPRAM中编码)分配给eventArgs。查看MouseButtonEventArgs的定义,我看不到将鼠标坐标注入其中的方法。如果您将消息转发到父窗口,让WPF担心将其转换为WPF事件,是否有效?@arx-不幸的是,它没有。@user1793036-您是正确的,没有办法注入鼠标坐标。WPF在内部类中自己确定它们。我知道这一点,但是它要求应用程序中的每个事件处理程序都选择这种行为。如果有必要,我会这么做,但这不是一个理想的前进方向。这个OpenGL控件正在替换一个现有控件,这样做还需要我更新一堆现有的事件处理程序,这是我不愿意做的。很抱歉,我不能提供更多帮助。希望有人能给出更好的答案。