Prioritized event handlers

Tobias Oelgarte tobias.oelgarte at gmail.com
Fri Nov 8 20:46:45 UTC 2024


This whole situation reminds me very much of Shadow DOM [1], an approach 
for components (controls/skins) with a complex internal structure that 
should not be visible from the outside. The handling of events could 
serve as a template here. [2]

[1] 
https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM
[2] https://pm.dartus.fr/posts/2021/shadow-dom-and-event-propagation/

On 07.11.24 20:06, Andy Goryachev wrote:
>
> > 4. The target allows you to register the same event handler instance 
> multiple times on multiple different controls without having to create 
> a new lambda each time that knows which control (target) it is for. It 
> has a similar function to the Observable parameter in 
> InvalidationListeners and ChangeListeners.  In effect, having the 
> Target will also make it possible in the future to share a single 
> input map with all controls of the same type (currently there is a ton 
> of memory being wasted here duplicating input maps, especially for 
> controls with large input maps like TextFields).
>
> This is a very strange rationale, in my opinion.  And why talk about 
> Controls when the Event.copyFor() is at the very fundamental level?  
> To me, this severely complicates everything, including breaking 
> isConsumed() logic.  Sending events that are not designed to bubble up 
> seems to run contrary to the whole event dispatching idea.  Are you 
> trying to explain the current implementation?  Swing never needed to 
> store the target in the event, and it works just fine.
>
> This brings me to another problem I am struggling with: why expose the 
> event dispatchers??  This should have never been done, in my opinion.  
> Swing's FwDispatcher is an implementation detail.  But instead, we 
> seem to be dealing with unnecessarily increased complexity, including 
> misbehaving EventDispatcher implementations.
>
> May be it's just me, I don't see the beauty of this thing.
>
> -andy
>
> *From: *openjfx-dev <openjfx-dev-retn at openjdk.org> on behalf of John 
> Hendrikx <john.hendrikx at gmail.com>
> *Date: *Thursday, November 7, 2024 at 05:35
> *To: *openjfx-dev at openjdk.org <openjfx-dev at openjdk.org>
> *Subject: *Re: Prioritized event handlers
>
> Hi Andy,
>
> Simply replacing a Skin can change the order of event handlers.  In 
> other words, if you registered your handler **after** the control was 
> shown, then replace its Skin (which can be done by CSS state changes), 
> then your handler is now called before the Skin/Behavior handlers.  
> Also removing your own handlers, then installing them back (in the 
> same order) will not lead to the same result either.  The culprit is 
> simply that two different parties (FX and the User) are sharing the 
> same system -- this is mostly fine for properties, but Events are 
> stateful, and so the processing order matters.
>
> I've given this quite a lot of thought recently, and I also looked at 
> the Spinner problem, and I think I do have an all encompassing 
> solution for both the ordering problem and the Spinner problem.
>
> Observations:
>
> Controls have some trouble keeping their Skin internals hidden from 
> the outside world.  For the user, the Spinner is a black box; it is 
> the thing that gets the focus and accepts input, even though the 
> actual implementation may be delegating this to an internal component 
> (in the case of the default SpinnerSkin, a FakeFocusTextField -- a 
> detail that you can see "leaking" out when registering a listener on 
> Scene#focusOwner).
>
> Secondly, because Skins are re-using normal controls, which only work 
> when focused, they sometimes have to "fake" the focus (this is a 
> problem I won't be solving in this post).  IMHO however, this should 
> be handled differently altogether, perhaps with some kind of focus 
> delegation mechanism as part of Behaviors -- the user observable focus 
> should IMHO always be the Control, as it is a black box.  The current 
> fake focus implementation tries to keep this hidden (by not firing 
> change listeners) which is incredibly dubious, and also fails (you can 
> still see internals being focused with Scene#focusOwner).
>
> Analysis:
>
> Inside SpinnerSkin, there is a lot of jumping through hoops in the 
> form of filtering events, copying them, and being aware of what the 
> Behavior needs (SpinnerSkin for example knows that the Behavior has 
> need of the UP/DOWN/LEFT/RIGHT keys, it shouldn't...) -- all this 
> jumping through hoops is to make the internal TextField component 
> receive events, but at the same time, not confusing the Behavior with 
> duplicated events (as the Behavior handlers are in between the event 
> root (the window) and the target (the internal textfield) events flow 
> past the Behavior's handlers).
>
> Solution:
>
> The Behavior event handlers are at the Control level -- as far as the 
> user is concerned, this is the target and also final destination 
> (there is nothing deeper).  In effect you could say that the Behavior 
> could register either a Filter or a Handler, and the result would be 
> the same -- filtering down from Scene -> Node -> Spinner and then 
> bubbling up from Spinner -> Node -> Scene, it shouldn't matter if the 
> Behavior registers a filter or a handler as they're at the same 
> position (of course there are some small differences, but I think we 
> actually WANT filters in behaviors, more on this later).
>
> One of the problems is that the event handlers in the behavior trigger 
> **after** the internal TextField -- but we have the perfect mechanism 
> to swap this around; if the Behavior registered a filter (which as I 
> said earlier is sort of at the same position conceptually) then it 
> would trigger **before** the internal TextField.  Spinner is then free 
> to process and consume the arrow keys it needs, and the Skin would not 
> need to be aware of this (ie. if a different Behavior decides to 
> consume the +/- keys, then it would work with the current Skin).  The 
> Skin then just forwards any events that arrive at the Control level 
> still (everything minus potential arrow keys consumed by the Behavior) 
> to the internal text field.
>
> The second part of this solution relates to how to get Events to go 
> down to the internal TextField.  Events are fired at the current focus 
> owner, which is the Control. The TextField only has fake focus.  So we 
> need to take events that arrive at the Control, copy them and send 
> them to the internal TextField.  However, there is a small problem -- 
> the standard event mechanism will make such copies filter down from 
> Window level all the way to the TextField level (passing the Spinner 
> control).  But this isn't want we want at all.  Spinner is a black 
> box; when we want to fire an event into the internals, we don't want 
> this event to be exposed either (aside from the problems that causes, 
> with having to be careful not to copy the event again as it will pass 
> your forwarding handler again).  In effect, we want a different event 
> dispatch hierarchy that is limited to the Control only. So, why not 
> build a dispatch chain that exclusively targets the internals?  This 
> chain in this case only consists of one EventDispatcher, the one from 
> the internal TextField.  The event copy is fired at it and the control 
> works as expected.  It automatically also solves the "ENTER" problem 
> described in https://bugs.openjdk.org/browse/JDK-8337246 without 
> further hacks.
>
> Takeaways:
>
> I now think Behaviors should always be doing their event handling in 
> filters, for the simple reason that they should get to act first 
> before the Skin gets to act on an event; events arriving at the 
> Control level which is the final destination can be handled by either 
> a handler or a filter and there would be no discernible difference.  
> Not only does this solve the problem with the internal text field and 
> the Skin needing to be aware of what keys Behavior will be handling, 
> but it also then makes behaviors always act before user event 
> handlers, creating consistency here (ie. replacing a Skin will not 
> affect event handler order).  This frees up handlers for user 
> purposes.  The ordering problem moves to the filters, so we may still 
> want to do a solution here (see below).
>
> Event handler order when Skins get re-added:
>
> Handlers registered by users can change order with regards to handlers 
> registered by Behaviors when the Skin change or depending on when the 
> user registered their handlers.  This is problematic for events as 
> they are stateful and can be consumed, and so an order change can lead 
> to a functionality change. This is not user friendly, as the user 
> expects that the event handler and filter infrastructure is for their 
> use alone.  I can see a few things we could explore further:
>
> - See if it is possible to add some internal API that allows Behaviors 
> to register filters or handlers first/last -- note that "last" here 
> means **always** last, even when user handlers are added later (first 
> would be an easier option).
> - See if it is possible to change how `buildEventDispatchChain` works 
> for Controls to include another dispatcher level exclusively for 
> Behaviors. Where a standard chain would look like 
> Window->Scene->Nodes->Control, it would become 
> Window->Scene->Nodes->Control->Behavior(private) -- as this is a 
> completely separate level, handlers from users and behaviors cannot 
> become intertwined and so there will always be a predictable ordering
>
> On 07/11/2024 01:03, Andy Goryachev wrote:
>
>     Dear Michael:
>
>     What happened to this proposal?  I would like to restart the
>     discussion, if possible.
>
>     More specifically, I would like to discuss the following topics:
>
>      1. the reason the discussion was started was due to "priority
>         inversion" problem in Controls/Skins, ex.: JDK-8231245
>         <https://bugs.openjdk.org/browse/JDK-8231245> Controls'
>         behavior must not depend on sequence of handler registration. 
>         Do we have this problem elsewhere?  In other words, does it
>         make sense to change the API at the EventDispatcher level when
>         the problem can be easily solved by the InputMap at the
>         Control level?
>      2. dispatching of events that have been consumed (as mentioned in
>         the earlier discussion)
>      3. problem of creating unnecessary clones of events via
>         Event.copyFor(), leading to ex.: JDK-8337246
>         <https://bugs.openjdk.org/browse/JDK-8337246> SpinnerSkin does
>         not consume ENTER KeyEvent when editor ActionEvent is consumed
>      4. why do we need Event.copyFor() in the first place?why does
>         Event contain the target??
>
> 1. You would have the same problem with other events that Behaviors 
> listen to (ie. mouse events), or events they handle without going 
> through the input map (TYPED, RELEASED), or events they handle with a 
> parent event type (all KeyEvents, not a specific one).
>
> 2. This is IMHO a bug, it only happens for the same event type at one 
> level, it should simply be fixed
>
> 3. See above, but in short, I think the problem is not the event 
> clones, but the fact that these clones are traversing the entire 
> Window->Scene->Node hierarchy.  Events with a restricted hierarchy 
> solve this.
>
> 4. The target allows you to register the same event handler instance 
> multiple times on multiple different controls without having to create 
> a new lambda each time that knows which control (target) it is for. It 
> has a similar function to the Observable parameter in 
> InvalidationListeners and ChangeListeners.  In effect, having the 
> Target will also make it possible in the future to share a single 
> input map with all controls of the same type (currently there is a ton 
> of memory being wasted here duplicating input maps, especially for 
> controls with large input maps like TextFields).
>
> --John
>
>     Too many topics, yes, we might want to split the discussion into
>     separate threads.
>
>     -andy
>
>     *From: *openjfx-dev <openjfx-dev-retn at openjdk.org>
>     <mailto:openjfx-dev-retn at openjdk.org> on behalf of Michael Strauß
>     <michaelstrau2 at gmail.com> <mailto:michaelstrau2 at gmail.com>
>     *Date: *Friday, October 27, 2023 at 19:41
>     *To: *openjfx-dev <openjfx-dev at openjdk.org>
>     <mailto:openjfx-dev at openjdk.org>
>     *Subject: *Re: Prioritized event handlers
>
>     Here is the proposal:
>     https://gist.github.com/mstr2/4bde9c97dcf608a0501030ade1ae7dc1
>
>     Comments are welcome.
>
>
>     On Fri, Oct 27, 2023 at 8:21 PM Andy Goryachev
>     <andy.goryachev at oracle.com> <mailto:andy.goryachev at oracle.com> wrote:
>     >
>     > Would it be possible to create a proposal in the JEP format
>     outlining the proposed public API?
>     >
>     >
>     >
>     > Thank you
>     >
>     > -andy
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20241108/6edbcaab/attachment-0001.htm>


More information about the openjfx-dev mailing list