Как я могу конвертировать сообщения мыши Win32 в события мыши WPF?

У меня есть элемент управления 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 не дали там никаких результатов.

Я действительно не знаю, что еще попробовать. Поддержка клавиатуры работала из коробки, это просто поддержка местоположения мыши, которую я не могу правильно настроить.

3 ответа

Решение

После другого дня, проведенного в Reflector для анализа рассматриваемого исходного кода.net, я нашел решение. Сначала нужно заполнить WPF InputManager, подняв PreviewInputReportEventArgs перенаправленное событие.

К сожалению, это событие и структуры аргументов события, необходимые для его вызова, помечены 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 это дельта колесика мыши

Обратите внимание, что необходимо только вызвать событие "Предварительный просмотр". После вызова этого события можно вызвать стандартные события мыши, и WPF вернет правильные координаты местоположения мыши, когда e.GetPosition() называется.

Я делаю это с другим подходом. Я держу прозрачное окно WPF наложенным на мой HwndHost, используя AllowsTransparency имущество. Я подключаю интересные события в оверлейном окне и перенаправляю их на свой контроль, используя RaiseEvent как это:

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

Единственное, что вам нужно сделать, это синхронизировать HwndHost и наложить позиции окна, что не так сложно.

Вы можете получить абсолютную позицию мыши (в экранных координатах) в любое время, используя функцию a 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);

Оттуда вы сможете легко рассчитать относительные координаты.

Другие вопросы по тегам