Hi Martin,

I'm still unsure how this exactly is supposed to go wrong.

Whenever you dispatch a new event, you should always follow-up and check if it was consumed after the dispatch finishes.  If it is, then you mark the triggering event also as consumed.  If this is done consistently everywhere, then the consumed flag should function as expected or not?

Code that does is simply wrong:

       e -> {
             node.fireEvent(new KeyEvent(...));  // can't check if it was consumed!
             // consume "e" or not?
       }

The above is a bug, and needs to be fixed if it is present in FX.

So the way I see it, every time you fire a new event in response to another, it is your responsibility to ensure that you check if the new event was consumed, and if it was, to mark the initial event as consumed as well.  Following this everywhere should make the consumed flag work as expected.

What am I missing?

--John

On 12/11/2024 19:14, Martin Fox wrote:
(Here’s hoping this e-mail makes it out to the list. If this is an ongoing 
problem I might have to switch to using my gmail address.)

I’m writing up some notes to improve the documentation on event dispatch. There 
seems to be only one rule governing dispatchers and that is “return null from 
dispatchEvent to end dispatch”. Beyond that anything goes.

 From what I can tell this PR will work if the event passed to the handler is 
the original event or a copy created using copyFor() and the event returned 
from dispatchEvent is either the same one that was passed to the handlers or a 
copy created using copyFor(). These are all reasonable assumptions but there’s 
nothing in the API that enforces them. In the worst case an event dispatcher 
may receive an event, pass an entirely new event to one of its handlers, and 
then return yet another entirely new event from dispatchEvent.

(In the codebase there are three classes that implement EventDispatcher and 
thirteen that extend an existing EventDispatcher. My guess is that all of them 
behave reasonably and copy their events using copyFor but I haven’t actually 
cracked open the code to verify that.)

At this point I’ll resort to hand-waving and speculation. Perhaps this should 
be divorced from events and reframed in terms of the dispatch process. Instead 
of “do this if this event is not consumed” it’s “do this if the current 
dispatch ends without consuming an event”. The unconsumed event handlers would 
then be attached to the dispatch chain instead of the event. And that’s about 
as far as I’ve thought this through.

Martin

On Nov 12, 2024, at 5:02 AM, John Hendrikx <john.hendr...@gmail.com> wrote:

I think `discardUnconsumedEventHandlers` is still a bit unpredictable, as it 
would depend on handler order.

The user handler would need to be called *after* Skin/Behavior handlers for it 
to be able to always discard any ifUnconsumed callbacks.  If the Skin is 
replaced, then it will re-register handlers and those will then be called 
last...

Of course, if we change Behaviors to do their work in filters, then we could 
get this to work properly.

--John

On 10/11/2024 08:12, Michael Strauß wrote:
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