(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