Focus delegation API

Michael Strauß michaelstrau2 at gmail.com
Sun Dec 8 23:12:53 UTC 2024


> Consider the following scenario: a compound control such as ComboBox, which has an editable TextField control as a part of its skin.  It might be expected that the ComboBox shows the focused border instead of its TextField even when the latter has the input focus.
>
> When the user presses and releases a key, the key events are first dispatched to the input focus owner which is the TextField.  This part is correct.

The focus owner in your example is the ComboBox, not the TextField.
The fact that the ComboBox skin uses a TextField in its implementation
is not a part of the user model. From the user's perspective, it is a
monolithic control with an unknown internal structure. Now, if you
click on a ComboBox, the only logical thing that can happen is that
the ComboBox receive the input focus.

More precisely, the following things must be true:
<ComboBox>.focused == true
Scene.focusOwner == <ComboBox>

This is how JavaFX is currently implemented. However, since JavaFX
doesn't provide the necessary tools to support these semantics, the
actual implementations are decidedly ad-hoc and held together by
hacks.


> The issue we seem to be struggling with is that, unlike the case when TextField is used as a top level control, now it is a part of a ComboBox.  Which means it should not handle some keys/events, instead delegating them to the top level control - there should be a single "controller" (in MVC parlance), instead of two fighting each other.  This statement applies to built-in controls as well as the custom controls.
>
> One way to accomplish that is to register a bunch of event filters with the top level control, to prevent the events to arrive at the inner control, forwarding these events to the top level controller, which in JavaFX case is the behavior.

Since the FakeFocusTextField that is contained in the ComboBox is not
the focus owner, it is not the target of input events. Delivering
events to the text field in order to make it work currently requires
the skin to fire a new event that is targeted at the text field. This
can be observed externally, and is an obvious defect (user presses a
single key, observers will see two key presses).


> The other way which I think will be easier, is to use the proposed InputMap to remove the undesired mappings from the inner control.  Doing so does not involve subclassing of the inner controls, since the input map and the mappings will be a public API.  With the mappings disabled (or remapped to the functions provided by the top level control), there is no contention between the two anymore.  The top level control's controller is in full control, it can do anything that's required to the inner controls - setting pseudo styles, inserting text, etc.  As an example - the right arrow key in the combo box's text field can be remapped to a function which checks if the caret is at the last position and then move the focus to the button, if so desired.  And if the caret is not at the last position, the default function of text area is invoked - all that enabled by the InputMap.

InputMap is only tangentially related to focus handling. Focus
delegation solves a whole array of problems that appear when you try
to implement a multi-level focus system, such as is the case in
JavaFX.

In addition to that, focus delegation and InputMap are different
levels of abstraction:
Focus delegation is a core feature of the framework (just like focus
is a core feature), while InputMap is one feature of one
implementation of controls. There is nothing special about
javafx.controls or its particular flavor of controls, skins, and
behaviors. Missing building blocks in the core framework can't be
solved in a controls library.


More information about the openjfx-dev mailing list