In a UI of mine, I have a PasswordField like so (urm the one at the bottom!):
I want a user to be able to check the checkbox you see in the picture and have all "secret" password characters displayed. Not much different from the option we get from many modern password-asking UI:s floating around. However, I cannot find anything in the JavaFX API that let me do that?
If my worries hold true, then I would like to use a TextField that display the last key pressed for only half a second or until next key is pressed, and then he shall mask all previous user input. This would produce a cool animation effect that one can see sometimes in modern UI:s. However, is there a way for me to get hold of the OS dependent (I think it is OS dependent??) password echo character I should use?
If it is not possible to get that OS dependent character, then I'd be glad to use the character you see on the picture (JavaFX on a Windows 8 machine). What is the UTF-8 code point for this stranger?
> However, I cannot find anything in the JavaFX API that let me do that?
The PasswordField component does not display masked text by default. However you can use PasswordField with TextField and toggle masked/unmasked text using these components respectively. Where the unmasked text is shown by TextField, as in example demo below.
> I would like to use a TextField that display the last key pressed for only half a second or until next key is pressed, and then he shall mask all previous user input.
Since PasswordField, itself is a extended version of TextField. You can always build your own custom password textbox with properties you mentioned.
> is there a way for me to get hold of the OS dependent (I think it is OS dependent??) password echo character I should use?
Frankly did not grab what you are saying here. You can track text changes by adding change listener to PasswordField.textPrperty() and do animations, timers etc. You can override the default bullet mask by extending PasswordFieldSkin and using it through CSS -fx-skin. See the definition of bullet in its source here:
public class PasswordFieldSkin extends TextFieldSkin {
public static final char BULLET = '\u2022';
public PasswordFieldSkin(PasswordField passwordField) {
super(passwordField, new PasswordFieldBehavior(passwordField));
}
#Override protected String maskText(String txt) {
TextField textField = getSkinnable();
int n = textField.getLength();
StringBuilder passwordBuilder = new StringBuilder(n);
for (int i=0; i<n; i++) {
passwordBuilder.append(BULLET);
}
return passwordBuilder.toString();
}
}
Finally, Here is kick off demo app of showing password characters using bindings:
#Override
public void start(Stage primaryStage) {
// text field to show password as unmasked
final TextField textField = new TextField();
// Set initial state
textField.setManaged(false);
textField.setVisible(false);
// Actual password field
final PasswordField passwordField = new PasswordField();
CheckBox checkBox = new CheckBox("Show/Hide password");
// Bind properties. Toggle textField and passwordField
// visibility and managability properties mutually when checkbox's state is changed.
// Because we want to display only one component (textField or passwordField)
// on the scene at a time.
textField.managedProperty().bind(checkBox.selectedProperty());
textField.visibleProperty().bind(checkBox.selectedProperty());
passwordField.managedProperty().bind(checkBox.selectedProperty().not());
passwordField.visibleProperty().bind(checkBox.selectedProperty().not());
// Bind the textField and passwordField text values bidirectionally.
textField.textProperty().bindBidirectional(passwordField.textProperty());
VBox root = new VBox(10);
root.getChildren().addAll(passwordField, textField, checkBox);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Demo");
primaryStage.setScene(scene);
primaryStage.show();
}
You need create three elements:
TextField : the password visible field
PasswodField : the password not visible field
CheckBox : the toggle visibility field
You place the passwords fields in the same position(x, y):
<PasswordField fx:id="pass_hidden" layoutX="X" layoutY="Y" />
<TextField fx:id="pass_text" layoutX="X" layoutY="Y"/>
<CheckBox fx:id="pass_toggle" onAction="#togglevisiblePassword" .... />
Note: Replaces the value of X and Y.
Add in your controller:
#FXML
private TextField pass_text;
#FXML
private CheckBox pass_toggle;
#FXML
private Button btn_start_stop;
/**
* Controls the visibility of the Password field
* #param event
*/
#FXML
public void togglevisiblePassword(ActionEvent event) {
if (pass_toggle.isSelected()) {
pass_text.setText(pass_hidden.getText());
pass_text.setVisible(true);
pass_hidden.setVisible(false);
return;
}
pass_hidden.setText(pass_text.getText());
pass_hidden.setVisible(true);
pass_text.setVisible(false);
}
//Run
#Override
public void initialize(URL location, ResourceBundle resources) {
this.togglevisiblePassword(null);
}
If you want to know the value of the password you can create a method that returns it:
private String passwordValue() {
return pass_toggle.isSelected()?
pass_text.getText(): pass_hidden.getText();
}
I know this is older, but i was searching for answer and this is my solution:
#FXML
private JFXButton showpassword;
private String password;
showpassword.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
password = passwordField.getText();
passwordField.clear();
passwordField.setPromptText(password);
});
showpassword.addEventFilter(MouseEvent.MOUSE_RELEASED, e -> {
passwordField.setText(password);
passwordField.setPromptText("Password");
});
Using button with graphic like "WIN10 Eye - unmask password"
You could use a custom Tooltip to show the password, and use the Checkbox to show / hide the Tooltip.
The code for this demo can be found here.
void viewpass(ActionEvent event) {
if (checkpass.isSelected()){
pass.setPromptText(pass.getText());
pass.setText("");
pass.setDisable(true);
}else {
pass .setText(pass.getPromptText());
pass.setPromptText("");
pass.setDisable(false);
}
}
You can also do it using textfield and password field with radio button As follows.
import javafx.fxml.Initializable;
import com.jfoenix.controls.*;
import com.jfoenix.controls.JFXPasswordField;
import com.jfoenix.controls.JFXRadioButton;
import javafx.fxml.FXML;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable{
#FXML
private JFXPasswordField PasswordField;
#FXML
private JFXRadioButton passVisible;
#FXML
private JFXTextField textField1;
#Override
public void initialize(URL location, ResourceBundle resources)
{
textField1.textProperty().bind(PasswordField.textProperty());
textField1.visibleProperty().bind(passVisible.selectedProperty());
PasswordField.visibleProperty().bind(passVisible.selectedProperty().not());
}
}
well, the password field has one property that can be set the text in bullets.. this method maskText(String txt) stays on skin.. you can replace this with a new Skin.. when you type the method maskText test if you can raplace in bullets.. use one boolean to inform.. you can reuse this code from another event. it's an example. Regards
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(50));
PasswordField passwordField = new PasswordField();
passwordField.setSkin(new VisiblePasswordFieldSkin(passwordField));
root.getChildren().add(passwordField);
stage.setScene(new Scene(root, 400, 400));
stage.show();
}
}
class VisiblePasswordFieldSkin extends TextFieldSkin {
private final Button actionButton = new Button("View");
private final SVGPath actionIcon = new SVGPath();
private boolean mask = true;
public VisiblePasswordFieldSkin(PasswordField textField) {
super(textField);
actionButton.setId("actionButton");
actionButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
actionButton.setPrefSize(30,30);
actionButton.setFocusTraversable(false);
actionButton.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, new Insets(0))));
getChildren().add(actionButton);
actionButton.setCursor(Cursor.HAND);
actionButton.toFront();
actionIcon.setContent(Icons.VIEWER.getContent());
actionButton.setGraphic(actionIcon);
actionButton.setVisible(false);
actionButton.setOnMouseClicked(event -> {
if(mask) {
actionIcon.setContent(Icons.VIEWER_OFF.getContent());
mask = false;
} else {
actionIcon.setContent(Icons.VIEWER.getContent());
mask = true;
}
textField.setText(textField.getText());
textField.end();
});
textField.textProperty().addListener((observable, oldValue, newValue) -> actionButton.setVisible(!newValue.isEmpty()));
}
#Override
protected void layoutChildren(double x, double y, double w, double h) {
super.layoutChildren(x, y, w, h);
layoutInArea(actionButton, x, y, w, h,0, HPos.RIGHT, VPos.CENTER);
}
#Override
protected String maskText(String txt) {
if (getSkinnable() instanceof PasswordField && mask) {
int n = txt.length();
StringBuilder passwordBuilder = new StringBuilder(n);
for (int i = 0; i < n; i++) {
passwordBuilder.append(BULLET);
}
return passwordBuilder.toString();
} else {
return txt;
}
}
}
enum Icons {
VIEWER_OFF("M12 6c3.79 0 7.17 2.13 8.82 5.5-.59 1.22-1.42 2.27-2." +
"41 3.12l1.41 1.41c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 " +
"0-2.49.2-3.64.57l1.65 1.65C10.66 6.09 11.32 6 12 6zm-1.07 1.14L13 9.21c.57.25 1.03.71 " +
"1.28 1.28l2.07 2.07c.08-.34.14-.7.14-1.07C16.5 9.01 14.48 7 12 7c-.37 0-.72.05-1.07." +
"14zM2.01 3.87l2.68 2.68C3.06 7.83 1.77 9.53 1 11.5 2.73 15.89 7 19 12 19c1.52 0 2.98-.29 " +
"4.32-.82l3.42 3.42 1.41-1.41L3.42 2.45 2.01 3.87zm7.5 7.5l2.61 2.61c-.04.01-.08.02-.12.02-1.38 " +
"0-2.5-1.12-2.5-2.5 0-.05.01-.08.01-.13zm-3.4-3.4l1.75 1.75c-.23.55-.36 1.15-.36 1.78 0 2.48 2.02 " +
"4.5 4.5 4.5.63 0 1.23-.13 1.77-.36l.98.98c-.88.24-1.8.38-2.75.38-3.79 0-7.17-2.13-8.82-5.5.7-1.43 1.72-2.61 2.93-3.53z"),
VIEWER("M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7." +
"5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z");
private String content;
Icons(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
View in GitHub
Related
I'm using some TextFields in JavaFX, and I want to limit their area of shown text. I already limit their maximum number of characters, but in some cases, the maximum char number typed is larger than the Textfield's width. As you see, the text is overlapping a custom Erase-text button I added.
I want to know if I can move the text's right margin a little bit to the left (without changing the TextField's properties - size, coordinates), so the button and the text won't overlap anymore.
I already tried margins, padding, but they don't do what I need.
How do I limit my TextField's maximum length (from stackoverflow):
public class LimitedJFXTextField extends JFXTextField {
private final IntegerProperty maxLength;
public LimitedJFXTextField() {
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);
}
}
}
}
Here is a solution using ControlsFX.
import java.io.IOException;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import org.controlsfx.control.textfield.CustomTextField;
/**
* JavaFX App
*/
public class App extends Application
{
private static Scene scene;
#Override
public void start(Stage stage) throws IOException
{
CustomTextField customTextField = new CustomTextField();
customTextField.setText("Hello World!");
Label labelX = new Label("x");
labelX.setTextFill(Color.RED);
customTextField.setRight(labelX);
customTextField.setMaxWidth(200);
scene = new Scene(new StackPane(customTextField), 500, 500);//loadFXML("primary"), 640, 480);
stage.setScene(scene);
stage.show();
}
}
I have never used JFXTextField, so take this answer with a grain of salt.
However, since JFXTextField extends TextField, you should be able to change properties of TextField. One of these is called padding:
So using just the "normal" TextField, you can use this:
public class ExampleApp extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
var textField = new TextField();
textField.setPadding(new Insets(0, 50, 0, 0)); // This adds 50px right padding
textField.setText("Hello World!");
var label = new Label("x");
var pane = new StackPane(textField, label);
StackPane.setAlignment(label, Pos.CENTER_RIGHT);
var scene = new Scene(pane, 500, 500);
stage.setScene(scene);
stage.show();
}
}
Or if you prefer FXML, use this:
<TextField>
<padding>
<Insets right="50"/>
</padding>
</TextField>
The StackPane as well as the Label are just for the sake of demonstrating some overlaying item, same as in Sedricks answer. The important part which actually restricts the text inside of the textfield is the padding.
I want to display a tooltip inside a TextField when user copies data from it to notify him that it happened and so there is my code.
public class TestController {
#FXML private TextField textField;
private final Clipboard clipboard;
private ContextMenu menu;
private MenuItem menuCopy;
#FXML protected void initialize() {
clipboard = Clipboard.getSystemClipboard();
menu = new ContextMenu();
menuCopy = new MenuItem("Copy");
menuCopy.setOnAction(this::copy);
textField.setContextMenu(menu);
}
private void copy(ActionEvent event) {
final ClipboardContent content = new ClipboardContent();
if(((TextField) menu.getUserData()).getSelectedText().length() == 0)
content.putString(((TextField) menu.getUserData()).getText());
else
content.putString(((TextField) menu.getUserData()).getSelectedText());
clipboard.setContent(content);
Point2D p = ((TextField) menu.getUserData()).localToScreen(0,0);
Tooltip test = new Tooltip("Copied");
test.setShowDuration(new Duration(2000));
test.show(((TextField) menu.getUserData()), p.getX(), p.getY());
}
}
But the setShowDuration doesn't seem to work. It will be displayed forever.
When you install a Tooltip via Tooltip#install(Node,Tooltip)1 there are three event handlers added to the given Node, all related to mouse events. It is these event handlers that implement the showDelay and showDuration functionality. This is done internally by using Timelines. However, when you manually show a Tooltip via one of its show methods you completely bypass this behavior2. In other words, your Tooltip has the same functionality as any other PopupControl.
If you want to manually display a Tooltip and have it disappear after a specified amount of time you'll have to implement that yourself. Here's a proof-of-concept:
import javafx.animation.PauseTransition;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.util.Duration;
public class NotifyingTextField extends TextField {
private final Tooltip tooltip = new Tooltip("Copied contents to clipboard");
private final PauseTransition hideAnimation = new PauseTransition();
{
hideAnimation.durationProperty().bind(tooltip.showDurationProperty());
hideAnimation.setOnFinished(e -> tooltip.hide());
tooltip.setShowDuration(Duration.seconds(2.0));
}
#Override
public void copy() {
var selectedText = getSelectedText();
if (!selectedText.isEmpty()) {
super.copy();
var point = localToScreen(0, 0);
tooltip.show(this, point.getX(), point.getY());
hideAnimation.playFromStart();
}
}
}
Of course, you may be able to find a third-party library that offers a ready-made solution.
1. Even setting properties such as Control#tooltip or Tab#tooltip will delegate to #install(Node,Tooltip).
2. This is true even if the Tooltip is installed. Manually calling show bypasses all the functionality added by the mouse event handlers.
I think it's the way you are implementing it, I tried it and it works for me
PasswordField pf = new PasswordField();
Tooltip tooltip = new Tooltip("Your password must be\nat least 8 characters in length");
tooltip.setShowDuration(new Duration(2000));
pf.setTooltip(tooltip);
If this doesn't work with you try to help us to help you by providing snippet of your code that I can test without adding anything to it
I'm trying to write a program similar to the contacts app on an android phone using javafx. In the fxml file I have a VBox which contains three textfields, the first two fields are for first name and last name, and the third one is for a number.
Now what I want the program to do is when the textfield for number is filled with even a single character, another textfield to be automatically added to the VBox. (for another number).
and I want the same thing to happen for the next field. and any other field that follows, so it has a recursive form.
Now the only method I know that might accomplish this, is using a listener, but I have no idea how to create such a recursive listener. and The listener to the old field would have to be removed once it has accomplished its job, so it wouldn't continuously create new fields when typing something in the old field. but you can't remove a listener while you're inside it.
Is there a way to do this?
A lambda expression can't refer to itself, but an anonymous inner class can, so if you implement your listener as an anonymous inner class, you can achieve what you're looking to do:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class DynamicTextFields extends Application {
private TextField lastTextField ;
#Override
public void start(Stage primaryStage) {
lastTextField = new TextField();
VBox vbox = new VBox(5, lastTextField);
ChangeListener<String> textFieldListener = new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> obs, String oldValue, String newValue) {
lastTextField.textProperty().removeListener(this);
lastTextField = new TextField();
lastTextField.textProperty().addListener(this);
vbox.getChildren().add(lastTextField);
}
};
lastTextField.textProperty().addListener(textFieldListener);
Scene scene = new Scene(new ScrollPane(vbox), 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Register a ChangeListener to the text property of the TextFields that adds/removes the TextField based on the index every time the text changes from empty to non-empty or the other way round.
public void addTextField(Pane parent) {
TextField textField = new TextField();
textField.textProperty().addListener((o, oldValue, newValue) -> {
boolean wasEmpty = oldValue.isEmpty();
boolean isEmpty = newValue.isEmpty();
if (wasEmpty != isEmpty) {
if (wasEmpty) {
// append textfield if last becomes non-empty
if (parent.getChildren().get(parent.getChildren().size() - 1) == textField) {
addTextField(parent);
}
} else {
int tfIndex = parent.getChildren().indexOf(textField);
if (tfIndex < parent.getChildren().size() - 1) {
// remove textfield if this is not the last one
parent.getChildren().remove(tfIndex);
parent.getChildren().get(tfIndex).requestFocus();
}
}
}
});
parent.getChildren().add(textField);
}
VBox root = new VBox();
addTextField(root);
Set 3 slidebar: value from 0 to 255, 3 slidebar set as red, green and blue. I try to bind 3 slidebars to color and it's faild. So how can I solve this? When I change the slidebar, the color can change? How can I program this function? Thanks.
code:
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private Slider redbar;
#FXML
private Slider greenbar;
#FXML
private Slider bluebar;
#FXML
private void handleButtonAction(ActionEvent event) {
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
Integer redValue=(int)redbar.getValue();
Integer greenValue=(int)greenbar.getValue();
Integer blueValue=(int)bluebar.getValue();
Color col=Color.rgb(redValue,greenValue,blueValue);//I only can set as like this, then pass color to laber
// I wanner bind col with laber.backgroundProperty() here
}
Using getValue() will give you the value at current time. If you want to react to changes you will need to attach a listener to the corresponding property:
ChangeListener myListener = new ChangeListener() {
#Override
public void changed(...) {
// Collect current values and set rgb color
}
...
redValue.valueProperty().addListener(myListener);
greenValue.valueProperty().addListener(myListener);
blueValue.valueProperty().addListener(myListener);
In your comments you wrote you want to bind the value. What you're doing in your code is not binding at all however. You simply retrieve the value just after the fxml has been loaded before the scene is even rendered.
Instead you need to create a binding depending on the slider values:
#Override
public void initialize(URL url, ResourceBundle rb) {
label.backgroundProperty().bind(Bindings.createObjectBinding(() -> new Background(new BackgroundFill(Color.rgb((int) redbar.getValue(),
(int) greenbar.getValue(),
(int) bluebar.getValue()),
CornerRadii.EMPTY,
Insets.EMPTY)),
redbar.valueProperty(),
greenbar.valueProperty(),
bluebar.valueProperty()));
}
I want to use a JavaFX TextArea as though it were exactly like a multi-line TextField. In other words, when I press [Tab] I want to cycle to the next control on the form and when I press [Enter] I want the Key.Event to go to the defaultButton control (rather than be consumed by the TextArea).
The default behavior for TextArea is that [Tab] gets inserted into the TextArea and [Enter] inserts a new-line character.
I know that I need to use EventFilters to get the behavior that I want, but I'm getting it all wrong. I don't want the TextArea to consume these events ... I just want it to let them "go right on by".
The solution here displays two text areas and a default button.
When the user presses the tab key, the focus moves to the next control down.
When the user presses the enter key, the default button is fired.
To achieve this behavior:
The enter key press for each text area is caught in an event filter, copied and targeted to the text area's parent node (which contains the default OK button). This causes the default OK button to be fired when enter is pressed anywhere on the form. The original enter key press is consumed so that it does not cause a new line to be added to the text area's text.
The tab key press for each text area is caught in a filter and the parent's focus traversable list is processed to find the next focusable control and focus is requested for that control. The original tab key press is consumed so that it does not cause new tab spacing to be added to the text area's text.
The code makes use of features implemented in Java 8, so Java 8 is required to execute it.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.value.*;
import javafx.collections.ObservableList;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.control.*;
import static javafx.scene.input.KeyCode.ENTER;
import static javafx.scene.input.KeyCode.TAB;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.*;
public class TextAreaTabAndEnterHandler extends Application {
final Label status = new Label();
public static void main(String[] args) { launch(args); }
#Override public void start(final Stage stage) {
final TextArea textArea1 = new TabAndEnterIgnoringTextArea();
final TextArea textArea2 = new TabAndEnterIgnoringTextArea();
final Button defaultButton = new Button("OK");
defaultButton.setDefaultButton(true);
defaultButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
status.setText("Default Button Pressed");
}
});
textArea1.textProperty().addListener(new ClearStatusListener());
textArea2.textProperty().addListener(new ClearStatusListener());
VBox layout = new VBox(10);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10px;");
layout.getChildren().setAll(
textArea1,
textArea2,
defaultButton,
status
);
stage.setScene(
new Scene(layout)
);
stage.show();
}
class ClearStatusListener implements ChangeListener<String> {
#Override public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
status.setText("");
}
}
class TabAndEnterIgnoringTextArea extends TextArea {
final TextArea myTextArea = this;
TabAndEnterIgnoringTextArea() {
addEventFilter(KeyEvent.KEY_PRESSED, new TabAndEnterHandler());
}
class TabAndEnterHandler implements EventHandler<KeyEvent> {
private KeyEvent recodedEvent;
#Override public void handle(KeyEvent event) {
if (recodedEvent != null) {
recodedEvent = null;
return;
}
Parent parent = myTextArea.getParent();
if (parent != null) {
switch (event.getCode()) {
case ENTER:
if (event.isControlDown()) {
recodedEvent = recodeWithoutControlDown(event);
myTextArea.fireEvent(recodedEvent);
} else {
Event parentEvent = event.copyFor(parent, parent);
myTextArea.getParent().fireEvent(parentEvent);
}
event.consume();
break;
case TAB:
if (event.isControlDown()) {
recodedEvent = recodeWithoutControlDown(event);
myTextArea.fireEvent(recodedEvent);
} else {
ObservableList<Node> children = parent.getChildrenUnmodifiable();
int idx = children.indexOf(myTextArea);
if (idx >= 0) {
for (int i = idx + 1; i < children.size(); i++) {
if (children.get(i).isFocusTraversable()) {
children.get(i).requestFocus();
break;
}
}
for (int i = 0; i < idx; i++) {
if (children.get(i).isFocusTraversable()) {
children.get(i).requestFocus();
break;
}
}
}
}
event.consume();
break;
}
}
}
private KeyEvent recodeWithoutControlDown(KeyEvent event) {
return new KeyEvent(
event.getEventType(),
event.getCharacter(),
event.getText(),
event.getCode(),
event.isShiftDown(),
false,
event.isAltDown(),
event.isMetaDown()
);
}
}
}
}
An alternate solution would be to implement your own customized skin for TextArea which includes new key handling behavior. I believe that such a process would be more complicated than the solution presented here.
Update
One thing I didn't really like about my original solution to this problem was that once the Tab or Enter key was consumed, there was no way to trigger their default processing. So I updated the solution such that if the user holds the control key down when pressing Tab or Enter, the default Tab or Enter operation will be performed. This updated logic allows the user to insert a new line or tab space into the text area by pressing CTRL+Enter or CTRL+Tab.