Home > database >  Javafx indexOutOfBoundsException within Textfield due to caret Postion
Javafx indexOutOfBoundsException within Textfield due to caret Postion

Time:03-07

I'm building a small app where TextField text is updated or deleted via buttons on screen. I have utilised code from this post to be able to move the caret around and add text. I can add and delete characters within the TextField however when n amount of characters are added to the TextField then n amount are removed an IndexOutOfBoundsException occurs when you attempt to add another character as the caret position (when all characters are removed) becomes 1 when it should be 0. I do not know why it becomes 1 when it should be 0. What have I done wrong here?

import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        try {
            
            BorderPane root = new BorderPane();
            Pane panel = new Pane();
            VBox box = new VBox();
            TextField tf = new TextField();
            Button characterButton = new Button("a");
            Button deleteChar = new Button("delete");
            
            box.getChildren().addAll(tf, characterButton, deleteChar );
            panel.getChildren().add(box);
            root.getChildren().add(panel);
            
            Scene scene = new Scene(root,400,400);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
            
            //handle caret pos
            AtomicInteger caretPos = new AtomicInteger();
            tf.caretPositionProperty().addListener((obs, oldVal, newVal) -> {
                System.out.println("newval"   newVal);
                if (tf.isFocused()) {
                    caretPos.set(newVal.intValue());
                    System.out.println("Innernewval"   newVal);
                }
            });
            //add character
            characterButton.setOnAction(( event) -> {
                Button btn = (Button)event.getSource();
                System.out.println("Pressed "   btn.getText()   " button");
                int pos = caretPos.get();
                tf.insertText(pos, btn.getText());
                tf.requestFocus();
                tf.positionCaret(pos 1);
                System.out.println("caretPos on add: "   caretPos.get());

            });
            //remove character
            deleteChar.setOnAction(( event) -> {
                Button btn = (Button)event.getSource();
                System.out.println("Pressed "   btn.getText()   " button");
                int newCaretPos = caretPos.get()-1;
                tf.deleteText(newCaretPos, caretPos.get());
                tf.requestFocus();
                tf.positionCaret(newCaretPos);   
                System.out.println("caretPos on delete: "   caretPos.get());
            });
            
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

CodePudding user response:

Your code does not compile. You have a catch without a try. In the below code, I removed the [orphan] catch block. Also, your style sheet, i.e. file application.css, is not related to your problem so in order to make the code in your question a minimal, reproducible example I suggest that you remove that part. It is commented out in the below code.

I only get IndexOutOfBoundsException when caretPos is zero and I press deleteChar button. Hence you simply need to add code that handles that edge case. In the below code, refer to the part after the comment Handle delete when 'caretPos' is zero.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        BorderPane root = new BorderPane();
        Pane panel = new Pane();
        VBox box = new VBox();
        TextField tf = new TextField();
        Button characterButton = new Button("a");
        Button deleteChar = new Button("delete");

        box.getChildren().addAll(tf, characterButton, deleteChar);
        panel.getChildren().add(box);
        root.getChildren().add(panel);

        Scene scene = new Scene(root, 400, 400);
//        scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.show();

        // handle caret pos
        AtomicInteger caretPos = new AtomicInteger();
        tf.caretPositionProperty().addListener((obs, oldVal, newVal) -> {
            System.out.println("newval: "   newVal);
            if (tf.isFocused()) {
                caretPos.set(newVal.intValue());
                System.out.println("Innernewval: "   newVal);
            }
        });
        // add character
        characterButton.setOnAction((event) -> {
            Button btn = (Button) event.getSource();
            System.out.println("Pressed "   btn.getText()   " button");
            int pos = caretPos.get();
            tf.insertText(pos, btn.getText());
            tf.requestFocus();
            tf.positionCaret(pos   1);
            System.out.println("caretPos on add: "   caretPos.get());

        });
        // remove character
        deleteChar.setOnAction((event) -> {
            Button btn = (Button) event.getSource();
            System.out.println("Pressed "   btn.getText()   " button");

            // Handle delete when 'caretPos' is zero.
            if (caretPos.get() == 0) {
                if (tf.getText().isEmpty()) {
                    return; // Nothing to delete.
                }
                else {
                    caretPos.set(1); // Ensure 'newCaretPos' will not be negative.
                }
            }

            int newCaretPos = caretPos.get() - 1;
            tf.deleteText(newCaretPos, caretPos.get());
            tf.requestFocus();
            tf.positionCaret(newCaretPos);
            caretPos.set(newCaretPos);
            System.out.println("caretPos on delete: "   caretPos.get());
        });
    }
}

Edit

You are correct. When I run your code with JavaFX 8, I do reproduce the problem. After some debugging, the problem is that when the TextField contains a single character and you press deleteChar, the caretPositionProperty listener does not run and hence caretPosition is not updated and remains with the value 1 (one) instead of 0 (zero).

There are probably many workarounds to this problem. I chose to add the below if statement after the line System.out.println("caretPos on delete: " caretPos.get()); in the event handler for deleteChar:

if (tf.getText().isEmpty()) {
    caretPos.set(0);
}

For the sake of completeness, here is the code I used to debug the program.

import java.util.concurrent.atomic.AtomicInteger;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            
            BorderPane root = new BorderPane();
            Pane panel = new Pane();
            VBox box = new VBox();
            TextField tf = new TextField();
            Button characterButton = new Button("a");
            Button deleteChar = new Button("delete");
            
            box.getChildren().addAll(tf, characterButton, deleteChar );
            panel.getChildren().add(box);
            root.getChildren().add(panel);
            
            Scene scene = new Scene(root,400,400);
//            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
            
            //handle caret pos
            AtomicInteger caretPos = new AtomicInteger();
            tf.caretPositionProperty().addListener((obs, oldVal, newVal) -> {
                System.out.println("newval"   newVal);
                if (tf.isFocused()) {
                    caretPos.set(newVal.intValue());
                    System.out.println("Innernewval"   newVal);
                }
            });
            //add character
            characterButton.setOnAction(( event) -> {
                Button btn = (Button)event.getSource();
                System.out.println("Pressed "   btn.getText()   " button");
                int pos = caretPos.get();
                System.out.println("###  DBUG  ### -> Adding: [Before 'insertText'] pos = "   pos);
                tf.insertText(pos, btn.getText());
                System.out.println("###  DBUG  ### -> Adding: [Before 'requestFocus'] pos = "   pos);
                tf.requestFocus();
                System.out.println("###  DBUG  ### -> Adding: [Before 'positionCaret'] pos = "   pos);
                tf.positionCaret(pos 1);
                System.out.println("caretPos on add: "   caretPos.get());
            });
            //remove character
            deleteChar.setOnAction(( event) -> {
                Button btn = (Button)event.getSource();
                System.out.println("Pressed "   btn.getText()   " button");
                int newCaretPos = caretPos.get()-1;
                System.out.println("###  DBUG  ### -> Deleting: [Before 'deleteText'] newCaretPos = "   newCaretPos);
                tf.deleteText(newCaretPos, caretPos.get());
                System.out.println("###  DBUG  ### -> Deleting: [Before 'requestFocus'] newCaretPos = "   newCaretPos);
                tf.requestFocus();
                System.out.println("###  DBUG  ### -> Deleting: [Before 'positionCaret'] newCaretPos = "   newCaretPos);
                tf.positionCaret(newCaretPos);   
                System.out.println("caretPos on delete: "   caretPos.get());
                if (tf.getText().isEmpty()) {
                    caretPos.set(0);
                }
            });
            
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

Here is the output when TextField contains a single character and I press deleteChar.

newval0
Pressed delete button
###  DBUG  ### -> Deleting: [Before 'deleteText'] newCaretPos = 0
###  DBUG  ### -> Deleting: [Before 'requestFocus'] newCaretPos = 0
###  DBUG  ### -> Deleting: [Before 'positionCaret'] newCaretPos = 0
caretPos on delete: 1

As you can see, the caretPositionProperty listener is not executed.

  • Related