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

12

我有一个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._inputSource在我的事件被触发时为null,但我使用.net反射进行的实验没有产生任何结果。
我真的不知道还能尝试什么。键盘支持可以直接使用,只有鼠标位置支持无法正确工作。

我看不到你在哪里将鼠标坐标(如果我没记错,它们被编码在LPARAM中)分配给eventArgs,然后再将其转发。并且根据MouseButtonEventArgs的定义,我看不到任何将鼠标坐标注入其中的方法。 - user1793036
如果将消息转发到父窗口并让WPF将其转换为WPF事件,那么它是否有效? - arx
@arx - 很遗憾,它不行。 - Josh
@user1793036 - 你说得对,没有办法注入鼠标坐标。WPF在内部类中自己确定它们。 - Josh
3个回答

7

在 Reflector 中分析了有关 .Net 源代码后,我找到了解决方案。必须先通过触发 PreviewInputReportEventArgs 路由事件来激活 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
  • pointXpointY 是相对于顶级应用程序窗口客户区域的鼠标坐标
  • wheel 是鼠标滚轮增量

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


0
我使用不同的方法来实现。我使用具有 AllowsTransparency 属性的透明 WPF 窗口覆盖在我的 HwndHost 上。我在叠加窗口上挂接有趣的事件,并使用像这样的 RaiseEvent 将它们重定向到我的控件:
//in HwndHost code
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
    this.RaiseEvent(e);
}

你需要做的唯一一件事就是同步 HwndHost 和覆盖窗口的位置,这并不难。

-1

您可以使用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);

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


我意识到这一点,但它需要应用程序中的每个事件处理程序选择加入此行为。如果必须这样做,那就是我要做的,但这不是一个理想的前进方式。这个OpenGL控件正在替换一个现有的控件,所以这样做也需要我更新一堆现有的事件处理程序,而我不太愿意这样做。 - Josh
抱歉我无法提供更多帮助。希望有人能够提供更好的答案。 - Nathan M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接