JavaFX ComboBox check input and change it - javafx

I am trying to build a Combobox in JavaFX that should work as followed.
The user should only type numbers in, but the typed numbers have to be formattet.
ex. 111-111-1111.
So if the user types in three numbers a - should be added automatically.
I figured out how to do that.
I blocked everything but numbers with a TexFormatter.
Now the main Part of the problem comes the part after this.
I added a keyevent.key_released place the caret at the end when a key is released.
But if the user is typing too fast it won't work.
Most of the time it works just fine like that, but when is the user every doing something only the way I expect?
I could not find another way to get the actual value of the combobox, because it seems to refresh after hitting enter or so.
Adding the - in the Textformatter resultet in the programm listening to itself and I wasn't able to place the caret at the end position.
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author
*/
public class tester extends Application{
ComboBox<String> combo = new ComboBox<String>();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
System.out.println("test");
HBox root = new HBox();
//
UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
#Override
public TextFormatter.Change apply(TextFormatter.Change t) {
System.out.println(t.getText());
if (t.isReplaced())
if(t.getText().matches("[^0-9]")) {
t.setText(t.getControlText().substring(t.getRangeStart(), t.getRangeEnd()));}
if (t.isAdded()) {
// Add in Formatter //
if (t.getControlText().length() == 2 || t.getControlText().length() == 6 && t.getText().matches("[0-9]")){
t.setText(t.getText() + "-");
combo.getEditor().end();
}
//
if (t.getText().matches("[^0-9]")) {
t.setText("");}
}
return t;
}
};
combo.setEditable(true);
combo.getEditor().setTextFormatter(new TextFormatter<>(filter));
combo.getEditor().addEventFilter(KeyEvent.KEY_RELEASED, e -> {
combo.getEditor().end();
});
root.getChildren().add(combo);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}

Related

How to bind multiple properties to one observable in JavaFX [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
Update 3: solution
I finally got it: just save a reference of MyRoot in a property:
MainApp
package my.group.javafxtestbinding;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MainApp extends Application {
private MyRoot root;
#Override
public void start(Stage primaryStage) {
root = new MyRoot();
HBox parent = root.myRoot;
Scene scene = new Scene(parent, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
Button gcButton = new Button("Collect Garbage");
gcButton.setOnMousePressed(event -> System.gc());
parent.getChildren().add(gcButton);
}
public static void main(String[] args) {
launch(args);
}
}
Update 3 solution end
Update 2:
So, I'm still having problems with losing my bindings and it's due to garbage collection; that I can reproduce. See the follwing Minimal Working Example.
The structure of the example is somewhat special, that is because I use the MVP framework from Adam Bien: afterburner.fx. And I wanted to simulate the same structure in my example.
I read a lot (i.e. here, here) and understand the basic problem but still can't really grasp it.
I'm somewhat new to JavaFX, hope somebody can give me a hint.
Reproduce:
When you click on an item in the table, the content of the label changes due to the binding between the selected model in the table (Select) and the label (Show). MyRoot manages both.
When you click on the button garbage collection is executed and the binding is gone.
MainApp
package my.group.javafxtestbinding;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MainApp extends Application {
#Override
public void start(Stage primaryStage) {
MyRoot root = new MyRoot();
HBox parent = root.myRoot;
Scene scene = new Scene(parent, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
Button gcButton = new Button("Collect Garbage");
gcButton.setOnMousePressed(event -> System.gc());
parent.getChildren().add(gcButton);
}
public static void main(String[] args) {
launch(args);
}
}
MyRoot
package my.group.javafxtestbinding;
import javafx.scene.layout.HBox;
public class MyRoot {
public HBox myRoot;
private final Select select;
private final Show show;
public MyRoot() {
select = new Select();
show = new Show();
myRoot = new HBox();
myRoot.getChildren().addAll(select.myTable, show.myLabel);
show.selectedModelProperty().bind(select.selectedModelProperty());
}
}
Select
package my.group.javafxtestbinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
public class Select {
private ObjectProperty<MyModel> selectedModel;
public TableView<MyModel> myTable;
private final TableColumn<MyModel, String> myColumn;
public Select() {
this.selectedModel = new SimpleObjectProperty<>();
myTable = new TableView();
myColumn = new TableColumn("Name");
myTable.getColumns().add(myColumn);
myTable.setItems(getList());
myColumn.setCellValueFactory(cellData -> cellData.getValue().getNameProperty());
ChangeListener<MyModel> changeListener = new ChangeListener<MyModel>() {
#Override
public void changed(ObservableValue<? extends MyModel> observable, MyModel oldValue, MyModel newValue) {
selectedModel.set(newValue);
}
};
myTable.getSelectionModel().selectedItemProperty().addListener(changeListener);
}
public ObjectProperty<MyModel> selectedModelProperty() {
return selectedModel;
}
private ObservableList<MyModel> getList() {
MyModel model1 = new MyModel("Joe");
MyModel model2 = new MyModel("Jim");
MyModel model3 = new MyModel("Jack");
ObservableList<MyModel> list = FXCollections.observableArrayList(model1, model2, model3);
return list;
}
}
Show
package my.group.javafxtestbinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Label;
public class Show {
private ObjectProperty<MyModel> selectedModel;
public Label myLabel;
public Show() {
this.selectedModel = new SimpleObjectProperty<>();
myLabel = new Label("Init");
ChangeListener listener = new ChangeListener<MyModel>() {
#Override
public void changed(ObservableValue<? extends MyModel> observable, MyModel oldValue, MyModel newValue) {
if (newValue != null) {
myLabel.setText(newValue.getName());
}
}
};
this.selectedModel.addListener(listener);
}
public ObjectProperty<MyModel> selectedModelProperty() {
return selectedModel;
}
}
MyModel
package my.group.javafxtestbinding;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class MyModel {
private final StringProperty name;
public MyModel(String name) {
this.name = new SimpleStringProperty(name);
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
public StringProperty getNameProperty() {
return name;
}
}
Update 2 end
Update 1:
Ok, thank you for your comments.
I thought I was doing it wrong. But if you say that this is the correct way then the problem must be somewhere else. I will post it when I find the problem...
Update 1 end
Original Post:
I have problems binding many properties to one observable.
Let's say I have three (or more) properties ObjectProperty<Foo> firstProp in FirstObject, ObjectProperty<Foo> secondProp in SecondObject and ObjectProperty<Foo> thirdProp in ThirdObject.
How can I bind second and third property to the first which is the observable.
So when firstProp changes the ChangeListeners attached to secondProp and thirdProp will be fired.
What I tried:
SecondObject.secondProperty().bind(FirstObject.firstProperty());
ThirdObject.thirdProperty().bind(FirstObject.firstProperty());
The result is that it sometimes works and sometimes not. If I add more bindings in the same way they do not work at all.
Is this not working because of this:
Note that JavaFX has all the bind calls implemented through weak listeners. This means the bound property can be garbage collected and stopped from being updated.
Javadoc bind()
I find a lot of answers to how to bind many observables to one property, but not the other way.
Can somebody point me in the right direction?

How to concatenate event handler with another method call?

I have two buttons in two separate classes, and I want to change the onAction of the first button when the second button has been pressed to be the original action plus one additional method call. Once the first button has been pressed I want its onAction to revert to the original.
What I currently have working is essientially
Button b1 = new Button("b1");
b1.setOnAction((event)-> {
oldActionMethod();
});
public void oldActionMethod(){
//actual code
}
b2.setOnAction((event)-> {
//some stuff
Button b1 = getB1();
EventHandler<ActionEvent> temp = b1.getOnAction();
b1.setOnAction((event) -> {
b1class.oldActionMethod();
additionalMethod();
b1.setOnAction(temp);
});
});
In order to make this work I had to move the block of code that was originally in the setOnAction lambda expression to a helper function. Is there a cleaner way to do this? Something like this which would eliminate the need for the helper function?
b1.setOnAction((event)-> {
//actual code
});
b2.setOnAction((event) -> {
//stuff
Button b1 = getB1();
EventHandler<ActionEvent> temp = b1.getOnAction();
b1.setOnAction(b1.getOnAction() + methodCall());
b1.setOnAction(temp);
//stuff
});
The way I have it currently does work but it feels really hack-y so I am just interested to know if there is a better option where you could essentially concatenate an actionEvent with another method. Also if there is a way to not require storing the original event in a temp object and resetting it at the end. A possible solution would be if I could tell b2 to listen for the next time b1 is pressed, but I don't know if there is any way to do that when they are in two separate classes.
One solution is to have a shared model class between the two classes containing the buttos.
See the following mcve. For conviniense the entire code can be copy-pasted into one file (FaMain.java) and run:
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FxMain extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Model model = new Model();
AClass aClass = new AClass(model);
BClass bClass = new BClass(model);
Label show = new Label();
show.textProperty().bind(model.getTextProperty());
VBox root = new VBox(10);
root.getChildren().addAll(aClass.getButton(),show, bClass.getButton());
primaryStage.setScene(new Scene(root, 400,100));
primaryStage.sizeToScene();
primaryStage.show();
}
public static void main(final String[] args) {
launch(args);
}
}
class Model {
private final BooleanProperty aButtonSelected;
private final SimpleStringProperty textProperty;
Model(){
aButtonSelected = new SimpleBooleanProperty();
textProperty= new SimpleStringProperty();
}
ObservableValue<? extends String> getTextProperty() {
return textProperty;
}
BooleanProperty aButtonSelectedProperty(){
return aButtonSelected;
}
void bButtonClicked() {
textProperty.set(aButtonSelected.get() ? "Button clicked. Toggle IS selected" :
"Button clicked. Toggle is NOT selected");
}
}
class AClass{
private final ToggleButton aButton;
AClass(Model model) {
aButton = new ToggleButton("Toogle");
model.aButtonSelectedProperty().bind(aButton.selectedProperty());
}
ToggleButton getButton(){
return aButton;
}
}
class BClass{
private final Button bButton;
BClass(Model model) {
bButton = new Button("Click");
bButton.setOnAction(e->model.bButtonClicked());
}
Button getButton(){
return bButton;
}
}

In JavaFX, how do I tell which Stage is in front of another?

I have a JavaFX application with several Stages open to provide floating windows. I want to iterate through these Stages from front to back. I have a list of all the Stages which I'd like to sort and am looking for a method that will let me compare two of them and determine which is in front. Is there a way to do this?
This is just one possibility.
Store each open Stage in a list that can be observed for changes.
Add a listener on each Stage's focusedProperty. When it changes to true,
Remove the stage from our List and readd it at index 0
Now, create a listener on the List and your "focused" Stage will always be at index 0.
You now have an ArrayList that stores the open stages, in order.
Here is a simple MCVE to demonstrate. There are certainly areas to be improved upon and I welcome suggestions, but this does provide some basic functionality.
OpenStages.java:
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
/**
* Implementation of a SimpleListProperty that will store our ObservableArrayList
*/
public class OpenStages<Stage> extends SimpleListProperty<Stage> {
/**
* Constructor that creates an ObservableArrayList
*/
public OpenStages() {
super(FXCollections.observableArrayList());
}
/**
* Removes this Stage from the list and re-adds it at index 0
*/
public void focusStage(Stage stage) {
this.remove(stage);
this.add(0, stage);
}
}
Main.java:
import javafx.application.Application;
import javafx.collections.ListChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
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) {
// Custom SimpleListProperty that holds our ObservableArrayList of open stages
OpenStages<Stage> openStages = new OpenStages<>();
// Simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
Button btnCreateStage = new Button("Create New Stage");
btnCreateStage.setOnAction(event -> {
// Create a new Stage
Stage newStage = new Stage();
// Add a listener to the focusedProperty of the Stage. When focus changes to true, we
// need to update our openStages list
newStage.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
openStages.focusStage(newStage);
}
});
// Add the stage to our OpenStages list.
openStages.add(newStage);
// Simple layout for the new Stage
VBox stageRoot = new VBox();
stageRoot.setPrefSize(300, 100);
stageRoot.setPadding(new Insets(10));
stageRoot.setAlignment(Pos.CENTER);
// Let's add a label and title to indicate which Stage this is
stageRoot.getChildren().add(new Label("Stage #" + openStages.size()));
newStage.setTitle("Stage #" + openStages.size());
newStage.setScene(new Scene(stageRoot));
// Finally, let's show the stage
newStage.show();
});
// Now, let's create a simple listener for our openStages list to print out the focused Stage
openStages.addListener(new ListChangeListener<Stage>() {
#Override
public void onChanged(Change<? extends Stage> c) {
// Only interested in a stage being added
if (c.next() && c.wasAdded()) {
System.out.println("Focused stage: " + openStages.get(0).getTitle());
}
}
});
// Add the Button to our main layout
root.getChildren().add(btnCreateStage);
// Show the Stage
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}

Force tableView edit next cell automatically

After typing in the cell, I want the focus
go to the next edit cell automatically.
How to do this ?
I'm using
tv.getSelectionModel (). select (row + 1);
tv.edit (row + 1, name); // <--- here not work edition next line
But unfortunately it is not working within setOnEditCommit
When it's called by clicking a button that is outside the tableView for example it works. The impression I have is that after setOnEditCommit it puts the last line cell's over.
How to solve this?
Thank's
File of properties
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.image.ImageView;
public class Lin {
private SimpleStringProperty number;
private SimpleStringProperty name;
public Lin(String number, String name ) {
this.number = new SimpleStringProperty(number);
this.name = new SimpleStringProperty(name);
}
public SimpleStringProperty numberProperty() {
return number;
}
public String getNumber() {
return number.getValue();
}
public SimpleStringProperty nameProperty() {
return name;
}
public String getName() {
return name.getValue();
}
}
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TableTesteEdit extends Application {
TableView tv = new TableView();
public ObservableList<Lin> data = FXCollections.observableArrayList();
#Override
public void start(Stage primaryStage) {
TableColumn<Lin,String> number = new TableColumn("#");
TableColumn<Lin,String> name = new TableColumn("Name");
name.setPrefWidth(400);
number.setCellValueFactory(new PropertyValueFactory("number"));
name.setCellValueFactory(new PropertyValueFactory("name"));
name.setCellFactory( TextFieldTableCell.<Lin>forTableColumn() );
name.setOnEditCommit(
new EventHandler<TableColumn.CellEditEvent<Lin, String>>() {
#Override
public void handle(TableColumn.CellEditEvent<Lin, String> t) {
int row = t.getTablePosition().getRow();
((Lin)t.getTableView().getItems().get(row)).nameProperty().setValue(t.getNewValue());
tv.getSelectionModel().select(row+1);
tv.edit(row+1, name); // <--- here not work edition next line
}
}
);
tv.setEditable(true);
number.setEditable(false);
name.setEditable(true);
data.addAll( new Lin("1","AFTER EditCommit go next cell and edit auto"),
new Lin("2","AFTER EditCommit go next cell and edit auto"),
new Lin("3","Congratulations !!!")
);
tv.getColumns().addAll(number, name);
tv.setItems(data);
Scene scene = new Scene(tv, 300, 250);
primaryStage.setTitle("Edit Auto next Cell");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
I think the baked-in behavior of the table's editing sets the editing cell to null once an edit has been committed. My guess is that this happens after the onEditCommit handler has been invoked, so your call to edit the next cell is basically revoked.
A (not very satisfactory) way to fix this is to wrap the call to tv.edit(...) in Platform.runLater(...), which has the effect of delegating it to a runnable which is executed after the pending events have been handled:
// tv.edit(row+1, name);
Platform.runLater(() -> tv.edit(row+1, name));
Caveat: As commented below, this method is not reliable, so if this is mission-critical you should not use this approach. If you're just looking for something that is convenient for the user, if not fully reliable, then this may suffice. Unless you're willing to rewrite the table view skin, this may be as good as it gets...
You should look into the .requestFocus() method if there is something in the next cell that can pull the focus to it for example if you can only type in 4 letters you could use setOnAction(event->{ if(checkLength()) cell2.requestFocus()});
Good Luck

How to set Alert box position over current primaryStage? (JavaFX)

EDIT:
I have an alert box that pops up if the user clicks "Delete" for removing an item in a ListView. It works, but I would like it to pop over the original stage. It showed up in my first monitor. Is there any way to set the position of the alert when it's shown?
Note, the "owner" is in a different class, and I created everything with Scenebuilder/FXML. I cannot figure out how to get initOwner() to work. Here is the "Main" class:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Assignment_5 extends Application {
public Stage primaryStage;
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("Assignment_5.fxml"));
primaryStage.setTitle("Plant Pack");
primaryStage.setScene(new Scene(root, 1200, 500));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Here is the working code within the Controller class. It's not necessary to implement the modality of this alert, but it would be a nice addition to make it more convenient. I simply don't know how to pass the main Window from the Main class to this:
protected void handleDeleteButtonClick(ActionEvent event) {
Alert alertBox = new Alert(Alert.AlertType.CONFIRMATION, "Confirm Delete", ButtonType.OK, ButtonType.CANCEL);
alertBox.setContentText("Are you sure you want to delete this " + plantType.getValue().toString().toLowerCase() + "?");
alertBox.showAndWait();
if(alertBox.getResult() == ButtonType.OK) {
int selectedPlant = plantList.getSelectionModel().getSelectedIndex();
observablePlantList.remove(selectedPlant);
}
else {
alertBox.close();
}
}
I understand this is fairly new, so it's difficult to find many resources. If anyone knows any info I may have missed, please let me know. Thanks for any help offered.
I am using Java 8 with IntelliJ 14.1.5.
As #jewelsea suggests, setting the modality and owner for the alert box will assure that the alert will appear over the stage, even if the stage is moved.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class DeleteAlertDemo extends Application {
Stage owner;
ObservableList<String> observablePlantList;
ListView<String> plantList;
protected void handleDeleteButtonClick(ActionEvent event) {
String item = plantList.getSelectionModel().getSelectedItem();
Alert alertBox = new Alert(Alert.AlertType.CONFIRMATION, "Confirm Delete",
ButtonType.OK, ButtonType.CANCEL);
alertBox.setContentText("Are you sure you want to delete this "
+ item.toLowerCase() + "?");
alertBox.initModality(Modality.APPLICATION_MODAL); /* *** */
alertBox.initOwner(owner); /* *** */
alertBox.showAndWait();
if (alertBox.getResult() == ButtonType.OK) {
int selectedPlant = plantList.getSelectionModel().getSelectedIndex();
observablePlantList.remove(selectedPlant);
} else {
alertBox.close();
}
}
#Override
public void start(Stage primaryStage) {
owner = primaryStage; /* *** */
Button deleteBtn = new Button();
deleteBtn.setText("Delete");
deleteBtn.setOnAction(this::handleDeleteButtonClick);
observablePlantList = FXCollections.observableArrayList("Begonia",
"Peony", "Rose", "Lilly", "Chrysanthemum", "Hosta");
plantList = new ListView<>(observablePlantList);
plantList.getSelectionModel().select(0);
BorderPane root = new BorderPane();
root.setCenter(plantList);
root.setRight(deleteBtn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Delete Alert Demo");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

Resources