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