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

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();
}
}

Related

JavaFX ComboBox check input and change it

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();
}
}

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;
}
}

JavaFX Dynamic Form Field UI

Does anyone know how to imitate the functionality from the UI components shown below? I want to replicate adding form fields when text is entered into the TextField box. I don't need the dropdown button, just the dynamic adding of the forms.
You could modify the children of a GridPane adding a new TextField & Button every time one of the buttons is activated. Listen to the text properties to enable/disable the Button and save the results.
private static void insertRow(GridPane grid, List<String> values, int index) {
// increment index of children with rowIndex >= index
for (Node n : grid.getChildren()) {
int row = GridPane.getRowIndex(n);
if (row >= index) {
GridPane.setRowIndex(n, row + 1);
}
}
TextField text = new TextField();
Button add = new Button("+");
add.setDisable(true);
add.setOnAction(evt -> {
insertRow(grid, values, GridPane.getRowIndex(add) + 1);
});
values.add(index, "");
text.textProperty().addListener((a, oldValue, newValue) -> {
add.setDisable(newValue.isEmpty());
values.set(GridPane.getRowIndex(add), newValue);
});
grid.addRow(index, text, add);
}
#Override
public void start(Stage primaryStage) throws Exception {
GridPane grid = new GridPane();
List<String> list = new ArrayList<>();
insertRow(grid, list, 0);
Button print = new Button("print");
print.setOnAction(evt -> {
System.out.println(list);
});
grid.add(print, 0, 1);
Scene scene = new Scene(grid, 300, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
This may not be exactly what you're looking for and may not be the best way to do this, but should be easy to adapt it to your needs.
Basically, you will need a list of HBox objects to be added to a VBox in your application. You could create the list yourself and bind it to the children of your VBox, or just add/remove the HBoxes to/from the VBox using the getChildren().add() and getChildren().remove() methods.
Here is a complete little application to demonstrate the concept. I created an internal class to handle the HBox with the fields you need. This could be adapted to be more felixable:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
private static VBox mainPane;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
mainPane = new VBox(5);
mainPane.setPadding(new Insets(10));
mainPane.setAlignment(Pos.TOP_CENTER);
mainPane.getChildren().add(new UIForms());
primaryStage.setScene(new Scene(mainPane));
primaryStage.show();
}
static void addField() {
mainPane.getChildren().add(new UIForms());
}
static void removeField(UIForms field) {
if (mainPane.getChildren().size() > 1) {
mainPane.getChildren().remove(field);
}
}
}
class UIForms extends HBox {
private TextField textField1;
private TextField textField2;
private Button btnAddField;
private Button btnRemoveField;
public UIForms() {
// Setup the HBox layout
setAlignment(Pos.CENTER_LEFT);
setSpacing(5);
// Create the UI controls
textField1 = new TextField();
textField2 = new TextField();
btnAddField = new Button("+");
btnRemoveField = new Button("-");
// Setup button actions
btnAddField.setOnAction(e -> Main.addField());
btnRemoveField.setOnAction(e -> Main.removeField(this));
// Add the UI controls
getChildren().addAll(
textField1, textField2, btnAddField, btnRemoveField
);
}
}

Javafx add a dropdown event listener for choicebox

I have a ChoiceBox and I want to refresh it's content whenever user expands it. I haven't found a proper listener for this. All the stuff google gives is related to handling ChangeValue events.
I reckon I should add eventListener<ActionEvent> to ChoiceBox since what I'm handling is click on a ChoiceBox, but my implementation doesn't work.
ActionEvent fires when I click on any List value, not when I click ChoiceBox itself.
Register a listener with the choice box's showingProperty:
choiceBox.showingProperty().addListener((obs, wasShowing, isNowShowing) -> {
if (isNowShowing) {
// choice box popup is now displayed
} else {
// choice box popup is now hidden
}
});
Here is a quick demo:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ChoiceBoxPopupTest extends Application {
private int nextValue ;
#Override
public void start(Stage primaryStage) {
ChoiceBox<Integer> choiceBox = new ChoiceBox<>();
choiceBox.getItems().add(nextValue);
choiceBox.setValue(nextValue);
choiceBox.showingProperty().addListener((obs, wasShowing, isNowShowing) -> {
if (isNowShowing) {
choiceBox.getItems().setAll(++nextValue, ++nextValue, ++nextValue);
}
});
BorderPane root = new BorderPane();
root.setTop(choiceBox);
BorderPane.setAlignment(choiceBox, Pos.CENTER);
root.setPadding(new Insets(5));
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Bind CSS Style Property to Node in JavaFX

I want to set up a model for my project so my controllers can communicate with each other. I want it to have a setter and getter, to allow easy access to styling certain nodes from either class.
My question: is it possible to bind a style property (ex. "-fx-background-color: blue") to a node?
From my research, I see that this is definitely possible with text values for labels (explained by James_D here: JavaFX - How to use a method in a controller from another controller?), but I am having a hard time figuring out what the syntax for doing a similar thing with "setStyle" would be.
The model I have so far:
public class Model {
private final StringProperty shadow = new SimpleStringProperty("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.24), 10,0,0,0)");
public StringProperty shadowProperty() {
return shadow;
}
public final String getShadow() {
return shadowProperty().get();
}
public final void setShadow(String shadow) {
shadowProperty().set(shadow);
}
}
I understand how I would set the "shadow" value from a controller, but what I don't understand is how I can bind a node from another controller to listen to that change.
Let's say the node is something like:
#FXML AnchorPane appBar
I want "appBar" to take on any changes made to "shadow" in the model. What would that look like?
You need to add a listener to the shadowProperty to listen to its changes.
something.shadowProperty() .addListener( (observable, oldValue, newValue) -> {
//do something with appBar
}) ;
I'm not entirely sure what you want to achieve, but this should answer your question about how to listen to property changes.
PS: im on mobile, so no guarantees regarding typos
Edit: you can also bind the property of one object to the property of another. Use bind() for that.
EDIT: Here is an example:
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
Property<Background> backgroundProperty;
StringProperty styleProperty;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
VBox root = new VBox(10);
backgroundProperty = new SimpleObjectProperty<>();
styleProperty = new SimpleStringProperty();
// Pane that changes background by listener
Pane pane1 = new Pane();
pane1.setMinHeight(40);
backgroundProperty.addListener( (observable, oldValue, newValue) -> {
pane1.setBackground(backgroundProperty.getValue());
});
// Pane that changes background by property binding
Pane pane2 = new Pane();
pane2.setMinHeight(40);
pane2.backgroundProperty().bind(backgroundProperty);
// Pane that binds the style property
Pane pane3 = new Pane();
pane3.setMinHeight(40);
pane3.styleProperty().bind(styleProperty);
backgroundProperty.setValue(new Background(new BackgroundFill(Color.RED, null, null)));
styleProperty.setValue("-fx-background-color: black");
root.getChildren().add(pane1);
root.getChildren().add(pane2);
root.getChildren().add(pane3);
Scene scene = new Scene(root, 200, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
}

Resources