JavaFX TextArea 中的 Tab 键导航

Posted

技术标签:

【中文标题】JavaFX TextArea 中的 Tab 键导航【英文标题】:Tab key navigation in JavaFX TextArea 【发布时间】:2012-10-03 08:27:26 【问题描述】:

如何让 TextArea 中的 Tab 键导航到下一个控件?

我可以为按键按下事件添加一个侦听器,但是如何使 TextArea 控件失去焦点(不知道要聚焦的链中的下一个字段)?

@FXML protected void handleTabKeyTextArea(KeyEvent event) 
    if (event.getCode() == KeyCode.TAB) 
        ...
    

【问题讨论】:

【参考方案1】:

如果按 TAB,则此代码遍历焦点,如果按 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();
                

            
        
    );

【讨论】:

一个小问题:它应该检查 event.isShiftDown() 应该调用“TraversePrevious”,而不是“TraverseNext”。 至少对于 JavaFX 8,SkinBase 应该改为 TextAreaSkin。【参考方案2】:

我使用遍历方法

@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();
    

【讨论】:

这个解决方案是我发现的最干净的。虽然没有必要为 TextFields 定义它,因为这已经是默认设置(至少在 Java 8 中)。 但问题是,它会强制您扩展 TextArea - 如果您使用 Scene Builder,则不是很方便。 ...实际上@Override 让我失望了,TextArea 中没有要覆盖的句柄方法,所以我想这只是一个常规的处理程序方法。不过,我希望有一个更简单的方法 - 哦,好吧。 ...实际上,有一种方法可以让它更容易重复使用!将一个专门用于此的静态类事件处理程序放在公共的地方,并在 TextArea 上调用它: .addEventFilter(KeyEvent.KEY_PRESSED, new UsabilityUtil.TabTraversalEventHandler()); TextAreaSkin 在 Java 1.8.0_311-b11 中无法解析,但 MarcG 的解决方案运行良好。【参考方案3】:

我遇到了同样的问题,我喜欢 Tom 使用的遍历方法。 但我也想在按下 ctrl+tab 时插入一个选项卡。

电话

behavior.callAction("InsertTab");

不适用于 JavaFX8。查看 TextAreaBehaviour 类,我发现现在有一个“TraverseOrInsertTab”动作。

但是,我认为这种动作调用在多个 java 版本中非常不稳定,因为它依赖于传递的字符串。

所以我没有使用 callAction() 方法,而是使用了

textArea.replaceSelection("\t");

【讨论】:

【参考方案4】:

如果选项卡 - 焦点问题的不同解决方案。 CTRL+TAB 键的 TextArea 的默认行为是将焦点移动到下一个控件。所以我用 CTRL+TAB 键事件替换了 TAB 键事件,当用户按下 CTRL+TAB 时,TextArea 中会插入一个制表符。

我的问题:可以在事件过滤器中触发事件吗?是否可以将 KeyEvent 的文本替换为 FOCUS_EVENT_TEXT,以便指示它是用户生成的事件,还是来自事件过滤器中创建的事件。

事件过滤器:

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

事件处理程序:

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);
        
    

【讨论】:

【参考方案5】:

受先前答案的启发,对于一个非常相似的案例,我构建了以下类:

/**
 * 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();
            
        
    

我只是没有看到在 TextField 的上下文中处理选项卡的必要性,所以我删除了这部分。

那么这个类就可以如User所描述的那样非常容易地使用:

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

整个事情就像一个魅力:)

【讨论】:

【参考方案6】:

从 Java 9 (2017) 开始,此页面中的大多数答案都不起作用,因为您不能再使用 skin.getBehavior()

这行得通:

@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)  
        
    

这也有效:

@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);            
        
    

当用户按下TAB 时,两者都模拟按下CTRL+TABCTRL+TAB 的 TextArea 的默认行为是将焦点移动到下一个控件。请注意,第二个代码基于 Johan De Schutter 的回答。

【讨论】:

此解决方案也适用于 Java 1.8.0_311-b11。谢谢大佬。【参考方案7】:

JavaFX 16(以及 17)中的 TextAreaSkin 类没有 getBehavior() 方法。为了防止在 TextArea 中插入标签,TextAreaTabToFocusEventHandler 的解决方案适用于我

【讨论】:

访问该行为总是脏的——如果我们被允许,我们仍然可以在其 inputMap 中以类似的脏度和tweak the keyMappings 访问它

以上是关于JavaFX TextArea 中的 Tab 键导航的主要内容,如果未能解决你的问题,请参考以下文章

如何禁用 TextArea (JavaFX) 中的文本选择?

Javafx 8 替换 textarea 中的文本并保持格式

JavaFX:如何滚动到textarea中的特定行

Javafx 不接受文本字段的 textArea 中的其他字体或语言

将 SLF4J 日志重定向到 JavaFX 中的 TextArea

将 System.out 重定向到 JavaFX 中的 TextArea