Цель EventDispatcher
Как следует из названия, цель EventDispatcher
- отправить Event
.То, как это происходит, зависит от реализации.Однако стандартная реализация (внутренняя для JavaFX) делает EventDispatcher
своего рода «коллекцией» EventHandler
s.EventDispatcher
обязан вызвать подходящего EventHandler
в нужное время.То, что делает EventHandler
«подходящим», зависит от:
- Если
EventHandler
было зарегистрировано для текущей фазы цикла отправки событий (захвата или передачи) - Если
EventHandler
зарегистрирован для текущего Event
EventType
или одного из супертипов (т. Е. EventType.getSuperType()
)
Путь события (или «Цепочка»)
Если мы сфокусируемся на графе сцены, то при срабатывании Event
он начинается в верхней части графа сцены (Window
) и проходит по иерархии к цели (обычно Node
,но, по крайней мере, реализация EventTarget
).Это фаза «захвата» цикла отправки событий.После достижения цели он перемещается обратно вверх по графу сцены, пока не достигнет Window
снова.Это «пузырящаяся» фаза цикла.
Фаза захвата
Window
-> Scene
-> Root Node
-> Middle Node
-> EventTarget
Фаза пузыря
Window
<- <code>Scene <- <code>Root Node <- <code>Middle Node <- <code>EventTarget
Если на каком-либо шаге используется Event
(через Event.consume()
), то он не будет переадресован на следующий шаг.Это фактически останавливает обработку Event
на нужном этапе в цепочке.
Способ вычисления этого пути осуществляется реализацией EventDispatchChain
и EventTarget
.EventTarget
должен реализовывать следующий метод:
EventDispatchChain buildEventDispatchChain(EventDispatchChain tail);
Когда срабатывает Event
, он имеет обозначенное EventTarget
.Поскольку EventTarget
будет в нижней части графа сцены, цепочка построена снизу вверх.Это достигается путем добавления EventDispatcher
к EventDispatchChain
на каждом уровне иерархии (используя EventDispatchChain.prepend(EventDispatcher)
).Именно здесь начинает приходить EventDispatcher
.
Каждый EventTarget
обычно имеет свой собственный EventDispatcher
, связанный с ним.Реализации EventTarget
в стандартном JavaFX (Window
, Scene
, Node
, MenuItem
и т. Д.) Предоставляют свои собственные реализации EventDispatcher
.В связи с этим вам не нужно беспокоиться о том, как использовать EventDispatcher
.Вы даже не используете это напрямую.Скорее, вы добавляете EventHandler
s с помощью методов [add|remove]EventHandler
и [add|remove]EventFilter
, а также различные свойства onXXX
.
Когда вызывается buildEventDispatchChain
, скажем, Button
, Button
добавляет его EventDispatcher
к данному EventDispatchChain
.Затем он вызывает buildEventDispatchChain
на своем Parent
, если он есть.Это продолжается до корня Node
из Scene
.Корень Node
вызывает buildEventDispatchChain
на указанном Scene
, который после добавления EventDispatcher
делает то же самое на Window
, к которому он присоединен.
На этом этапе EventDispatchChain
полностью построен и готов к обработке Event
.Если еще не очевидно, EventDispatchChain
является просто "стеком" EventDispatcher
с.Другими словами, это узкоспециализированный java.util.Deque
, но фактически не расширяющий этот интерфейс.
Примечание: EventDispatchChain
также предоставляет метод append(EventDispatcher)
для ситуаций, в которых добавление является неправильной операцией .
Отправка события
Как только EventDispatchChain
будет полностью построен, пришло время фактически отправить Event
.Это можно сделать, вызвав этот метод для EventDispatchChain
:
Event dispatchEvent(Event event);
. В нем EventDispatchChain
get (pop) первый EventDispatcher
в стеке и вызов * его метода.:
Event dispatchEvent(Event event, EventDispatchChain tail);
Side Note: К сожалению, метод EventDispatcher
имеет аналогичную сигнатуру EventDispatchChain.dispatchEvent(Event)
, что может вызвать путаницу .
Side Note# 2: tail
будет одинаковым EventDispatchChain
на протяжении всего процесса .
Здесь действительно используются EventDispatcher
s.Вот алгоритм, используемый каждым EventDispatcher
как определено внутренним классом com.sun.javafx.event.BasicEventDispatcher
(исходный код Java 10):
@Override
public Event dispatchEvent(Event event, final EventDispatchChain tail) {
event = dispatchCapturingEvent(event);
if (event.isConsumed()) {
return null;
}
event = tail.dispatchEvent(event);
if (event != null) {
event = dispatchBubblingEvent(event);
if (event.isConsumed()) {
return null;
}
}
return event;
}
Шаги:
- Отправка
Event
для фазы захвата - Это вызывает все
EventHandler
с, которые были добавлены как фильтр
- Использование
Event
здесь не остановит отправку Event
in
этот шаг ; но остановит обработку Event
на последующих шагах
- Если
Event
используется, верните null
, иначе перешлите Event
на
tail
и дождаться его возвращения
- Если
tail.dispatchEvent
не возвращает null
, отправьте Event
для
пузырчатая фаза
- Если возвращенное
Event
было null
, это означает, что Event
было израсходовано
где-то вниз по цепочке и больше не нужно делать обработку
- Это вызывает все
EventHandler
s, которые были добавлены как обработчик (который
включает те, которые добавлены через свойства onXXX
)
- Как и на шаге 1, использование
Event
здесь не останавливает обработку
этот шаг ; но остановит обработку Event
на последующих шагах
- Если
Event
потребляется, вернуть null
, в противном случае вернуть Event
- Как и в случае
EventDispatchChain
, возврат null
означает, что Event
был
потребляется и обработка Event
должна прекратиться
Это делается для каждого EventDispatcher
в EventDispatchChain
. Вызов
tail.dispatchEvent
является в основном "рекурсивной" операцией (без
«реальная» рекурсия). По сути, этот код идет вниз по стеку (вызовы
tail.dispatchEvent
) и затем возвращается обратно в стек (когда tail.dispatchEvent
возвращается). И каждое «звено в цепочке» выполняет обработку перед «рекурсивным»
вызов (фаза захвата) и после «рекурсивного» вызова возвращается (пузырение
фаза).
Заметьте здесь, однако, что на каждом этапе это EventDispatcher
, который является
на самом деле вызывая каждый из соответствующих EventHandler
с. Вот как
EventDispatcher
используется .
Реализация своего собственного EventDispatcher
При расширении класса, который уже реализует EventTarget
, вам следует только
создайте свой собственный EventDispatcher
, когда абсолютно необходим . Если ваша цель заключается в
контролировать, если Event
достигает определенного EventTarget
, тогда ваш первый выбор должен
потреблять Event
в соответствующем месте (упомянутое Джай в
Комментарии). Если вы хотите что-то изменить в пути Event
, то вы
может потребоваться предоставить свой EventDispatcher
. Однако из-за закрытых
характер внутренних EventDispatcher
реализаций в сочетании с тем,
что интерфейс EventDispatcher
ограничен, вы, вероятно, будете ограничены
оборачивать оригинальную EventDispatcher
в вашу собственную реализацию и делегировать
когда необходимо. Я видел это сделано в коде других людей (возможно, даже видел
это в самой JavaFX) но я не могу вспомнить код достаточно хорошо, чтобы дать вам
примеры этого.
Если вы создаете собственный EventTarget
с нуля, тогда вам придется
реализовать свой собственный EventDispatcher
. Некоторые вещи, которые нужно иметь в виду, если вы делаете
нужна ваша собственная реализация:
- Он должен вызывать только
EventHandler
, зарегистрированные для текущей фазы (если
должны быть фазы)
- Он должен вызывать только
EventHandler
, зарегистрированные для Event
* EventType
и сказал EventType
супертипы
- Он должен направить
Event
к хвосту EventDispatchChain
- Он должен возвращать
null
только если Event
было израсходовано
- Должно быть , способное к одновременной модификации тем же
Thread
- Причина этого в том, что
EventHandler
может удалить себя или
добавить / удалить еще один EventHandler
, пока выполняется метод handle
. это
будет происходить, пока EventDispatcher
итерирует EventHandler
s
в некотором роде.
- Он должен "исправить источник"
Event
. В стандартной реализации JavaFX
каждый EventDispatcher
обновляет источник Event
до Object
связанный с этим EventDispatcher
(например, Node
). Это сделано в
подклассы вышеупомянутого com.sun.javafx.event.BasicEventDispatcher
.
Метод «исправить источник» - Event.copyFor(Object, EventTarget)
- Примечание: я не уверен, является ли это на самом деле частью контракта и может быть
необходимо, если ваша реализация не требует этого.