I try to do an application with JavaFX.
I have a little question :
I have a TextField and i would like it will be a TextField for "phone number" (0477/40.00.09 for example).
Unfortunatly, i can't do this.
I would like that '/' and '.' are always write in the TextField.
I also would like when the user has wrote the fourth first numbers, the TextField set the cursor behind the '/'. So the user don't take care for '/' and '.'. He has just to type the number of the phone number.
How can i do this ?
Thanks for your help,
Mikis
(Sorry for my English ....)
You can use a TextFormatter to control the text inside a TextField.
PhoneNumber TextField described in the question should do the following:
Add / after 4 characters
Add . after 7 and 10 characters
Doesn't allow more than 13 characters
Here is a sample which does all of the above:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TextFieldFormatter extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
final TextField textField = new TextField();
textField.setTextFormatter(new TextFormatter<String>(change -> {
final int oldLength = change.getControlText().length();
int newLength = change.getControlNewText().length();
// Handle backspace
if (newLength < oldLength) return change;
// Add / after 4 characters
// Add . after 7 and 10 characters
// Do not accept more than 13 characters
switch (newLength) {
case 4 :
change.setText(change.getText() + "/");
newLength++;
break;
case 7: case 10:
change.setText(change.getText() + ".");
newLength++;
break;
case 14:
return null;
}
// Set caret position
change.setCaretPosition(newLength);
change.setAnchor(newLength);
return change;
}));
BorderPane root = new BorderPane();
root.setCenter(textField);
Scene scene = new Scene(root, 200, 200);
stage.setScene(scene);
stage.setTitle("TextField Example");
stage.show();
}
}
You can addListener() to TextField propertyText and control the inputs.In your question you did not put any condition so just you need a useful implementation for that you can use this simple code :
TextField field = new TextField();
field.setPromptText("xxxx/xx.xx.xx");
int maxDigits = 13;
field.textProperty().addListener((ObservableValue<? extends String> observable, String oldValue, String newValue) -> {
if (newValue.length() == 4 && oldValue.length() != 5) {
field.setText(newValue + "/");
} else if (newValue.length() == 7 && oldValue.length() != 8 || newValue.length() == 10 && oldValue.length() != 13) {
field.setText(newValue + ".");
}
if (newValue.length() > maxDigits) {
field.setText(oldValue);
}
});
Finaly, i found what i was searching in this project :
https://github.com/jidesoft/jidefx-oss
But i adapted it to my needs. Do I put the code for the new object "MyMaskTextField" ?
Related
In my application, there are two scenes: mainScene and bossScene where mainScene is used when starting up the application.
I'm trying to implement the boss key functionality where by pressing the 'b' key on the the keyboard should change the scene to bossScene. And also by pressing the button in bossScene should switch back to mainScene.
I'm getting an error on InteliJ saying "Cannot resolve method setOnKeyPressed in List
My Code:
public void start(Stage stage) throws Exception {
stage.setTitle("BossKey Example");
// Scene and layout for the main view
VBox root = new VBox();
Scene mainScene = new Scene(root, 500, 300);
// Scene for the BOSS view
Scene bossScene = new Scene(new Label("Nothing suspicious here"), 500, 300);
List<TextField> fields = new ArrayList<TextField>();
for (int i = 0; i < 10; i++) {
fields.add(new TextField());
}
fields.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent keyEvent) {
switch (keyEvent.getCharacter()){
case "b": stage.setScene(bossScene); break;
}
}
});
/////// Added addEventFilter, still not working
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, new
EventHandler<KeyEvent() {
#Override
public void handle(KeyEvent keyEvent) {
switch (keyEvent.getCharacter()){
case "b": stage.setScene(bossScene); break;
}
keyEvent.consume();
}
});
// Create components for main view
root.getChildren().addAll(fields);
root.getChildren().add(new Button("Hello!"));
stage.setScene(mainScene);
stage.show();
}
}
KeyCombination filters
You should use a key combination in an event filter, e.g., CTRL+B or SHORTCUT+B.
For details on how to apply key combinations, see:
javafx keyboard event shortcut key
Why a key combination is superior to filtering on the character "b":
If you filter on a "b" character, the feature won't work if caps lock is down.
If you filter on a "b" character, you will be unable to type "b" in the text field.
You might think you could write scene.setOnKeyPressed(...), however, that won't work as expected in many cases. A filter is required rather than a key press event handler because the key events may be consumed by focused fields like text fields if you use a handler, so a handler implementation might not activate in all desired cases.
Filtering on a key combination avoids the issues with trying to handle a character key press. The key combinations rely on key codes which represent the physical key pressed and don't rely on the state of other keys such as caps lock unless you explicitly add additional logic for that.
If you don't understand the difference between an event filter and an event handler and the capturing and bubbling phases of event dispatch, then study:
the oracle event handling tutorial.
KeyCombination filter implementation
final EventHandler<KeyEvent> bossEventFilter = new EventHandler<>() {
final KeyCombination bossKeyCombo = new KeyCodeCombination(
KeyCode.B,
KeyCombination.CONTROL_DOWN
);
public void handle(KeyEvent e) {
if (bossKeyCombo.match(e)) {
if (stage.getScene() == mainScene) {
stage.setScene(bossScene);
} else if (stage.getScene() == bossScene) {
stage.setScene(mainScene);
}
e.consume();
}
}
};
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
bossScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
Accelerator alternative
An accelerator could be used instead of an event filter. Information on applying an accelerator is also in an answer to the linked question, I won't detail this alternative further here.
Example Solution
Standalone executable example code:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.io.IOException;
public class SceneSwap extends Application {
#Override
public void start(Stage stage) throws IOException {
final Scene mainScene = new Scene(
createLayout(
"Press CTRL+B to enter boss mode",
Color.PALEGREEN
)
);
final Scene bossScene = new Scene(
createLayout(
"Press CTRL+B to exit boss mode",
Color.PALEGOLDENROD
)
);
final EventHandler<KeyEvent> bossEventFilter = new EventHandler<>() {
final KeyCombination bossKeyCombo = new KeyCodeCombination(
KeyCode.B,
KeyCombination.CONTROL_DOWN
);
public void handle(KeyEvent e) {
if (bossKeyCombo.match(e)) {
if (stage.getScene() == mainScene) {
stage.setScene(bossScene);
} else if (stage.getScene() == bossScene) {
stage.setScene(mainScene);
}
e.consume();
}
}
};
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
bossScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
stage.setScene(mainScene);
stage.show();
}
private VBox createLayout(String text, Color color) {
VBox mainLayout = new VBox(10,
new Label(text),
new TextField()
);
mainLayout.setPadding(new Insets(10));
mainLayout.setStyle("-fx-background: " + toCssColor(color));
return mainLayout;
}
private String toCssColor(Color color) {
int r = (int) Math.round(color.getRed() * 255.0);
int g = (int) Math.round(color.getGreen() * 255.0);
int b = (int) Math.round(color.getBlue() * 255.0);
int o = (int) Math.round(color.getOpacity() * 255.0);
return String.format("#%02x%02x%02x%02x" , r, g, b, o);
}
public static void main(String[] args) {
launch();
}
}
I am trying to track input to TextField and allow user to input only 1 symbol per TextField, here is my code:
package sample;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
TextField textField = new TextField(); //creating new textfield
Pane window = new Pane();
Parent root = window;
window.getChildren().addAll(textField); //adding textfield to the window
primaryStage.setScene(new Scene(root, 200, 50));
primaryStage.show();
textField.textProperty().addListener(event ->
{
try {
if (textField.getLength() > 1) { //check if the length of the textfield text exceeds 1
System.out.println("NOT Accepted");
textField.setText(String.valueOf(textField.getText().charAt(0))); //set textfield text to first char only
} else {
System.out.println("Accepted");
}
} catch (IndexOutOfBoundsException Bound) {}
}
);
}
public static void main(String[] args) {
launch(args);
}
}
When I press any symbol first time, everything works good, but when I press second time, event listener repeats 3 times. Here the exapmle:
"a" key has been pressed, console output:
Accepted //<---Correct
"a" key (or any another key) has been pressed second time, console output:
NOT Accepted //<---Correct
Accepted //<---Not correct
Accepted //<---Not correct
As shown above listener repeats 3 times.
But I expect that console should show only "NOT Accepted" and shouldn't repeat 2 times more.
Instead of using a ChangeListener, I recommend a simple TextFormatter instead. This allows you to prevent the change without triggering another event.
textField.setTextFormatter(new TextFormatter<String>((TextFormatter.Change change) -> {
String newText = change.getControlNewText();
if (newText.length() == 1) {
System.out.println("Accepted");
} else if (newText.length() > 1) {
System.out.println("NOT Accepted");
return null;
}
return change;
}));
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;
import java.util.stream.IntStream;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
final StringBuilder sb = new StringBuilder();
IntStream.range(1, 100).forEach(i -> sb.append("Line " + i + "\n"));
TextArea ta = new TextArea();
ta.setText(sb.toString());
//how to I get line 30 at top of the visible textarea
double someValue = 0;
ta.setScrollTop(someValue);
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(ta, 300, 300));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
How do I get line 30 at top of the visible textarea?
I think 'someValue' should be relative to the total height which can be scrolled
But what is the total height that can be scrolled
This is the result I want to achieve:
This is a bit tricky. We could just determine each line height and call ta.setScrollTop((line - 1) * lineHeight);, but we do not know what line spacing TextArea uses.
But I found that TextAreaSkin contains public methods for determining bounds for any selected character, we just need to know its index.
#Override
public void start(Stage primaryStage) throws Exception{
final StringBuilder sb = new StringBuilder();
IntStream.range(1, 100).forEach(i -> sb.append("Line " + i + "\n"));
TextArea ta = new TextArea();
ta.setText(sb.toString());
// TextArea did not setup its skin yet, so we can't use it right now.
// We just append our task to the user tasks queue.
Platform.runLater(() -> {
// Define desired line
final int line = 30;
// Index of the first character in line that we look for.
int index = 0;
// for this example following line will work:
// int index = ta.getText().indexOf("Line " + line);
// for lines that do not contain its index we rely on "\n" count
int linesEncountered = 0;
boolean lineFound = false;
for (int i = 0; i < ta.getText().length(); i++) {
// count characters on our way to our desired line
index++;
if(ta.getText().charAt(i) == '\n') {
// next line char encountered
linesEncountered++;
if(linesEncountered == line-1) {
// next line is what we're looking for, stop now
lineFound = true;
break;
}
}
}
// scroll only if line found
if(lineFound) {
// Get bounds of the first character in the line using internal API (see comment below the code)
Rectangle2D lineBounds = ((com.sun.javafx.scene.control.skin.TextAreaSkin) ta.getSkin()).getCharacterBounds(index);
// Scroll to the top-Y of our line
ta.setScrollTop(lineBounds.getMinY());
}
});
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(ta, 300, 300));
primaryStage.show();
}
This solution works on Java 8, on 9+ TextAreaSkin was moved to the public package, so everything you need to make it work is to replace com.sun.javafx.scene.control.skin.TextAreaSkin with javafx.scene.control.skin.TextAreaSkin
First of all it's my first app, I'm trying to code a calculator. When pressing an operator, if there is old one, calculate it and send the result to continue with it to the new process. The calculation process don't go to the second step, anyone can help to make this code work properly?
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class main extends Application {
String num1 ="";
String num2 ="";
String op ;
double result= 0;
boolean oldop =false ;
// the GUI component
public void start(Stage stage) throws Exception {
Button one = new Button("1");
Button two = new Button("2");
Button pls = new Button("+");
Button eql = new Button("=");
Button ac = new Button("AC");
Label lbl = new Label("empty");
FlowPane pane = new FlowPane();
pane.setHgap(10);
pane.getChildren().addAll(one,two,pls,eql,ac,lbl);
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.show();
// The Actions on buttons
one.setOnAction(e ->
{
if(!oldop){
num1+='1';
lbl.setText(num1);}
else {
num2+='1';
lbl.setText(num2);}});
two.setOnAction(e ->
{
if(!oldop){
num1+='2';
lbl.setText(num1);}
else {
num2+='2';
lbl.setText(num2);}});
pls.setOnAction(e -> {
if(!oldop){
oldop = true;
op="+";
lbl.setText(op);}
else {
result=calc(num1 , num2 ,op);
num1=String.valueOf(result);
num2="";
op="+";
lbl.setText(num1+op);
oldop = true;}});
eql.setOnAction(e ->{
if(oldop){
result=calc(num1 , num2 , op);
lbl.setText(String.valueOf(result));
oldop=false;
num2="";}
else
return;});
ac.setOnAction(e -> {
num1="";
num2="";
result=0;
oldop=false;});
}
// The calculation method
public int calc (String n1 , String n2 , String op){
switch (op) {
case "+" :
return Integer.parseInt(n1) + Integer.parseInt(n2) ;
case "-" :
return Integer.parseInt(n1) - Integer.parseInt(n2) ;
case "*" :
return Integer.parseInt(n1) * Integer.parseInt(n2) ;
case "/" :
return Integer.parseInt(n1) / Integer.parseInt(n2) ;
default :
return 0;
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
The problem seems to be that you cannot use the result of an previous operation in the second step, because you use String.valueOf which gives e.g. 3.0 for the int 3 (result of 1+2). This string cannot be used again in calc as it cannot be parsed back to an int with `Integer.parseInt.
I would suggest to work with int and only convert them to strings for the labels.
An ulgy workaround would be to add the following lines at the beginning of calc:
n1=n1.split("\\.")[0];
n2=n2.split("\\.")[0];
I´m using a FXML to set my form, but I need to set the limit of characters in textfields. How can I made this ?
You can't directly set a limit to number of characters. But you can add a listener to lengthProperty() of the textfield
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TextFieldLimit extends Application {
private static final int LIMIT = 10;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(final Stage primaryStage) {
final TextField textField = new TextField();
textField.lengthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
if (newValue.intValue() > oldValue.intValue()) {
// Check if the new character is greater than LIMIT
if (textField.getText().length() >= LIMIT) {
// if it's 11th character then just setText to previous
// one
textField.setText(textField.getText().substring(0, LIMIT));
}
}
}
});
VBox vbox = new VBox(20);
vbox.getChildren().add(textField);
Scene scene = new Scene(vbox, 400, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
}
One more elegance solution
Pattern pattern = Pattern.compile(".{0,25}");
TextFormatter formatter = new TextFormatter((UnaryOperator<TextFormatter.Change>) change -> {
return pattern.matcher(change.getControlNewText()).matches() ? change : null;
});
textField.setTextFormatter(formatter);
where 0 and 25 - min and max amount of chars. + ability to set a pattern of input text
Here is my solution to limit the length of a textfield.
I would not recommend solutions which use a listener (on text property or on length property), they do not behave correctly in all situations (for what I have seen).
I create an HTML input text with a max length, and compare it to my textfield in JavaFX. I had the same behavior with paste operations (Ctrl + V), cancel operations (Ctrl + Z) in both cases. The goal here is to check if the text is valid BEFORE modifying the textfield.
We could use a similar approach for a numeric text field.
import java.util.Objects;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.control.TextField;
public class LimitedTextField extends TextField {
private final IntegerProperty maxLength;
public LimitedTextField() {
super();
this.maxLength = new SimpleIntegerProperty(-1);
}
public IntegerProperty maxLengthProperty() {
return this.maxLength;
}
public final Integer getMaxLength() {
return this.maxLength.getValue();
}
public final void setMaxLength(Integer maxLength) {
Objects.requireNonNull(maxLength, "Max length cannot be null, -1 for no limit");
this.maxLength.setValue(maxLength);
}
#Override
public void replaceText(int start, int end, String insertedText) {
if (this.getMaxLength() <= 0) {
// Default behavior, in case of no max length
super.replaceText(start, end, insertedText);
}
else {
// Get the text in the textfield, before the user enters something
String currentText = this.getText() == null ? "" : this.getText();
// Compute the text that should normally be in the textfield now
String finalText = currentText.substring(0, start) + insertedText + currentText.substring(end);
// If the max length is not excedeed
int numberOfexceedingCharacters = finalText.length() - this.getMaxLength();
if (numberOfexceedingCharacters <= 0) {
// Normal behavior
super.replaceText(start, end, insertedText);
}
else {
// Otherwise, cut the the text that was going to be inserted
String cutInsertedText = insertedText.substring(
0,
insertedText.length() - numberOfexceedingCharacters
);
// And replace this text
super.replaceText(start, end, cutInsertedText);
}
}
}
}
Tested with JavaFX 8 and Java 8u45
This is a very simple solution that seems to work for me.
textfield.setOnKeyTyped(event ->{
int maxCharacters = 5;
if(tfInput.getText().length() > maxCharacters) event.consume();
});
I use a simple call to ChangeListener, where I test the condition to perform stops.
textFild.addListener((observable, oldValue, newValue) -> {
if (newValue.length() == MAX_SIZE) {
textField.setText(oldValue);
}
});
This is a better way to do the job on a generic text field:
public static void addTextLimiter(final TextField tf, final int maxLength) {
tf.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(final ObservableValue<? extends String> ov, final String oldValue, final String newValue) {
if (tf.getText().length() > maxLength) {
String s = tf.getText().substring(0, maxLength);
tf.setText(s);
}
}
});
}
Works perfectly, except for that Undo bug.
the following 1-liner will exactly do it, wheras 5 is the limit of the TextField tf:
tf.setTextFormatter(new TextFormatter<>(c -> c.getControlNewText().matches(".{0,5}") ? c : null));
This is a solution that works well:
#FXML
void limitTextFields(KeyEvent event) {
int maxLength = 5;
TextField tf = (TextField) event.getSource();
if (tf.getText().length() > maxLength) {
tf.deletePreviousChar();
}
}