Tab key navigation in JavaFX TextArea
Asked Answered
O

7

16

How do I make hitting the Tab Key in TextArea navigates to the next control ?

I could add a listener to cath de key pressed event, but how do I make te TextArea control to lose it focus (without knowing the next field in the chain to be focused) ?

@FXML protected void handleTabKeyTextArea(KeyEvent event) {
    if (event.getCode() == KeyCode.TAB) {
        ...
    }
}
Overhear answered 12/10, 2012 at 13:46 Comment(0)
F
11

This code traverse focus if pressing TAB and insert tab if pressing CONTROL+TAB

textArea.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent event) {
            if (event.getCode() == KeyCode.TAB) {
                SkinBase skin = (SkinBase) textArea.getSkin();
                if (skin.getBehavior() instanceof TextAreaBehavior) {
                    TextAreaBehavior behavior = (TextAreaBehavior) skin.getBehavior();
                    if (event.isControlDown()) {
                        behavior.callAction("InsertTab");
                    } else {
                        behavior.callAction("TraverseNext");
                    }
                    event.consume();
                }

            }
        }
    });
Fiery answered 12/10, 2012 at 15:42 Comment(2)
One minor issue: it should have a check for event.isShiftDown() which should call "TraversePrevious", not "TraverseNext".Drapery
At least for JavaFX 8, SkinBase should be changed to TextAreaSkin.Distressful
G
13

I use the traverse-methods

@Override
public void handle(KeyEvent event) {
    if (event.getCode().equals(KeyCode.TAB)) {
        Node node = (Node) event.getSource();
        if (node instanceof TextField) {
            TextFieldSkin skin = (TextFieldSkin) ((TextField)node).getSkin();
            if (event.isShiftDown()) {
                skin.getBehavior().traversePrevious();
            }
            else {
                skin.getBehavior().traverseNext();
            }               
        }
        else if (node instanceof TextArea) {
            TextAreaSkin skin = (TextAreaSkin) ((TextArea)node).getSkin();
            if (event.isShiftDown()) {
                skin.getBehavior().traversePrevious();
            }
            else {
                skin.getBehavior().traverseNext();
            }
        }

        event.consume();
    }
}
Gaylagayle answered 6/2, 2015 at 9:21 Comment(5)
This solution is the cleanest I found. Although it's not necessary to define it for TextFields, since this is already a default (at least in Java 8).Mantling
Problem though, is that it forces you to extend TextArea - not very convenient if you use Scene Builder.Persas
... actually @Override threw me off, there is no handle method to override in a TextArea, so I guess this is just a regular handler method. Still, I wish there was an easier way - oh well.Persas
... actually, there is a way to make this easier to re-use! Put a static class event handler dedicated to this somewhere public, and call this on the TextArea: .addEventFilter(KeyEvent.KEY_PRESSED, new UsabilityUtil.TabTraversalEventHandler());Persas
TextAreaSkin could not be resolved in Java 1.8.0_311-b11 but MarcG's solution worked perfectly.Ebbie
F
11

This code traverse focus if pressing TAB and insert tab if pressing CONTROL+TAB

textArea.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent event) {
            if (event.getCode() == KeyCode.TAB) {
                SkinBase skin = (SkinBase) textArea.getSkin();
                if (skin.getBehavior() instanceof TextAreaBehavior) {
                    TextAreaBehavior behavior = (TextAreaBehavior) skin.getBehavior();
                    if (event.isControlDown()) {
                        behavior.callAction("InsertTab");
                    } else {
                        behavior.callAction("TraverseNext");
                    }
                    event.consume();
                }

            }
        }
    });
Fiery answered 12/10, 2012 at 15:42 Comment(2)
One minor issue: it should have a check for event.isShiftDown() which should call "TraversePrevious", not "TraverseNext".Drapery
At least for JavaFX 8, SkinBase should be changed to TextAreaSkin.Distressful
B
10

As of Java 9 (2017), most answers in this page don't work, since you can't do skin.getBehavior() anymore.

This works:

@Override
public void handle(KeyEvent event) {
    KeyCode code = event.getCode();

    if (code == KeyCode.TAB && !event.isShiftDown() && !event.isControlDown()) {
        event.consume();
        Node node = (Node) event.getSource();
        try {
            Robot robot = new Robot();
            robot.keyPress(KeyCode.CONTROL.getCode());
            robot.keyPress(KeyCode.TAB.getCode());
            robot.delay(10);
            robot.keyRelease(KeyCode.TAB.getCode());
            robot.keyRelease(KeyCode.CONTROL.getCode());
            }
        catch (AWTException e) { }
        }
    }

This also works:

@Override
public void handle(KeyEvent event) {
    KeyCode code = event.getCode();

    if (code == KeyCode.TAB && !event.isShiftDown() && !event.isControlDown()) {
        event.consume();
        Node node = (Node) event.getSource();            
        KeyEvent newEvent 
          = new KeyEvent(event.getSource(),
                     event.getTarget(), event.getEventType(),
                     event.getCharacter(), event.getText(),
                     event.getCode(), event.isShiftDown(),
                     true, event.isAltDown(),
                     event.isMetaDown());

        node.fireEvent(newEvent);            
        }
    }

Both simulate pressing CTRL+TAB when the user presses TAB. The default behaviour of the TextArea for CTRL+TAB is moving the focus to the next control. Please note the second code is based on Johan De Schutter's answer.

Bastien answered 5/12, 2017 at 4:11 Comment(1)
This solution works perfectly with Java 1.8.0_311-b11 as well. Thanks man.Ebbie
R
3

If a different solution for the Tab - Focus problem. The default behaviour of the TextArea for the CTRL+TAB key is a move of focus to the next control. So I replaced the TAB key event with a CTRL+TAB key event, and when the user hits CTRL+TAB a tab character is inserted in the TextArea.

My question: is it OK to fire an event in the event filter? And is it OK to replace the text of the KeyEvent with the FOCUS_EVENT_TEXT, in order to have an indication if it is the an event generated by the user, or from the event created in the event filter.

The event filter:

javafx.scene.control.TextArea textArea1 = new javafx.scene.control.TextArea();
textArea1.addEventFilter(KeyEvent.KEY_PRESSED, new TextAreaTabToFocusEventHandler());

The event handler:

public class TextAreaTabToFocusEventHandler implements EventHandler<KeyEvent>
{
    private static final String FOCUS_EVENT_TEXT = "TAB_TO_FOCUS_EVENT";

    @Override
    public void handle(final KeyEvent event)
    {
        if (!KeyCode.TAB.equals(event.getCode()))
        {
            return;
        }

        // handle events where the TAB key or TAB + CTRL key is pressed
        // so don't handle the event if the ALT, SHIFT or any other modifier key is pressed
        if (event.isAltDown() || event.isMetaDown() || event.isShiftDown())
        {
            return;
        }

        if (!(event.getSource() instanceof TextArea))
        {
            return;
        }

        final TextArea textArea = (TextArea) event.getSource();
        if (event.isControlDown())
        {
            // if the event text contains the special focus event text
            // => do not consume the event, and let the default behaviour (= move focus to the next control) happen.
            //
            // if the focus event text is not present, then the user has pressed CTRL + TAB key,
            // then consume the event and insert or replace selection with tab character
            if (!FOCUS_EVENT_TEXT.equalsIgnoreCase(event.getText()))
            {
                event.consume();
                textArea.replaceSelection("\t");
            }
        }
        else
        {
            // The default behaviour of the TextArea for the CTRL+TAB key is a move of focus to the next control.
            // So we consume the TAB key event, and fire a new event with the CTRL + TAB key.

            event.consume();

            final KeyEvent tabControlEvent = new KeyEvent(event.getSource(), event.getTarget(), event.getEventType(), event.getCharacter(),
                                                          FOCUS_EVENT_TEXT, event.getCode(), event.isShiftDown(), true, event.isAltDown(), event.isMetaDown());
            textArea.fireEvent(tabControlEvent);
        }
    }
}
Ricarda answered 11/10, 2017 at 10:24 Comment(0)
I
1

Inspired by the previous answers and for a very similar case, I built the following class:

/**
 * Handles tab/shift-tab keystrokes to navigate to other fields,
 * ctrl-tab to insert a tab character in the text area.
 */
public class TabTraversalEventHandler implements EventHandler<KeyEvent> {
    @Override
    public void handle(KeyEvent event) {
        if (event.getCode().equals(KeyCode.TAB)) {
            Node node = (Node) event.getSource();
            if (node instanceof TextArea) {
                TextAreaSkin skin = (TextAreaSkin) ((TextArea)node).getSkin();
                if (!event.isControlDown()) {
                    // Tab or shift-tab => navigational action
                    if (event.isShiftDown()) {
                        skin.getBehavior().traversePrevious();
                    } else {
                        skin.getBehavior().traverseNext();
                    }
                } else {
                    // Ctrl-Tab => insert a tab character in the text area
                    TextArea textArea = (TextArea) node;
                    textArea.replaceSelection("\t");
                }
                event.consume();
            }
        }
    }
}

I just have not seen the necessity of handling tab in the context of a TextField so I removed this part.

Then this class can be very easily used as described by User:

TextArea myTextArea = new TextArea();
mytTextArea.addEventFilter(KeyEvent.KEY_PRESSED, new TabTraversalEventHandler());

And the whole thing works like a charm :)

Ikeda answered 27/11, 2017 at 14:2 Comment(0)
R
0

I had the same issue and I like the traverse-methods that Tom uses. But I also want to insert a tab when ctrl+tab is pressed.

The call

behavior.callAction("InsertTab");

doesn´t work with JavaFX8. A look in the TextAreaBehaviour class showed me that there now is a "TraverseOrInsertTab" action.

But however, I think this kind of action calling is quite unstable across several java versions because it relies on a string that is passed.

So instead of the callAction() method, I used

textArea.replaceSelection("\t");
Recommendatory answered 30/9, 2017 at 12:48 Comment(0)
G
0

The TextAreaSkin class in JavaFX 16 (and also 17) does not have getBehavior() method. For preventing tabs inserted in a TextArea the solution the TextAreaTabToFocusEventHandler works for me

Gilmour answered 16/10, 2021 at 7:51 Comment(1)
accessing the behavior was always dirty - if we are allowed to, we can still access it with similar level of dirtyness and tweak the keyMappings in its inputMapLiverpool

© 2022 - 2024 — McMap. All rights reserved.