【问题标题】:How can I convert Win32 mouse messages to WPF mouse events?如何将 Win32 鼠标消息转换为 WPF 鼠标事件?
【发布时间】:2014-02-10 11:41:27
【问题描述】:

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

我创建了一个 HwndHost 派生实例来托管本机窗口,并覆盖了此类中的 WndProc 函数。为了向 WPF 传播 win32 消息,我处理特定的鼠标消息并将它们映射到 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._inputSourcenull,但我的实验.net 反射没有产生任何结果。

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

【问题讨论】:

  • 在转发之前,我看不到您在哪里将鼠标坐标(如果有记忆在 LPARAM 中编码)分配给 eventArgs。查看 MouseButtonEventArgs 的定义,我看不到将鼠​​标坐标注入其中的方法。
  • 如果将消息转发到父窗口并让 WPF 担心将其转换为 WPF 事件,它是否有效?
  • @arx - 不幸的是它没有。
  • @user1793036 - 你是对的,没有办法注入鼠标坐标。 WPF 在内部类中自行确定它们。

标签: .net wpf winapi 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 控件正在替换现有的控件,因此这样做还需要我更新一堆现有的事件处理程序,而我不愿意这样做。
  • 抱歉,我无法提供更多帮助。希望有人能提供更好的答案。
【解决方案2】:

在 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 是鼠标滚轮增量

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-14
      • 2017-06-08
      • 2018-10-08
      • 2013-07-04
      • 2018-12-04
      • 1970-01-01
      • 1970-01-01
      • 2021-02-10
      相关资源
      最近更新 更多