Question regarding the order of events in the EDT

35 views
Skip to first unread message

Thomas

unread,
Sep 19, 2018, 9:34:28 PM9/19/18
to CodenameOne Discussions
In what order are events dispatched by the EDT in the component hierarchy?
The logic would be that events are dispatched from the component with the highest Z-order (the one "closest" to the screen and thus the user) up to the one witht the lowest Z-oder (the root form). But it doesn't seems to be the case. 
For example for a pointerPressed event, if I consume it at the form level, the components that are drawn in the layeredPane of this form never gets it (whereas they do if I do not consume it) whereas they are drawn in front of the form (= they have a higher z-order) and thus should receive the event before it reach the form (and gets consumed)...

Shai Almog

unread,
Sep 20, 2018, 3:01:49 AM9/20/18
to CodenameOne Discussions
All events reach the form first. It dispatches them onward.

  • Form callback methods (pointerPressed/Released etc.)
  • Form listener methods
  • Focused component (except for drag which is a bit of a special case)
We don't send events to all the components in the hierarchy except for the special case of drag where events are handled a bit differently.

Thomas

unread,
Sep 20, 2018, 11:13:24 AM9/20/18
to CodenameOne Discussions
I see. So events are dispatched in the direction parent -> child rather than beeing child -> parent.
That seems pretty illogic and useless to be able to consume an event in that case. Usually you consume an event so it isn't passed from a child component to its parent because the child, which is closest to the screen (in z dimension), already has reacted to the user interaction...
This is how every other Framework that I have tested and used so far works (they dispatch events from the innermost compenent in the component's hierarchy up to the root (=the last ancestor, that would be the Form in CN1)).
So, in CN1, how do I prevent an event from beeing used by an interactive component when the interaction was supposed to be on one of its childs? (for example if I have a button B over a larger component P that can also react to clicks, how do I prevent P to react to a click that would happen in the zone of B ?)

Shai Almog

unread,
Sep 21, 2018, 12:52:52 AM9/21/18
to CodenameOne Discussions
No. All lightweight frameworks start from the root frame or parent.

This is the most efficient and most sensible way to do this in a lightweight framework. You might be thinking of a heavyweight framework where the components are outside of the control of the framework and thus they are responsible for handling their own events. But if you look at the OS implementation and other lightweight frameworks you'll see that our implementation is how things actually work because it's the one that makes sense.

Here's why:

- You have one point where you can override the pointer behavior for everyone - the form this is very useful
- You don't pay the cost of going through all the components to process the event logic if you do this

Notice that the OS doesn't know about our components it just sends a pointer event. "Someone" needs to find the component to process, that code is in Form. If you override pointer handling there you effectively save us the cost of walking the component hierarchy to find the right child for the event at the given location.

This is a far more powerful and consistent approach, one of the problems with heavyweight frameworks is the inconsistent event dispatch behavior which they try to normalize from the component level.

Thomas

unread,
Sep 21, 2018, 1:28:43 PM9/21/18
to CodenameOne Discussions
You are wrong. All recents major ligthweighted frameworks support "events bubbling" (where events are passed from child to parent, as opposed to "events capturing" or "trickling", where event is passed from parent to child).  Flutter for example takes this approch (events are dispached by bubbling). The reason for that is that you may pay a really small performance price and events handling may be a bit more complicated internally, but the advantages for the developper are worth it. Indeed, when a user interact with your app, he naturally expects to interact with the element that is closest to him (= with the highest z-order). So if, in your app, you have a button over a lightweighted interactive map where you can add a marker on tap for example, the user naturally expect a tap on the button to trigger that button action and not to add a marker below that button on the map... With events bubbling, handeling this type of common use cases is straightforward.
As for having "one point where you can override the pointer behavior for everyone", with the bubbling approch, you just have to add a transparent component over all the others you want to override the pointer behavior for. As this transparent component covers all your other components (or just a specific zone of your screen if you want to), it would be called first durring the bubbling events dispatch phase so you can override the pointer behaviour from here...
But in some edge cases, it is true that events capturing might be a lighter approach (if you have a really complex components structure, events bubbling might affect your performances on low-end devices, even if I doubt it would be perceptible...). For this reason, most frameworks actually chose to use an hybrid approach. This is the case for example for flash AS3 (https://www.adobe.com/devnet/actionscript/articles/event_handling_as3.html) or HTML/javascript since the WC3 spécifications (http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html#Events-flow-basic) (old versions of internet explorer (<9) only supported events bubbling). This is the approach that I prefer and that I intend to implement into my CN1 Fork (and that you should probably implement into CN1). In this approach, there is first a Capturing phase followed by a Bubbling phase. So each component actually receive the event twice.
How I see it implemented into CN1:
 - during the capturing phase, the event object stores the components it pass through and call them in reverse order during the bubbling phase
 -  an event can have two states that are passed as arguments to the events listeners (or that can be called as event object methods isCapturing() or isBubbling()) so that a component event listener knows if it has to treat this event or to ignore it (as, by default, it might receive it twice, once on each phase)
- events can be consumed (in wich case they immediatly stop capturing and bubbling), stopCapturing (they would still go down (from parent to child) the components list to be able to perform the bubbling phase but won't call components event listeners during this capturing phase) or stopBubbling (they would stop the bubbling phase) and every of these methods (with their startCapturing() and startBubbling() counterpart) can be called at any point during the event propagation (for example if you call stopBubbling() from inside an event listener during the capturing phase, the capturing phase would continue and the event propagation would stop when it reach the start of the bubbling phase (unless startBubbling() has been called in the meantime)).     

Bytheway, you didn't answer my question on the first post on how to prevent, with the current CN1 implementation, a pointer event on a screen zone where a button B1 covers another larger button B2 to trigger B2 action for example...?

Shai Almog

unread,
Sep 22, 2018, 12:18:59 AM9/22/18
to CodenameOne Discussions
I was talking about events being handled at the top since they arrive from the canvas so I'm not wrong about that as that's literally the definition of lightweight implementation. Every implementation needs to go from root to child to find the child.

Sending events to every parent in the hierarchy has no valuable use case and can impact performance on large layouts. If you want to listen to an event or broadcast it you can do so easily. However, if we implemented something like that people will always pay the performance penalty whether they want to or not.

Web isn't a lightweight framework. It's a great example of SLOW because of various decisions e.g. automatic reflow and generic events. This makes a lot of sense for a high level scripting language but not for performance.

You wrote a lot but didn't mention a single programmer visible benefit for slowing everything considerably...

Events go to the top most focusable component. You can disable focusability and use features such as lead component to implement typical use cases. If you are building a game where you need to handle complex event logic overriding the event handling at the form level makes the most sense anyway.

Thomas

unread,
Sep 24, 2018, 10:35:43 PM9/24/18
to CodenameOne Discussions
I took a closer look at how CN1 is handeling pointer events and I must say it is kind of a mess.
Most of this is explained by the fact that the pointer events logic is directly merged into the Form, Container and Component classes (rather than having a dedicated general pointer events controller, with all the logic on a single point, wich is good coding practice and avoid logic inconsistencies that seems to occur in the current implementation, as component in the background beeing returned instead of the frontmost one when the frontmost focusable component is a container inside an other container alowing layout overlay or the getComponentAt() method beeing unecessarilly called twice, in the case of a pointerPressed call on a container...).
Also, it appears that the pointer listeners of a Form would always be called in the case of a pointer event as the call of these listeners appear before dispatching the pointer event to the front most focusable component. So, in fact, in the CN1 logic, if seems that, if you have a form F containing a focusable container Cc, containing a focusable component Co, a touch on Co would first trigger a PointerPressed event on the PointerPressedLiteners of F and then a PointerPressed event on the PointerPressedLiteners of Co (and PointerPressedLiteners of Cc would never be called). And theses two events would be distinct meaning that consuming the event triggered by the Form would not affect the event triggered by the component Co....
In my point of view, this is not how pointer events should be handled and a real events bubbling approach would be preferable (or, even better, an hybrid approach). And unlike you may say, this do not specifically affect performances. After all, Android, iOS and Flutter are all examples of lightweighted frameworks implementing events bubbling (and web frameworks, that I cited as example of hybrid approaches, may be "slow", but it is unrealated to the way they handle touch events). 
In a correctly implemented bubbling approach, the event propagation phase is distinct from the event reaction phase (where the event would be passed to event listeners). And, by default, the event reaction phase usually stops after the first component that notifies beeing interested into this event (so events are not sent to every parent of the hierarchy, like you say, thus performances are kept intact.) BUT it can also continue to dispatch the event to the parent if the developper decided to implement a component that shouldn't block dispatching the event to its parent after reacting to it. For example, this is how it is implemented in Android:  http://codetheory.in/understanding-android-input-touch-events/ 
This alows to easily implement some UI like blocking (where a touch the parent would have no effect) or non-blocking (where the parent would remain interactive) modals, drawers, user inactivity tracker...
And no, I am not implementing a game but I am implementing some generic UI components (like a drawer (~= side-menu) that I can add to any component (not just a Form)) and the lack of possibility to dispatch pointer events to more than one layer of the components hierarchy in some cases, complicates the creation of such components when it should be (and would be with a native lightweighted framework) straightforward ...

Shai Almog

unread,
Sep 25, 2018, 1:39:24 AM9/25/18
to CodenameOne Discussions
I agree it's a mess. It evolved over more than a decade trying to unify very different OS's. We need to maintain compatibility for very unique use cases.
Some of that design was made in 2007 when the primary target was still J2ME and adding additional abstraction classes was deemed too expensive.

That's the price you pay for maturity, some things are really hard to "fix" properly. The thing is, it's still performant which is what matters most to us.

You can implement blocking easily, just bind a listener to the form and consume the events. If that doesn't work for some cases that might be a regression.
Reply all
Reply to author
Forward
0 new messages