Как я могу конвертировать сообщения мыши 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);
Оттуда вы сможете легко рассчитать относительные координаты.