> Yes, each step of the event dispatch chain gets its own event instance.
Yep, this seems unnecessary and counterproductive to me. All we need is to drop the target field from the event. > 2. ComboBox's skin has installed an event filter on ComboBox So we have another scenario where different priorities are needed: adding event filters. > 3. However, it must forward the event to the TextField (as otherwise the TextField doesn't work), so it fires off a copy of the event targeted at TextField. Maybe instead, there should be a way to send the event to a Node directly, without bubbling up. These internal events should never propagate outside of the skin's internals. > Consumed events are dispatched to all listeners on the same step of the event dispatch chain. I've often suggested that we finally fix this bug. Agree. Moving to the second part: > 1. When you have abstraction, you also want encapsulation. Agree. Swing does not do it (combo box's text field is a top level component, being the to "focusOwner"), but I buy the idea of encapsulation of controls. > 2. From this it necessarily follows that the focus owner must always be the control, and not any part of its internal and opaque substructure. Agree. > 3. When the control is the focus owner, then it will receive all key events. We need a way to reconcile the JavaFX event system with the internal substructure, without exposing the internals to the outside world, and without changing the user model of the control. Agree. > This is part of what focus delegation solves: it separates different levels of abstraction. It allows key events to be sent to the control, which is exactly what users of a control would expect. Every layer of abstraction will see exactly what it would expect to see: an event that is targeted at itself, not at some other thing. > It also allows users to reason about events. For example, when a listener inspects a key event, it will see that the event is targeted at the ComboBox. If it was instead targeted at FakeFocusTextField, the listener couldn't reason about the event, as there is no such thing in the user model. Well, we don't need to add a bunch of weird properties for that (the first part). Just send the events to the skin's components directly, preventing the bubbling up part. There is no need for Event.target because there is no extraneous events being bubbled up, and both CB and TF can process the events as they come in. What do you think? -andy From: Michael Strauß <michaelstr...@gmail.com> Date: Monday, December 9, 2024 at 14:59 To: Andy Goryachev <andy.goryac...@oracle.com> Cc: openjfx-dev <openjfx-dev@openjdk.org> Subject: [External] : Re: Focus delegation API Hi Andy, your example highlights the observable defect that I've been trying to explain. Answers to your questions below. On Mon, Dec 9, 2024 at 10:27 PM Andy Goryachev <andy.goryac...@oracle.com> wrote: > > OK, let's backtrack a bit. > > consider this example > https://urldefense.com/v3/__https://github.com/andy-goryachev-oracle/Test/blob/main/src/goryachev/research/ComboBox_Events.java__;!!ACWV5N9M2RV99hQ!LKECD0WK6vtwHmtgrjyLZtP8ewEdwKDErX57HgvozbPNhmM-FGxTjzFchI_2JaLjjbmfjDLtbAPuQ-n2C5uFsQa3hx6M$<https://urldefense.com/v3/__https:/github.com/andy-goryachev-oracle/Test/blob/main/src/goryachev/research/ComboBox_Events.java__;!!ACWV5N9M2RV99hQ!LKECD0WK6vtwHmtgrjyLZtP8ewEdwKDErX57HgvozbPNhmM-FGxTjzFchI_2JaLjjbmfjDLtbAPuQ-n2C5uFsQa3hx6M$> > > it's a single combo box with application-level filters and handlers attached > to scene, combo box, and the combo box editor. > > pressing and releasing a key, we get the following output: > > > user presses 'a' key > 1. stage filter: KEY_PRESSED h=913 target=876 > 2. combobox filter: KEY_PRESSED h=457 target=876 > 3. stage filter: KEY_PRESSED h=487 target=553 > 4. combobox filter: KEY_PRESSED h=123 target=553 > 5. editor filter: KEY_PRESSED h=372 target=553 > 6. editor handler: KEY_PRESSED h=372 consumed! target=553 > [...] > This output is very puzzling, please help me understand it. > > - with the exception of events logged from the editor, the events being sent > are all different instances (h= prints the event's hashCode) Yes, each step of the event dispatch chain gets its own event instance. This is due to the immutable nature of the Event class, as on each step the source (and/or target) field must be changed; the way JavaFX does this is by copying the event instance and adjusting the fields in the process. > - where did the duplicate event in step 3 come from? step 9? step 15? That's the core of the issue. Here's what's happening, step by step: 1. ComboBox is the focus owner of the scene, so the KEY_PRESSED event is sent to the ComboBox. 2. ComboBox's skin has installed an event filter on ComboBox, and when it receives a key event, it consumes the event. This is why event dispatching stops after step 2 in your example. 3. However, it must forward the event to the TextField (as otherwise the TextField doesn't work), so it fires off a copy of the event targeted at TextField. The new event starts again at the root ("stage filter") and travels to the ComboBox ("combobox filter"). 4. When ComboBox receives the duplicate event, it inspects the target of the event, and realizes that it is targeted at the TextField. In this special case, the event is not consumed, but allowed to travel further. 5. Now the duplicate event reaches the TextField ("editor filter"), where nothing happens, and begins to bubble up ("editor handler"). This is where the event is consumed by the TextField I hope that you agree that the duplicated event, being visible to all listeners, is a defect. When a key is pressed once, a listener should not see two KEY_PRESSED events for the same key. > - why a consumed event is being dispatched to the editor in line 6? line 12? That's a bug. Consumed events are dispatched to all listeners on the same step of the event dispatch chain. I've often suggested that we finally fix this bug. > Maybe we should ask the question why in this case the Scene.focusOwner is the > ComboBox, and not its editor, the TextField? Having the actual component > that receives the input events to be the focus owner would eliminate the need > for hacks in the skin to send events, wouldn't it? > > Or, if we say that's the grand design of javafx - that the skins must forward > event copies to the skin constituents, then adding a handler to the top level > control (combo box in this case) should result in that handler to be called > before the skin's one, right? The core of JavaFX is just the scene graph, there's no concept of controls or skins (that's added by javafx.controls). I would say that a "skin", "shadow DOM", "internal/external" type of abstraction is a very useful abstraction, and JavaFX should support the creation of such abstractions. 1. When you have abstraction, you also want encapsulation. That means that the internal implementation of a control should not leak into its user model. Whether ComboBox contains a TextField, or whether it "custom draws" its text editor (for example, by writing pixels directly to a Canvas) shouldn't affect users of ComboBox. 2. From this it necessarily follows that the focus owner must always be the control, and not any part of its internal and opaque substructure. Users of ComboBox shouldn't have to change their code when they switch to a custom-drawing skin. 3. When the control is the focus owner, then it will receive all key events. We need a way to reconcile the JavaFX event system with the internal substructure, without exposing the internals to the outside world, and without changing the user model of the control. This is part of what focus delegation solves: it separates different levels of abstraction. It allows key events to be sent to the control, which is exactly what users of a control would expect. Every layer of abstraction will see exactly what it would expect to see: an event that is targeted at itself, not at some other thing. It also allows users to reason about events. For example, when a listener inspects a key event, it will see that the event is targeted at the ComboBox. If it was instead targeted at FakeFocusTextField, the listener couldn't reason about the event, as there is no such thing in the user model. > This, I think, brings us again to the discussion of event handling priority. > BTW, you never responded to my questions in > https://mail.openjdk.org/pipermail/openjfx-dev/2024-November/050655.html I did: https://mail.openjdk.org/pipermail/openjfx-dev/2024-November/050753.html