In JavaFX, user code and skins share the same event system. Since
invocation order is fundamentally important for events, this leads to
a lot of problems when skins add event handlers to their controls.
Assume user code adds an event handler to a control, and _then_ sets a
skin that also adds an event handler for the same event. In this case,
the user-provided handler is invoked first. If the skin is set
_first_, the skin gets the first chance to handle the event (see also
https://bugs.openjdk.org/browse/JDK-8231245).

Prioritized event handlers might be a solution for this problem, but
they are quite difficult to get right. Instead, I think we can get
almost all of the benefits using a much simpler solution: unconsumed
event handlers.

We add a new method to the `Event` class:

    <E extends Event> void ifUnconsumed(EventHandler<E> handler)

When an event filter or an event handler receives an event, it calls
the `ifUnconsumed` method with another event handler. Then, after both
phases of event delivery have completed, the list of unconsumed event
handlers associated with the event is invoked in sequence. Once an
unconsumed event handler consumes the event, further propagation is
stopped.

Skins and their behaviors would then always use the unconsumed form of
event handlers:

    // inside a skin/behavior
    control.addEventHandler(
        KeyEvent.KEY_PRESSED,
        e -> e.ifUnconsumed(event -> {
            // act on the event
        }));

This allows user code to see an event during both the capture and
bubble phases without any inteference of the skin or behavior. If user
code doesn't consume the event, the skin or behavior gets to act on
it.

In addition to that, we add a second new method to the `Event` class:

    void discardUnconsumedEventHandlers()

Calling this method in an event handler allows user code to discard
any unconsumed event handlers that have been registered prior. Let's
consider an example where user code wants to prevent a control skin
from acting on the ENTER key event, but it also doesn't want to
consume the event. Adding an event handler to the control, and then
calling `discardUnconsumedEventHandlers()` inside of the event handler
achieves exactly that: it allows the ENTER key event to flow freely
through the control without interacting with it, since its unconsumed
event handlers will never be called.

Here is the PR for this proposal: https://github.com/openjdk/jfx/pull/1633

Reply via email to