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.