Prioritized event handlers
John Hendrikx
john.hendrikx at gmail.com
Sun Oct 29 10:16:08 UTC 2023
Hi Michael,
What you describe in the generalization sounds excellent, and would go a
step further and avoids the registration mechanism by instead doing
multiple dispatch/bubbling phases. The registration mechanism is more a
system to avoid doing that multiple times, but it could also be enforced
based on priority. The way that would work is to leave PREFERRED
handlers alone, but wrap any DEFAULT handlers to register themselves in
a default list and any SYSTEM handlers in a system list. This way
dispatch/bubbling occurs once, and after it finishes it is immediately
known what default or system handlers may still need calling (if any).
So just to clarify further how the registration mechanism would work (if
not clear already now):
It's not exactly a 2nd bubbling phase (although I suppose it could be),
the event already bubbles (or dispatches) past all possible event
handlers, but some handlers opt for not acting on the event
immediately. Instead they just register their interest in being called
if bubbling completes without the event being consumed. This 3rd phase
therefore only calls the registered handlers.
In other words, a Behavior could do:
addEventHandler(KeyEvent.KEY_PRESSED, e -> {
e.registerDefaultHandler(this::keyPressed);
// don't consume event; the above should be pretty low
overhead, same lambda is registered each time
});
Instead of its more usual:
addEventHandler(KeyEvent.KEY_PRESSED, this::keyPressed);
Internally, the registerDefaultHander call would probably need to keep
track of the original event (so the source/target are all correct); it's
code would be something like:
record DefaultRegistration(Event event, EventHandler eventHandler);
void registerDefaultHandler(EventHandler<? super T> handler) {
defaultRegistrations.add(new DefaultRegistration(this,
handler));
}
And calling them all at the end would just be something like:
void callDefaultHandlers() {
if (isConsumed()) return;
for (DefaultRegistrationregistration : defaultRegistrations) {
registration.eventHandler().handle(registration.event());
if (registration.event().isConsumed()) break;
}
}
----
If I can draw a comparison to something I've done in my own code; I had
a need for the various components in the scene graph to contribute
options to a global context menu. I solved this by registering an event
handler for a context menu event; each interested control or layer could
register on this event (the event is a subclass of Event with extra
capabilties) that they have context menu items to provide. At the root
level the event is then examined (by in this case another event handler
installed at the root), and all the interested parties were called to
provide their items and a single context menu was formed; some code:
publicclassPresentationEvent extendsEvent {
publicstaticfinalEventType<PresentationEvent> ANY=
newEventType<>(EventType.ROOT, "PRESENTATION");
publicstaticfinalEventType<PresentationEvent> CONTEXT_MENU=
newEventType<>(ANY, "PRESENTATION_CONTEXT_MENU");
publicstaticfinalEventType<PresentationEvent> REFRESH=
newEventType<>(ANY, "PRESENTATION_REFRESH");
privatefinalList<Presentation> presentations= newArrayList<>();
public voidaddPresentation(Presentation presentation) {
this.presentations.add(presentation);
}
publicList<Presentation> getPresentations() {
returnnewArrayList<>(presentations);
}
}
Each presentation would add itself as a filter in this case:
addEventFilter(PresentationEvent.ANY, e -> e.addPresentation(this))
--John
On 29/10/2023 05:20, Michael Strauß wrote:
> Hi John,
>
> from what I understand, you're essentially suggesting a third phase of
> event dispatch:
>
> 1. The event starts at the root and travels to the event target
> (capturing phase)
> 2. If the event is not consumed, it travels back to the root, but
> default handlers are excluded (bubbling phase)
> 3. If the event is still not consumed, the bubbling phase is repeated
> for default handlers.
>
> Maybe a generalization of that would be the following:
>
> Both the capturing phase and the bubbling phase are repeated for each
> priority level, beginning with the highest priority. In each
> iteration, the event is only received by handlers with the
> corresponding priority. If the event is consumed, it is not propagated
> further.
>
> Installing a handler with higher priority therefore has the effect of
> "hiding" all handlers with lower priority for the iteration that
> corresponds to the handler's priority.
>
> "Blocking default processing" could be achieved by marking the event
> as "skip for any lower priority level". This would complete the event
> dispatch for the current priority, and then stop.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20231029/f3cff232/attachment.htm>
More information about the openjfx-dev
mailing list