How can I convert Win32 mouse messages to WPF mouse events?

半城伤御伤魂 提交于 2019-12-21 03:49:10

问题


I have a Win32 (OpenGL) control that I need to embed in our WPF application. It must respond to, and propogate, mouse and keyboard events.

I've created a HwndHost derived instance to host the native window and have overridden the WndProc function in this class. To propogate win32 messages to WPF I handle specific mouse messages and map them to WPF events, then use the static InputManager class to raise them.

The problem is, when I go to handle them the mouse coordinates are messed up.

Heres a sample of the code I'm using to raise the events:

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

When my WPF event handlers are triggered and I try to retrieve the mouse coordinates (e.g. e.GetPosition((IInputElement) sender)) I recieve the wrong values (and they are always the same values regardless of where the original event occurred within my control).

The values I get seem to be related to the screen location of the WPF window that hosts the application because they change when the window position changes, but they don't correspond to the actual location of the application window either.

I think this may have something to do with the internal state of the WPF InputManager, and the fact that the private field MouseDevice._inputSource is null when my events are raised, but my experiments with .net reflection haven't yielded any results there.

I don't really know what else to try. Keyboard support worked out of the box, it's just the mouse location support that I can't get working correctly.


回答1:


After another day spent in Reflector analysing the .net sourcecode in question I have found the solution. One must first prime the WPF InputManager by raising a PreviewInputReportEventArgs routed event.

Unfortunately, this event and the event argument structures required to raise it are all marked internal, leaving reflection the only way to raise this event. For anyone with the same problem, here's the C# code required to do so:

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

where:

  • timestamp is the system tick count when the mouse event occurred (usually Environment.TickCount)
  • pointX and pointY are the mouse coordinates, relative to the top level application window's client area
  • wheel is the mouse wheel delta

Note that it is only necessary to raise the "Preview" event. After raising this event the standard mouse events can be raised and WPF will return the correct mouse location coordinates when e.GetPosition() is called.




回答2:


The way I do it is with different approach. I keep transparent WPF Window overlayed on my HwndHost using AllowsTransparency property. I hook up interesting events on overlay window and redirect them to my control using RaiseEvent like this:

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

The only thing you need to do is to synchronize HwndHost and overlay window positions, which is not that hard.




回答3:


You can get the absolute position of the mouse (in screen coordinates) at any time by using a p/Invoke function:

[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);

From there you should be able to easily calculate relative coordinates.



来源:https://stackoverflow.com/questions/21248643/how-can-i-convert-win32-mouse-messages-to-wpf-mouse-events

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!