WPF - имитировать события мыши на сетке, отображаемой за пределами экрана

Я визуализирую сетку WPF с несколькими элементами (кнопки, текстовое поле,...) в растровое изображение, которое затем используется в качестве текстуры для трехмерной поверхности в сцене Direct3D. Для взаимодействия с пользователем я создаю 3D-луч из положения 2D-курсора мыши в 3D-сцену, находя точку пересечения с поверхностью графического интерфейса. Итак, я знаю, где пользователь щелкнул сетку WPF, но оттуда я застрял:

Как я могу имитировать события мыши на элементах WPF, когда они фактически не отображаются в открытом окне, а отображаются за пределами экрана?

Недавно я изучал UIAutomation и RaiseEvent, но они используются для отправки событий отдельным элементам, а не всему визуальному дереву. Обход дерева вручную и поиск элементов в позиции курсора был бы вариантом, но я не нашел способа сделать это точно. VisualTreeHelper.HitTest — хорошее начало, но вместо TextBox он находит TextBoxView, а вместо ListBox — Border.

РЕДАКТИРОВАТЬ: возврат HitTestResultBehavior.Continue в обратном вызове результата HitTest позволяет мне пройти через все элементы в заданной точке. Теперь я могу отправлять события мыши всем этим элементам, но значения объекта MouseEventArgs совпадают со значениями реальной мыши. Поэтому мне нужно создать собственный MouseDevice, что, по-видимому, невозможно.

PointHitTestParameters p = new PointHitTestParameters(new Point(
    ((Vector2)hit).X * sourceElement.ActualWidth, 
    (1 - ((Vector2)hit).Y) * sourceElement.ActualHeight));
VisualTreeHelper.HitTest(sourceElement,
     new HitTestFilterCallback(delegate(DependencyObject o)
     {
         return HitTestFilterBehavior.Continue;
     }),
     new HitTestResultCallback(delegate(HitTestResult r)
     {
         UIElement el = r.VisualHit as UIElement;
         if (el != null)
         {
             MouseButtonEventArgs e = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left);
             if (leftMouseDown) e.RoutedEvent = Mouse.MouseDownEvent;
             else e.RoutedEvent = Mouse.MouseUpEvent;
             el.RaiseEvent(e);
         }
         return HitTestResultBehavior.Continue;
     }), p);

person Community    schedule 17.12.2009    source источник


Ответы (2)


Возможно, вы сможете отправлять сообщения Windows (например, WM_MOUSEMOVE) в HWND окна WPF с помощью метода Win32 PostMessage(..). Я считаю, что эти сообщения будут прочитаны WPF и выполнены так, как если бы они исходили от пользователя.

person Jeremiah Morrill    schedule 18.12.2009
comment
Первая проблема с этим методом заключается в том, что элементы не должны находиться в окне. Но даже если я создам его и размещу на нем элементы, сообщения не обрабатываются WPF. Я использовал обработчики событий для окна, UserControl, сетки и, наконец, для таких элементов, как кнопки и прямоугольники. Однако оконные сообщения принимаются WPF-окном, инкапсулирующим HwndSource. Я мог видеть это, когда добавлял свой собственный хук сообщений с классом HwndSource. - person ; 18.12.2009
comment
Глядя на разборку HwndSource, я думаю, что вы правы. Сообщения должны выполняться так, как если бы они исходили от пользователя. Но, видимо, это не так, и я не могу понять, почему. - person ; 18.12.2009
comment
После нескольких часов дизассемблирования и пошаговой отладки я нашел причину в HwndMouseInputProvider.ReportInput: окно WPF не активно, а API-функция GetCapture() не возвращает дескриптор окна. Таким образом, неактивные окна получают сообщения, но обработчик взаимодействия WPF отбрасывает их. Легко исправить, подумал я, но простой вызов Activate() в окне WPF или установка дескриптора с помощью SetCapture() не помогает. буду копать глубже... - person ; 18.12.2009
comment
Хорошо, мой плохой. Я сбрасываю дескриптор с помощью SetCapture() сразу после отправки сообщения, забывая, что система оконных сообщений не сразу отправляет сообщения. Теперь он работает с первым сообщением, которое я отправляю, но последующие взаимодействия, такие как WM_LMOUSEUP после WM_LMOUSEDOWN, не распознаются, потому что дескриптор захвата мыши был установлен в окно WPF. - person ; 18.12.2009

Если вы чувствуете себя действительно смелым, вы можете попробовать мой способ получить доступ к WPF IDirect3DDevice9Ex. Оттуда вы можете скопировать резервный буфер в цепочке подкачки в свою сцену d3d. Если вы используете 9Ex (Vista D3D9), вы можете воспользоваться функцией общих ресурсов (общие поверхности/текстуры/и т. д. между устройствами и процессами), чтобы повысить производительность.

Возможно, вам все же придется проделать некоторые трюки с сообщениями Windows для интерактивности.

person Jeremiah Morrill    schedule 19.12.2009