I am currently trying to build an application which behaves similar to a command shell. I want to display a path that I give it (or a '>' character at the very least) before the user's input text in a javaFX text field. like this:
I have it so that the text field will clear when the user submits the text. After a submission it sets the text of the field to be my path to achieve a similar effect, but the user can still delete this path while inputting text.
How can I make it so that my path text appears in the field but the user cannot delete it?
I've tried this but it only updates the caret position after submission:
textField.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
textField.positionCaret(textField.getLength());
}
});
You can use a TextFormatter to filter out invalid operations on the text field. A TextFormatter has a filter which filters changes to the text field; you can veto any changes by having the filter return null. The simplest implementation for what you describe would just filter out any changes where the caret position or the anchor for the text field were before the end of the fixed text:
UnaryOperator<TextFormatter.Change> filter = c -> {
if (c.getCaretPosition() < prefix.length() || c.getAnchor() < prefix.length()) {
return null ;
} else {
return c ;
}
};
textField.setTextFormatter(new TextFormatter<String>(filter));
You can experiment with other logic here (for example if you want the user to be able to select the fixed text).
Here is a SSCCE:
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TextFieldFixedPrefix extends Application {
private TextField createFixedPrefixTextField(String prefix) {
TextField textField = new TextField(prefix);
UnaryOperator<TextFormatter.Change> filter = c -> {
if (c.getCaretPosition() < prefix.length() || c.getAnchor() < prefix.length()) {
return null ;
} else {
return c ;
}
};
textField.setTextFormatter(new TextFormatter<String>(filter));
textField.positionCaret(prefix.length());
return textField ;
}
#Override
public void start(Stage primaryStage) {
TextField textField = createFixedPrefixTextField("/home/currentUser $ ");
StackPane root = new StackPane(textField);
Scene scene = new Scene(root, 300,40);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Related
How i can do textField accepts only letter "N or E" in Java? doesn't accept number and another characters.
textField.textProperty().addListener((observable, oldValue, newValue) -> {
if (newValue.length() > 1) textField.setText(oldValue);
if (newValue.matches("[^\\d]")) return;
textField.setText(newValue.replaceAll("\\d*", ""));
});
i tryed this, this worked for maxValue. But i am need textField accept only "N" and "E" characters.
So how i can do this?
Use a TextFormatter. You can modify or veto the proposed change to the text. This version:
Only accepts changes where the text typed (or pasted) is "N" or "E" (either upper or lower case)
Makes the text upper case
Changes the proposed change so that it replaces existing text, instead of adding to it
Allows for deleting the current text
Your exact requirements may differ slightly. See the Javadocs for TextFormatter.Change for more details.
import java.util.function.UnaryOperator;
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 NorETextField extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField = new TextField();
UnaryOperator<TextFormatter.Change> filter = c -> {
if (c.getText().matches("[NnEe]")) {
c.setText(c.getText().toUpperCase());
c.setRange(0, textField.getText().length());
return c ;
} else if (c.getText().isEmpty()) {
return c ;
}
return null ;
};
textField.setTextFormatter(new TextFormatter<String>(filter));
BorderPane root = new BorderPane(textField);
Scene scene = new Scene(root, 400, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
You can try this. This accept only character N
Pattern pattern = Pattern.compile("N");
UnaryOperator<TextFormatter.Change> filter = c -> {
if (pattern.matcher(c.getControlNewText()).matches()) {
return c ;
} else {
return null ;
}
};
TextFormatter<String> formatter = new TextFormatter<>(filter);
textField.setTextFormatter(formatter);
Please help me solve the problem. There are two fxml files and their controllers:
sample.fxml, its controller ControllerMain (main window of the program)
find_win.fxml, its ControllerFind controller (modal window "text search")
In the modal window find_win.fxml there is a TextField into which the search text is entered, and the Find button, when clicked, ControllerFind must process the click and call the search method and highlight the search text in the TextArea element of the sample.fxml window.
<fx: include source = "sample.fxml" fx: id = "textAreaOne" />
and the inheritance of the ControllerMain by the ControllerFind controller does not help to achieve a solution - in the first case the entire window markup is included in the modal window completely, in the second case, the java.lang.reflect.InvocationTargetException is returned during an operation on the TextArea.
How to implement actions on elements of one window from another window?
I've found a solution elsewhere. Thanks to Comrade Antizam, who tried to help, but did not quite understand what I needed.
The solution described here is .
In short, it is necessary to create an instance of a controller-parent and a method that takes an instance of a controller-parent as a parameter in the controller-child. When a new window is opened from a controller-parent, get an instance of the controller-child and indicate to it through the created method "this".
Further, in the controller-child, it will be possible to access the elements of the parent controller.
controller-parent:
package sample;
public class ControllerMain {
private ControllerFind children; // controller-child
//main-window
#FXML
public TextArea textAreaOne;
#FXML
public MenuItem findMenuItem;
public void findAction(ActionEvent actionEvent) {
try {
Stage stageFind = new Stage();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("FXML/find_win.fxml"));
Parent root = loader.load();
stageFind.setTitle("Find");
stageFind.setMinHeight(200);
stageFind.setMinWidth(150);
stageFind.setResizable(false);
stageFind.setScene(new Scene(root));
stageFind.getIcons().add(new Image("image/search.png"));
stageFind.initModality(Modality.NONE);
stageFind.show();
children = loader.getController(); //getting controller of window find_win.fxml
children.setParent(this); //setting parent of the controller-child - this
} catch (IOException e) {
e.printStackTrace();
}
}
}
controller-child:
package sample.Controllers;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import sample.Classes.DialogManager;
import sample.ControllerMain;
public class ControllerFind {
//Window "Find"
#FXML public TextField searchTextField;
#FXML public Label findTextLabel;
#FXML public Button okTextFindButton;
#FXML public Button cancelTextFindButton;
private String text;
private ControllerMain controller;
public void setParent (ControllerMain controller){
this.controller = controller;
}
public ControllerFind getThis(){
return this;
}
public void initialize(){
System.out.println("psvm");
}
public void textFindOkButtonAction(ActionEvent actionEvent) {
text = (searchTextField.getText());
if (text.equals("")) {
DialogManager.showInfoDialog("Error!", "Enter text what you are looking for!");
} else {
if (controller.textAreaOne.getText() != null && !controller.textAreaOne.getText().isEmpty()) {
int index = controller.textAreaOne.getText().indexOf(text);
if (index == -1) {
DialogManager.showInfoDialog("Result", "There isn't text what you are looking for");
} else {
controller.textAreaOne.selectRange(index, index + text.length());
}
} else {
DialogManager.showInfoDialog("Error", "TextArea is empty!");
}
}
}
public void textFindCancelButtonAction(ActionEvent actionEvent) {
Node source = (Node) actionEvent.getSource();
Stage stage = (Stage) source.getScene().getWindow();
stage.close();
}
}
Vis-a-vis windows communication in this situation, it's optimal to use TextInputDialog something like this:
#Override
public void start(Stage primaryStage){
Button btn=new Button("Click");
VBox vbox=new VBox();
vbox.setAlignment(Pos.CENTER);
vbox.getChildren().addAll(btn);
Scene scene=new Scene(vbox, 200,200);
btn.setOnAction(e->
{
TextInputDialog dialog=new TextInputDialog();
Optional<String> result = dialog.showAndWait();
if (result.isPresent()){
System.out.println(result.get());
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
but text highlighting is not something that can be done easy.
You could do String search of TextArea text, compare it to result from another window then highlight it via
textArea.getSelectedRange(firstIndex, lastIndex);
firstIndex and lastIndex being indexes of textArea text for the word we are searching. Then having button on every click displaying next word occurancy inside text and highlighting it. But if you insist on highlighting every instance of word at the same time I would suggest using RichTextFX.
I am trying to make my first app an have hit a roadblock. The app is basically just a simple typing practice app where the user is given a string which they copy. I am trying to implement a count that counts the number of errors made by the user.
Currently I have code that uses a listener to observe the string the user is inputting to a text area. I have an error count that is added to every time the users string does not equal the provided string, the issue with this is I want to only add 1 to the count for every error made. For example, currently if you make a mistake and type 2 wrong characters the error count will go up to 3 as the code runs every time a key is pressed meaning it counts the 2 wrong chars and 1 backspace to remove them and I would like this to only add 1 to the error count.
The code I have currently is below:
public class MainController implements Initializable {
#FXML
private BorderPane errorStatus;
#FXML
private TextArea inputTextArea;
#FXML
public TextArea generatedText;
private String userText;
private String thePassage;
private Integer errorCount = 0;
private Boolean errorCheck = false;
timer time = new timer();
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
thePassage = PassageData.getPassage();
generatedText.setText(thePassage);
inputTextArea.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable,
String oldValue, String newValue) {
if (!inputTextArea.getText().isEmpty()) {
time.start();
}
userText = newValue;
if (userText.equals("") || userText.equals(null)) {
errorStatus.setStyle(
"-fx-background-color: radial-gradient(radius 100%, white, white); -fx-background-radius: 10; -fx-border-radius: 10");
} else if (thePassage.regionMatches(0, userText, 0, userText.length())) {
errorCheck = false;
errorStatus.setStyle(
"-fx-background-color: radial-gradient(radius 100%, green, white); -fx-background-radius: 10; -fx-border-radius: 10");
} else {
errorCheck = true;
errorStatus.setStyle(
"-fx-background-color: radial-gradient(radius 100%, red, white); -fx-background-radius: 10; -fx-border-radius: 10");
}
if (userText.equals(thePassage)) {
thePassage = PassageData.getPassage();
generatedText.setText(thePassage);
Platform.runLater(() -> {
inputTextArea.clear();
});
errorStatus.setStyle(
"-fx-background-color: radial-gradient(radius 100%, white, white); -fx-background-radius: 10; -fx-border-radius: 10");
time.pause();
}
if (errorCheck) {
errorCount++;
System.out.println("Error count = " + errorCount);
}
}
});
}
}
Repeating (across several questions) Don't use low-level listeners if the framework provides higher-level support!
In this context, the high level support for fine-grained changes of text input is a TextFormatter, in particular its filter property. Such a filter will be notified whenever the text changes in any way (including caret navigation) and even allows to modify the change - all before the textProperty is changed.
A raw snippet that will count errors against a given text - for context see the Zephyr's answer
UnaryOperator<TextFormatter.Change> filter = c -> {
if (c.isAdded()) {
// tbd: guard against off-range
int pos = c.getRangeStart();
if (!c.getText().equals(target.substring(pos, pos + 1))) {
errorCount.set(errorCount.get() +1);
}
}
return c;
};
textArea.setTextFormatter(new TextFormatter<>(filter));
You will want to setup a change listener for the TextArea, but then compare each character of your input String with each character of the entered text.
The MCVE below demonstrates this:
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
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) {
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
IntegerProperty errorCount = new SimpleIntegerProperty(0);
String target = "This is the sample text to be typed correctly.";
Label instructions = new Label(target);
TextArea textArea = new TextArea();
textArea.setWrapText(true);
HBox hbox = new HBox(5);
hbox.setAlignment(Pos.CENTER);
Label errorsLabel = new Label();
hbox.getChildren().addAll(new Label("Errors:"), errorsLabel);
// Bind the label to errorcount
errorsLabel.textProperty().bind(errorCount.asString());
// Listen for changes to the textArea text and check again target string
textArea.textProperty().addListener((observableValue, s, newValue) -> {
if (newValue != null) {
errorCount.set(getErrorCount(target, newValue));
}
});
root.getChildren().addAll(instructions, textArea, hbox);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private int getErrorCount(String target, String entered) {
int errors = 0;
// Compare each character in the strings
char[] targetChars = target.toCharArray();
char[] enteredChars = entered.toCharArray();
// Starting at the beginning of the entered text, check that each character, in order, matches the target String
for (int i = 0; i < enteredChars.length; i++) {
if (enteredChars[i] != targetChars[i]) {
errors++;
}
}
return errors;
}
}
So what is happening here is each time a character is typed (or deleted) in the TextArea, the getErrorCount() method compares the entered text to the target String. If any characters are incorrect, it will increase the error count.
This is a lot like the "Submit" button idea from the comments above, but is performed each time the text changes in the TextArea, without a need for the extra button.
My problem is as follows,
For the sake of this question I reproduced the problem in a new project.
Say I have this application with a combobox in it, there could be 1 or more items in there. And I would like it to be so that when the user clicks an item in the combobox that 'something' happens.
I produced the following code:
obsvList.add("item1");
cbTest.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Item clicked");
}
});
This works when the application starts and an item is selected for the first time. This also works when there are 2 or more items in the combobox (when the user clicks item 1, then item 2, then item 1 for example)
However my problem is that when there is only 1 item in the combobox, let's say "item1". And the user reopens the combobox and clicks "item1" again then it won't redo the action.
It will only print the line "Item Clicked" when a 'new' item is clicked.
I hope it made it clear what the problem i'm experiencing is, if not please ask for clarification and I will give so where needed.
Thanks in advance!
The functionality of a combo box is to present the user with a list of options from which to choose. When you are using a control which implies selection, you should really ensure that the UI is always consistent with the option that is selected. If you do this, then it makes no sense to "repeat an action" when the user "reselects" the same option (because the UI is already in the required state). One approach to this is to use binding or listeners on the combo box's value:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ComboBoxExample extends Application {
#Override
public void start(Stage primaryStage) {
ComboBox<Item> choices = new ComboBox<>();
for (int i = 1 ; i <=3 ; i++) {
choices.getItems().add(new Item("Choice "+i, "These are the details for choice "+i));
}
Label label = new Label();
choices.valueProperty().addListener((obs, oldItem, newItem) -> {
label.textProperty().unbind();
if (newItem == null) {
label.setText("");
} else {
label.textProperty().bind(newItem.detailsProperty());
}
});
BorderPane root = new BorderPane();
root.setCenter(label);
root.setTop(choices);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public class Item {
private final String name ;
private final StringProperty details = new SimpleStringProperty() ;
public Item(String name, String details) {
this.name = name ;
setDetails(details) ;
}
public String getName() {
return name ;
}
#Override
public String toString() {
return getName();
}
public final StringProperty detailsProperty() {
return this.details;
}
public final String getDetails() {
return this.detailsProperty().get();
}
public final void setDetails(final String details) {
this.detailsProperty().set(details);
}
}
public static void main(String[] args) {
launch(args);
}
}
In this case, there is never a need to repeat an action when the user "reselects" the same option, because the code always assures that the UI is consistent with what is selected anyway (there is necessarily nothing to do if the user selects the option that is already selected). By using bindings in the part of the UI showing the details (just a simple label in this case), we are assured that the UI stays up to date if the data changes externally. (Obviously in a real application, this may be far more complex, but the basic strategy is still exactly the same.)
On the other hand, functionality that requires an action to be repeated if the user selects the same functionality is better considered as presenting the user with a set of "actions". The appropriate controls for this are things like menus, toolbars with buttons, and MenuButtons.
An example of a set of repeatable actions is:
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MenuButtonExample extends Application {
#Override
public void start(Stage primaryStage) {
MenuButton menuButton = new MenuButton("Items");
Label label = new Label();
Item[] items = new Item[3];
for (int i = 1 ; i <=3 ; i++) {
items[i-1] = new Item("Item "+i);
}
for (Item item : items) {
MenuItem menuItem = new MenuItem(item.getName());
menuItem.setOnAction(e -> item.setTimesChosen(item.getTimesChosen() + 1));
menuButton.getItems().add(menuItem);
}
label.textProperty().bind(Bindings.createStringBinding(() ->
Stream.of(items)
.map(item -> String.format("%s chosen %d times", item.getName(), item.getTimesChosen()))
.collect(Collectors.joining("\n")),
Stream.of(items)
.map(Item::timesChosenProperty)
.collect(Collectors.toList()).toArray(new IntegerProperty[0])));
BorderPane root = new BorderPane();
root.setCenter(label);
root.setTop(menuButton);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Item {
private final String name ;
private final IntegerProperty timesChosen = new SimpleIntegerProperty();
public Item(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
#Override
public String toString() {
return getName();
}
public final IntegerProperty timesChosenProperty() {
return this.timesChosen;
}
public final int getTimesChosen() {
return this.timesChosenProperty().get();
}
public final void setTimesChosen(final int timesChosen) {
this.timesChosenProperty().set(timesChosen);
}
}
public static void main(String[] args) {
launch(args);
}
}
The idea is to set a listener on the ListView pane, that appears whenever you click on the ComboBox. The ListView instance is created once the ComboBox is first loaded in the JavaFX scene. Therefore, we add a listener on the ComboBox to check when it appears on the scene, and then through the "lookup" method we get the ListView and add a listener to it.
private EventHandler<MouseEvent> cboxMouseEventHandler;
private void initComboBox() {
ComboBox<String> comboBox = new ComboBox<String>();
comboBox.getItems().add("Item 1");
comboBox.getItems().add("Item 2");
comboBox.getItems().add("Item 3");
comboBox.sceneProperty().addListener((a,oldScene,newScene) -> {
if(newScene == null || cboxMouseEventHandler != null)
return;
ListView<?> listView = (ListView<?>) comboBox.lookup(".list-view");
if(listView != null) {
cboxMouseEventHandler = (e) -> {
Platform.runLater(()-> {
String selectedValue = (String) listView.getSelectionModel().getSelectedItem();
if(selectedValue.equals("Item 1"))
System.out.println("Item 1 clicked");
});
}; // cboxMouseEventHandler
listView.addEventFilter(MouseEvent.MOUSE_PRESSED, cboxMouseEventHandler);
} // if
});
} // initComboBox
I'd like to have a ComboBox with following options:
(combobox employment:)
- Education
- Automotive
- (...)
- OTHER <-- editable
If user selects "other", he could edit the item in the ComboBox but all the other options would be non-editable.
Is that possible or should I just display additional TextField when user selects "other"?
There is the option to make a ComboBox editable:
combobox.setEditable(true);
You can only make all entries editable with this function though.
Read more at: http://docs.oracle.com/javafx/2/ui_controls/combo-box.htm
As far as I know you can only add Strings to the ObservableList which contains the content of your Combobox. Therefore you can not add a Node (in this case a Textfield).
Same goes for the ChoiceBox, if you add a TextField there (which is technically possible) but you will only get the .toString displayed when you actually use it.
Therefore you are probably best of creating a seperate field.
Just as an idea: You can quickly make a popup window when the user clicks "Other", in which whatever other is entered. Then, when you close the window or click enter or whatever, this value is added to the ObservableList. Would make it look nicer I guess...
Use this example:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package comboboxeditable;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author reegan
*/
public class ComboBoxEditable extends Application {
Node sub;
#Override
public void start(Stage primaryStage) {
ComboBox mainCombo = new ComboBox(listofCombo());
Button save = new Button("Save");
sub = new ComboBox(listofCombo());
HBox root = new HBox(20);
root.getChildren().addAll(mainCombo, sub,save);
mainCombo.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
if (newValue == "Others") {
sub = new TextField();
} else {
sub = new ComboBox(listofCombo());
}
root.getChildren().remove(1);
root.getChildren().add(1, sub);
}
});
save.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println(mainCombo.getValue());
if(sub.getClass() == ComboBox.class) {
ComboBox sub1 = (ComboBox)sub;
System.out.println(sub1.getValue());
} else {
TextField field = (TextField)sub;
System.out.println(field.getText());
}
}
});
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public ObservableList listofCombo() {
ObservableList<String> list = FXCollections.observableArrayList();
for (int i = 0; i < 10; i++) {
list.add(String.valueOf("Hello" + i));
}
list.add("Others");
return list;
}
}