Skip to content

Event Dispatch for ActionEvent

Jeanette Winzenburg edited this page Dec 20, 2019 · 32 revisions
Note
this is a draft for an overview on issues related to cancel/commit textual input. Is related to CommitOnFocusLost with focus more on options to solve them.

(Not only) TextField: Dispatch ActionEvent

Created

2018-07-17 22:25

Status

fixed fx14, integrated pull request #15

The issue is a broken event dispatch sequence for ENTER: normally, the singleton handler (registered with setOnKeyPressed) would be notified as first of the bubbling phase, consuming would stop further bubbling - which behaves as expected for any key except ENTER. In that case, the singleton handler is notified as last in the bubbling phase, even after the scene’s accelerators.

Created

2015-12-14 15:49

Status

fixed fx9 2016-06-21 01:45

EventFilter for ENTER on combo’s TextField is not notified. Reason is special casing of enter in many places (without, there’s an event explosiong, according to Jonathan), in particular forwardToParent. Fixed by firing the event directly on the textField after having set a value with key TextInputControlBehavior.DISABLE_FORWARD_TO_PARENT to true.

Created

2016-02-10 11:45

Status

fixed fx9 2016-06-29 01:14

Issue is that sequence in EventDispatch is broken. Fix (probably) introduced regression of JDK-8145515. The fixing of the former was to fire the enter key back into the TextField if not consumed - this fix does so only if not consumed and eventType is keyReleased.

Created

2019-08-20

Status

new

Similar to JDK-8149622 but for editable comboBox (and worse: both scene handler and filter are messaged twice)

Created

2016-03-23 15:21

Status

fixed fx9 2016-06-29 01:13

The issue was that the - potentially changed - text was not yet committed when receiving the actionEvent such that a formatter’s value != text. Solved by changing the sequence of method calls.

Created

2013-05-15 23:05

Status

fixed fx9 2015-10-14 02:19

The fixed changed the consume mechanics for ESCAPE: previously it was consumed always, with the fix it is consumed only if the field has a TextFormatter (besides adding a if-else block to the cancelEdit, the mapping was changed to not auto-consume and the else delegate to super - which essentially forwards to parent - instead of forwarding to parent directly). This led to unexpected behavior in TextField with formatter, a related question on SO: Escape from a Number TextField in a JavaFX dialog

Created

2018-07-16 10:57

Status

open fx11

The issue is that enter on focused textField in a custom menuItem triggers the action handler of the menuItem above the custom. The reason is (at least) twofold: 1. textFieldBehavior forwards the keyEvent to parent (because it can’t detect whether it is consumed or not - the example in the report/SO question should but doesn’t ) 2. bug in internal book keeping in the MenuItemContainer. Todo: check if XTextFieldSkin combined with consuming the event helps.

Created

2019-08-13

Status

New

This is critical because the mere presence of an EventFilter prevents the correct program flow f.i. it TextFieldBehavior.fire: it must be able to detect whether or not the actionEvent it sent around has been consumed and either consume (or not) the received ENTER, see JDK-8229467

Created

2012-08-03 18:02

Status

rfe, fixed fx14, merged pull request 580

EventHandlerManager’s methods dispatchCapturingEvent and dispatchBubblingEvent have TODOs: skip when no filters/handlers are registered in the CompositeEventHandler. This is not only a sanitary issue: a side-effect is to copy the fired event, such that the target/handler consumes that copy vs. the original - the sender has no means to test for consume.

Created

2014-03-27 08:37

Status

rfe, open fx12

This is specifically for ActionEvent: they are meant for a specific target.

Passing them through filters obviates any consuming, see comment to JDK-8092352 above.

Work on Improvements/Fixes

When/what/how enter/escape keys on a TextField are handled exactly, is an implementation detail of TextFieldBehavior and has undergone adhoc changes to fix issues (at least on face value). The mechanics are complex due to several (not necessarily orthogonal) states:

  • with/out TextFormatter

  • with/out onAction

  • with/out actionHandler added

  • standalone or editor in controls like ComboBox et al

Experiments with a general approach for all those (off core fx) are hampered by Behavior/InputMap not being public api and the behavior fields in controls being private and final. Consequently, dirty hacks are needed:

  • access the control’s behavior field reflectively

  • replace mappings in the behavior’s inputMap with custom mappings

The behavior’s "nearest" collaborator is TextFieldSkin, my extension is XTextFieldSkin. I’m in the process of increasing test coverage with TestFX (tests reside in de.swingempire.testfx.textinput starting with X/TextField)

Current (fx11) Implementation

Implementation of escape/enter mapping by TextFieldBehavior

Escape - keyMapping is not auto-consume, calls cancel(KeyEvent) which

  • with textFormatter,

    1. calls textField.cancelEdit()

    2. consumes keyEvent

  • without textFormatter

    1. calls super.cancelEdit(KeyEvent) (which effectively fires the keyEvent on parent)

Enter - keyMapping is auto-consume, calls fire(KeyEvent) which

  1. calls textField.commitValue()

  2. creates actionEvent with new ActionEvent(textField, null)

  3. fires the actionEvent on textField

  4. calls forwardToParent(keyEvent) if the action is not consumed and the field has no onAction handler (todo: why special case the mere having an onAction?)

Note the null as eventTarget, which leads to creating and passing around a copy immediately such the consumed state of the fired action is always false, see JDK-8207774

Resulting Semantics

Escape

consumed always with textFormatter, consumed never without textFormatter

Enter

consumed always with onAction, consumed never without onAction

Custom XTextFieldSkin

Implementation of escape/enter mapping by XTextFieldSkin

Escape - keyMapping is not auto-consuming, calls cancel(KeyEvent) which

  1. checks whether the textField is dirty (== has uncommitted changes)

  2. calls textField.cancelEdit()

  3. consumes the keyEvent if textField was dirty

Enter - keyMapping is not auto-consuming, calls fire(KeyEvent) which

  1. checks whether the textField is dirty

  2. calls textField.commitValue()

  3. creates an actionEvent with new ActionEvent(textField, textField)

  4. fires the actionEvent on textField

  5. consumes the keyEvent if textField was dirty or the action is consumed or the field has a onAction (todo: special case the mere having an onAction taken from core, not quite sure if we should)

Notes

  • the dirty check compares the textField’s text against the formatter’s converted value (todo: might not be good enough with real formats?) and returns true if they are not equal. TextFields without formatter are never dirty.

  • passing in the textField as eventTarget to the action prevents its immediate copy, alleviating JDK-8207774

  • that’s not good enough if there are eventFilters around in the parent dispatch chain, see JDK-8229467. The hack around is to store the fired event in the field’s property map and provide a utility method that client code can use to consume it. Requires collaboration of clients to be effective.

Resulting Semantics

Escape

consumed if text is dirty

Enter

consumed if text is dirty or the fired actionEvent was consumed (todo: or the field has an onAction handler)

Work on OpenJFX

(Limited) Fix for JDK-8207774

Changing the created ActionEvent to have a not-null target fixes the issue in the scope of the TextFieldBehavior (see pending pull request 575). Can do nothing in that scope to really solve: the sender has no control about potential copies being passed around.

Options (none really attractive)

  • provide hack as in XTextFieldSkin by storing the sent action: requires knowledge of the hack in client scope

  • fix JDK-8092352 to prevent copies that are not used, WIP - alleviates but doesn’t solve the problem

  • think about an enhanced dispatch mechanism for (not only) actionEvents, as suggested in JDK-8091151 - sounds like an epic change

Fix for JDK-8092352

The issue is formalized from a code comment in EventHandlerManager:

if (compositeEventHandler != null) {
    // TODO: skip when no filters are registered in the
    //       CompositeEventHandler (RT-23952)
    event = fixEventSource(event, eventSource);
    compositeEventHandler.dispatchCapturingEvent(event);
}

The event is fixed (aka: copied) even if the composite handler is empty, that is has no filters/handlers.

To prevent the copy at in cases where it’s not needed we need:

  • api on composite handler to check whether it contains filters/handlers

  • use the new api along with the null check to skip the copy/dispatch if empty in filters/handlers

CompositeEventHandler keeps the contained handlers in (basically) a linked list of EventProcessorRecords: a record contains

  • the actual handler (guaranteed to be not null)

  • state (by extension of basic record) of being either filter or handler

  • implementations of bubbling/capturing as no-op for the state which doesn’t apply

  • disconnected flag to support weakEventHandler

Summary:

Table 1. EventProcessorRecord
HandlerRecord FilterRecord

eventHandler

normal

weak

normal

weak

capturing

no-op

handle

bubbling

handle

no-op

isFilter

false

true

stores

!isFilter

isFilter

disconnected

false

garbageCollected

false

garbageCollected

Clone this wiki locally