I am just starting to learn Java Fx.
I have a combo box filled with objects. I dealt with toString() method, and I can see that name I wanted to display on the screen. But now I would like to make it editable, that user will enter its own text, and ComboBox will create a new object and put that text into the correct field. I know that problem is in converter FromString or ToString, but I cannot deal with it.
package mnet;
import javafx.application.Application;
import javafx.scene.control.ComboBox;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class sample extends Application {
Stage window;
public static void main(String[] args) {
launch(args);
}
public void start(Stage primaryStage) {
window = primaryStage;
window.setTitle("Sample");
GridPane grid = new GridPane();
User usr1 = new User("Witold", "ciastko");
User usr2 = new User("Michał", "styk");
User usr3 = new User("Maciej", "masloo");
ComboBox<User> combo1 = new ComboBox<User>();
combo1.getItems().addAll(usr1, usr2, usr3);
combo1.setConverter(new StringConverter<User>() {
#Override
public String toString(User usr) {
return usr.getName();
}
#Override
public User fromString(String s) {
User usr = new User(s, "haslo");
combo1.getItems().add(usr);
return usr;
}
});
combo1.setEditable(true);
combo1.valueProperty().addListener((v, oldValue, newValue) -> {
System.out.println(newValue);
});
GridPane.setConstraints(combo1, 2, 1);
grid.getChildren().addAll(combo1);
Scene scene = new Scene(grid, 400, 200);
window.setScene(scene);
window.show();
}
}
package mnet;
public class User {
String user;
String password;
public User() {
this.user="";
this.password="";
}
public User(String user, String password){
this.user=user;
this.password=password;
}
public String getName(){
return this.user;
}
}
If I get rid of StringConverter it works correctly, but instead of name of user I only see address of Object like this "mnet.User#1f3b971"
EDIT: Added appropriately working code
You have a null pointer exception in you stringconverter since you can get a null User.
Your string converter should only convert User to/from String without modifying items since you don't know how many time it will be called.
To add a user I add an on event handler (when you type enter) on the combo that add a new user.
Note that thanks to the string converter you can call getValue on the combobox and get a user with the entered name
You should add a plus button to commit the user instead of my on event handler
here my working example :
public class Main extends Application {
Stage window;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
window = primaryStage;
window.setTitle("Sample");
GridPane grid = new GridPane();
User usr1 = new User("Witold", "ciastko");
User usr2 = new User("Michał", "styk");
User usr3 = new User("Maciej", "masloo");
ComboBox<User> combo1 = new ComboBox<User>();
combo1.getItems().addAll(usr1, usr2, usr3);
combo1.setConverter(new StringConverter<User>() {
#Override
public String toString(User usr) {
return usr == null ? "" : usr.getName();
}
#Override
public User fromString(String s) {
User usr = new User(s, "haslo");
return usr;
}
});
combo1.setEditable(true);
combo1.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() == KeyCode.ENTER) {
combo1.getItems().add(combo1.getValue());
}
});
GridPane.setConstraints(combo1, 2, 1);
grid.getChildren().addAll(combo1);
Scene scene = new Scene(grid, 400, 200);
window.setScene(scene);
window.show();
}
Related
I'm currently working on a password manager. Before making any changes to a certain service, the program will ask the user for a password for authorization and then proceed to show the appropriate dialog, if the password is correct.
The issue that I'm having is that if I go through the cycle of putting in my password to make the change, click "ok", and then proceeding to make changes on the shown dialog, on the next turn if instead of putting the password when prompted I close the prompt, then the program retrieves the password from the previous iteration although it has been explicitly cleared. Resulting in the concurrent dialog showing, which is only supposed to show if you put in the correct password.
private void handleEditButton(MouseEvent event) {
Optional<String> rslt = passwordConfirmDialog.showAndWait();
if (rslt.get().equals(""))
return; //Do not proceed
String userInput = rslt.get().trim();
// Complex expression, but use of && statement is necessary to avoid an
// unecessary call to db and have return statement on this IF
if (!(!userInput.isBlank() && isCorrectPassword(userInput))) {
// show dialog
AlertConfigs.invalidPasswordTransactionFailed.showAndWait();
return;
}
System.out.println("Edit Handler: Correct password. -> " + userInput);
//Proceed to show next dialog...
private void initializePasswordConfirmDialog() {
passwordConfirmDialog.setTitle("User Account Control");
passwordConfirmDialog.setHeaderText("Please enter your password to continue.");
// Set the button types.
ButtonType ok = new ButtonType("Ok", ButtonData.OK_DONE);
passwordConfirmDialog.getDialogPane().getButtonTypes().addAll(ok, ButtonType.CANCEL);
final PasswordField psField = new PasswordField();
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(20, 150, 10, 10));
grid.add(new Label("Please Enter your password"), 0, 0);
grid.add(psField, 1, 0);
passwordConfirmDialog.getDialogPane().setContent(grid);
passwordConfirmDialog.setResultConverter(buttonType -> {
String rslt = "";
if (buttonType == ok) {
rslt = psField.getText();
}
psField.clear();
return rslt;
});
}
I've posted a video on YouTube to help visualize the problem. https://youtu.be/sgayh7Q7Ne8
The PasswordField in initializePasswordConfirmDialog() is cleared because whenever I run the the prompt the second time, the PasswordField is blank (visually). Nevertheless, for some reason it still grabs the result from the previous iteration.
The initializePasswordConfirmDialog() is called once inside the constructor and is responsible for set the passwordConfirmDialog variable with the adequate properties.
Some additional code:
HomeController.java
#FXML
private GridPane servicesGrid;
private Dialog<String> passwordConfirmDialog;
private Dialog<Service> editServiceDialog;
private final int NUM_COLUMNS = 7;
public HomeController() {
passwordConfirmDialog = new Dialog<>();
initializePasswordConfirmDialog();
editServiceDialog = new Dialog<>();
}
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
loadServicesGridpane();
}
private void loadServicesGridpane() {
ArrayList<Service> currS = acct.getServices();
// int currentRow = 1;
for (Service s : currS)
addRowToServiceGrid(s);
}
private void addRowToServiceGrid(Service s) {
int rowIdx = servicesGrid.getChildren().size() / 4;
Button editButton = new Button("Edit");
editButton.setOnMouseClicked(event -> {
handleEditButton(event);
});
Button deleteButton = new Button("Delete");
deleteButton.setOnMouseClicked(event -> {
handleDeleteButton(event);
});
deleteButton.setId(s.getServiceName());
Label currServiceName = new Label(s.getServiceName());
currServiceName.setId(s.getServiceName());
Label currUsername = new Label(s.getServiceUsername());
Label currPassword = new Label(s.getServicePassword());
Label dateCreated = new Label(s.getDateCreated());
Label lastPssdChange = new Label(s.getLastPasswordChange());
servicesGrid.addRow(rowIdx, currServiceName, currUsername, currPassword, dateCreated, lastPssdChange,
deleteButton, editButton);
}
To study the problem in isolation, I refactored this example to permit reusing the dialog. As shown below, reusing the dialog requires clearing the password field. Replace the parameter dialog with an invocation of createDialog() to see that creating the dialog each time does not require clearing the password field. Comparing the profile of each approach may help you decide which approach is acceptable; in my experiments, reuse added negligible memory overhead (~250 KB), and it protracted garbage collection slightly(~50 ms).
#!/bin/sh
java … DialogTest -reuse &
pid1=$!
java … DialogTest -no-reuse &
pid2=$!
echo $pid1 $pid2
jconsole $pid1 $pid2
Unfortunately, creating the dialog each time may only appear to solve the problem; it may have exposed a latent synchronization problem. In particular, verify that your result converter's callback executes on the JavaFX Application Thread. To illustrate, I've added a call to Platform.isFxApplicationThread() in resultsNotPresent() below.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/q/73328282/230513
* #see https://stackoverflow.com/a/44172143/230513
*/
public class DialogTest extends Application {
private static boolean REUSE_DIALOG = true;
private record Results(String text, String pass) {
private static Results of(String text, String pass) {
return new Results(text, pass);
}
}
#Override
public void start(Stage stage) {
var label = new Label("Reuse: " + REUSE_DIALOG);
var button = new Button("Button");
if (REUSE_DIALOG) {
var dialog = createDialog();
button.setOnAction(e -> showDialog(dialog));
} else {
button.setOnAction(e -> showDialog(createDialog()));
}
stage.setScene(new Scene(new HBox(8, label, button)));
stage.show();
}
private Dialog<Results> createDialog() {
var dialog = new Dialog<Results>();
dialog.setTitle("Dialog Test");
dialog.setHeaderText("Please authenticate…");
var dialogPane = dialog.getDialogPane();
dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
var text = new TextField("Name");
var pass = new PasswordField();
dialogPane.setContent(new VBox(8, text, pass));
dialog.showingProperty().addListener((o, wasShowing, isShowing) -> {
if (isShowing) {
Platform.runLater(pass::requestFocus);
}
});
dialog.setResultConverter((ButtonType bt) -> {
if (ButtonType.OK == bt) {
var results = Results.of(text.getText(), pass.getText());
pass.clear();
return results;
}
pass.clear();
return null;
});
return dialog;
}
private void showDialog(Dialog<Results> dialog) {
var optionalResult = dialog.showAndWait();
optionalResult.ifPresentOrElse(
(var results) -> System.out.println(results),
(this::resultsNotPresent));
}
private void resultsNotPresent() {
System.out.println("Canceled on FX application thread: "
+ Platform.isFxApplicationThread());
}
public static void main(String[] args) {
if (args.length > 0) {
REUSE_DIALOG = args[0].startsWith("-r");
}
launch(args);
}
}
I have a table in JavaFX. I want to control the show/hide of the thousand commas. Currently, I can control the color by column1.setStyle("-fx-text-fill: green"), but how can I show the thousand commas (and then hide them back in some later stage), probably via a similar approach?
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
TableView tableView = new TableView();
TableColumn<Integer, Person> column1 = new TableColumn<>("Salary");
column1.setCellValueFactory(new PropertyValueFactory("salary"));
tableView.getColumns().add(column1);
tableView.getItems().add(new Person(27000));
tableView.getItems().add(new Person(48000));
column1.setStyle("-fx-text-fill: green");
VBox vbox = new VBox(tableView);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Person:
import javafx.beans.property.SimpleIntegerProperty;
public class Person {
private SimpleIntegerProperty salaryProperty;
public Person() {
}
public Person(int salary) {
this.salaryProperty = new SimpleIntegerProperty(salary);
}
public int getSalary() {
return salaryProperty.get();
}
public void setSalary(int salary) {
this.salaryProperty = new SimpleIntegerProperty(salary);
}
}
I think that it is not possible to do with css. You need to use some kind of NumberFormat like in this example:
App:
package formatter;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.text.NumberFormat;
import java.util.Locale;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
// Create the table view and its column (like you already did):
TableView<Person> tableView = new TableView<>();
TableColumn<Person, Integer> salaryColumn = new TableColumn<>("Salary");
salaryColumn.setCellValueFactory(new PropertyValueFactory<>("salary"));
tableView.getColumns().add(salaryColumn);
// Using a check box in this example to change between formats::
CheckBox useGroupingCheckBox = new CheckBox("use grouping");
// Create a currency formatter with a locale which is important for internationalization:
NumberFormat formatter = NumberFormat.getCurrencyInstance(Locale.CANADA);
formatter.setMaximumFractionDigits(0);
// Create a custom cell:
salaryColumn.setCellFactory(column -> new TableCell<>() {
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText("");
} else {
// Use grouping when check box selected, don't when not selected:
formatter.setGroupingUsed(useGroupingCheckBox.isSelected());
setText(formatter.format(item));
}
}
});
// Refresh table on check box action:
useGroupingCheckBox.setOnAction(event -> tableView.refresh());
// Add some test data:
tableView.getItems().add(new Person(27000));
tableView.getItems().add(new Person(48000));
// Prepare scene and stage:
VBox vbox = new VBox(useGroupingCheckBox, tableView);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch();
}
}
Person class:
package formatter;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class Person {
private IntegerProperty salary;
public Person() {
salary = new SimpleIntegerProperty();
}
public Person(int salary) {
this();
this.salary.set(salary);
}
public Integer getSalary() {
return salary.get();
}
public IntegerProperty salaryProperty() {
return salary;
}
public void setSalary(int salary) {
this.salary.set(salary);
}
}
Preview:
The accepted answer is just fine though a bit smelly because it requires to manually refresh the table when changing the format's property.
An alternative is
to wrap the format - which is not observable - into something that is observable
implement a custom cell which listens to the change/s and updates itself as needed.
An example of how to implement the first (for the grouping state) is FormattingHandler in the code below. Note that
the wrapping property itself implements the update of the contained format
the NumberFormat is completely hidden inside the handler: that's doing the best to not allow changes of its properties under the feet of the handler (obviously it's not entirely fool-proof because outside code can still keep a reference to the format and change it at will - it's a similar isolation level as f.i. the backing list in core ObservableList implementations)
An example of how to implement the second is FormattingCell. It takes a not-null FormattingHandler, registers a listener to the grouping property and updates itself on invalidation notification. Note that this might introduce a memory leak (even though the listener is weak!) if the observable doesn't change at all (it's a known issue in the design of weak listeners that will not be changed, unfortunately) - the only way out would be to move the listening into a custom cell skin and remove the listener in the skin's dispose.
The code (boilderplate stolen from Anko's answer :)
public class DynamicFormattingCellBinding extends Application {
/**
* Observable wrapper around NumberFormat.
*/
public static class FormattingHandler {
/*
* Property controlling the grouping of the format.
*/
private BooleanProperty useGrouping = new SimpleBooleanProperty(this, "useGrouping", false) {
#Override
protected void invalidated() {
super.invalidated();
groupingInvalidated();
}
};
private NumberFormat formatter;
public FormattingHandler(NumberFormat formatter) {
this.formatter = formatter;
setGrouping(formatter.isGroupingUsed());
}
public BooleanProperty groupingProperty() {
return useGrouping;
}
public boolean isGrouping() {
return groupingProperty().get();
}
public void setGrouping(boolean grouping) {
groupingProperty().set(grouping);
}
public String format(Number number) {
return formatter.format(number);
}
private void groupingInvalidated() {
formatter.setGroupingUsed(isGrouping());
}
}
public static class FormattingCell<T, S extends Number> extends TableCell<T, S> {
private FormattingHandler formattingHandler;
private InvalidationListener groupingListener = o -> updateItem(getItem(), isEmpty());
public FormattingCell(FormattingHandler formattingHandler) {
this.formattingHandler = Objects.requireNonNull(formattingHandler, "formatter must not be null");
// Beware: a weak listener isn't entirely safe
// will introduce memory leaks if the observable doesn't change!
formattingHandler.groupingProperty().addListener(new WeakInvalidationListener(groupingListener));
}
#Override
protected void updateItem(S item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText("");
} else {
setText(formattingHandler.format(item));
}
}
}
#Override
public void start(Stage primaryStage) {
TableView<Person> tableView = new TableView<>();
TableColumn<Person, Integer> salaryColumn = new TableColumn<>("Salary");
salaryColumn.setCellValueFactory(cc -> cc.getValue().salaryProperty().asObject());
tableView.getColumns().add(salaryColumn);
// instantiate the formatting support and register bidi binding with a view element
FormattingHandler formatter = new FormattingHandler(NumberFormat.getCurrencyInstance());
CheckBox useGroupingCheckBox = new CheckBox("use grouping");
useGroupingCheckBox.selectedProperty().bindBidirectional(formatter.groupingProperty());
// install custom formatting cell
salaryColumn.setCellFactory(column -> new FormattingCell<>(formatter));
// Add some test data:
tableView.getItems().add(new Person(27000));
tableView.getItems().add(new Person(48000));
// Prepare scene and stage:
VBox vbox = new VBox(useGroupingCheckBox, tableView);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch();
}
private static class Person {
// exact same as in the other answer
}
}
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
Is there a way to get the current opened Stage in JavaFX, if there is one open?
Something like this:
Stage newStage = new Stage();
newStage.initOwner(JavaFx.getCurrentOpenedStage()); //Like this
Java 9 makes this possible by the addition of the javafx.stage.Window.getWindows() method. Therefore you can just get list of Windows and see which are showing
List<Window> open = Stage.getWindows().stream().filter(Window::isShowing);
If you need the current stage reference inside an event handler method, you can get it from the ActionEvent param. For example:
#FXML
public void OnButtonClick(ActionEvent event) {
Stage stage = (Stage)((Node) event.getSource()).getScene().getWindow();
(...)
}
You can also get it from any control declared in your controller:
#FXML
private Button buttonSave;
(...)
Stage stage = (Stage) buttonSave.getScene().getWindow();
There's no built-in functionality for this. In most use cases, you open a new Stage as a result of user action, so you can call getScene().getWindow() on the node on which the action occurred to get the "current" window.
In other use cases, you will have to write code to track current windows yourself. Of course, multiple windows might be open, so you need to track them in some kind of collection. I'd recommend creating a factory class to manage the stages and registering event handlers for the stages opening and closing, so you can update a property and/or list. You'd probably want this to be a singleton. Here's a sample implementation: here getOpenStages() gives an observable list of open stages - the last one is the most recently opened - and currentStageProperty() gives the focused stage (if any). Your exact implementation might be different, depending on your exact needs.
public enum StageFactory {
INSTANCE ;
private final ObservableList<Stage> openStages = FXCollections.observableArrayList();
public ObservableList<Stage> getOpenStages() {
return openStages ;
}
private final ObjectProperty<Stage> currentStage = new SimpleObjectProperty<>(null);
public final ObjectProperty<Stage> currentStageProperty() {
return this.currentStage;
}
public final javafx.stage.Stage getCurrentStage() {
return this.currentStageProperty().get();
}
public final void setCurrentStage(final javafx.stage.Stage currentStage) {
this.currentStageProperty().set(currentStage);
}
public void registerStage(Stage stage) {
stage.addEventHandler(WindowEvent.WINDOW_SHOWN, e ->
openStages.add(stage));
stage.addEventHandler(WindowEvent.WINDOW_HIDDEN, e ->
openStages.remove(stage));
stage.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
currentStage.set(stage);
} else {
currentStage.set(null);
}
});
}
public Stage createStage() {
Stage stage = new Stage();
registerStage(stage);
return stage ;
}
}
Note this only allows you to track stages obtained from StageFactory.INSTANCE.createStage() or created elsewhere and passed to the StageFactory.INSTANCE.registerStage(...) method, so your code has to collaborate with that requirement. On the other hand, it gives you the chance to centralize code that initializes your stages, which may be otherwise beneficial.
Here's a simple example using this:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
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;
import javafx.stage.WindowEvent;
public class SceneTrackingExample extends Application {
int count = 0 ;
#Override
public void start(Stage primaryStage) {
StageFactory factory = StageFactory.INSTANCE ;
factory.registerStage(primaryStage);
configureStage(primaryStage);
primaryStage.show();
}
private void configureStage(Stage stage) {
StageFactory stageFactory = StageFactory.INSTANCE;
Stage owner = stageFactory.getCurrentStage() ;
Label ownerLabel = new Label();
if (owner == null) {
ownerLabel.setText("No owner");
} else {
ownerLabel.setText("Owner: "+owner.getTitle());
stage.initOwner(owner);
}
stage.setTitle("Stage "+(++count));
Button newStage = new Button("New Stage");
newStage.setOnAction(e -> {
Stage s = stageFactory.createStage();
Stage current = stageFactory.getCurrentStage() ;
if (current != null) {
s.setX(current.getX() + 20);
s.setY(current.getY() + 20);
}
configureStage(s);
s.show();
});
VBox root = new VBox(10, ownerLabel, newStage);
root.setAlignment(Pos.CENTER);
stage.setScene(new Scene(root, 360, 150));
}
public enum StageFactory {
INSTANCE ;
private final ObservableList<Stage> openStages = FXCollections.observableArrayList();
public ObservableList<Stage> getOpenStages() {
return openStages ;
}
private final ObjectProperty<Stage> currentStage = new SimpleObjectProperty<>(null);
public final ObjectProperty<Stage> currentStageProperty() {
return this.currentStage;
}
public final javafx.stage.Stage getCurrentStage() {
return this.currentStageProperty().get();
}
public final void setCurrentStage(final javafx.stage.Stage currentStage) {
this.currentStageProperty().set(currentStage);
}
public void registerStage(Stage stage) {
stage.addEventHandler(WindowEvent.WINDOW_SHOWN, e ->
openStages.add(stage));
stage.addEventHandler(WindowEvent.WINDOW_HIDDEN, e ->
openStages.remove(stage));
stage.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
currentStage.set(stage);
} else {
currentStage.set(null);
}
});
}
public Stage createStage() {
Stage stage = new Stage();
registerStage(stage);
return stage ;
}
}
public static void main(String[] args) {
launch(args);
}
}
You can create a label in your java fxml.
Then in your controller class refer your label like this :
#FXML
private Label label;
Then in any function of the controller class you can access the current stage by this block of code :
private void any_function(){
Stage stage;
stage=(Stage) label.getScene().getWindow();
}
I am using the following code with java 8 using javaFx.
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.web.*;
import javafx.stage.Stage;
#SuppressWarnings("all")
public class Highlighter extends Application {
private boolean marked;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final WebView webView = new WebView();
final WebEngine engine = webView.getEngine();
engine.load("http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html");
final TextField searchField = new TextField("light");
searchField.setPromptText("Enter the text you would like to highlight and press ENTER to highlight");
searchField.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
if (engine.getDocument() != null) {
highlight(
engine,
searchField.getText()
);
}
}
});
final Button highlightButton = new Button("Highlight");
highlightButton.setDefaultButton(true);
highlightButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
searchField.fireEvent(new ActionEvent());
}
});
final Button markedButton = new Button("Mark it");
markedButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
marked = true;
}
});
markedButton.setCancelButton(true);
HBox controls = new HBox(10);
controls.getChildren().setAll(
highlightButton,
markedButton
);
VBox layout = new VBox(10);
layout.getChildren().setAll(searchField, controls, webView);
searchField.setMinHeight(Control.USE_PREF_SIZE);
controls.setMinHeight(Control.USE_PREF_SIZE);
controls.disableProperty().bind(webView.getEngine().getLoadWorker().runningProperty());
searchField.disableProperty().bind(webView.getEngine().getLoadWorker().runningProperty());
primaryStage.setScene(new Scene(layout));
primaryStage.show();
webView.requestFocus();
}
private void highlight(WebEngine engine, String text) {
engine.executeScript("$('body').removeHighlight().highlight('" + text + "')");
}
}
My problem is I want to add a label which displays the marked status of a page.
I tried simply adding a Label label = new Label("Marked: " + marked) to the controls, but this does not work.
Any recommendations how I could add a label to my code to display the marked status?
I appreciate your replies!
If you add a Label to controls with your actual code:
private boolean marked;
Label label = new Label("Marked: " + marked)
controls.getChildren().setAll(
highlightButton,
markedButton,
label
);
it will always show Marked: false, no matter if you change marked afterwards.
If you want that your control responds to changes, JavaFX has observable properties, as you can read here.
So you can replace the boolean primitive with this property that wraps the boolean value:
private final BooleanProperty marked=new SimpleBooleanProperty();
Create the label:
Label label=new Label("Marked: "+marked.get());
HBox controls = new HBox(10);
controls.setAlignment(Pos.CENTER_LEFT);
controls.getChildren().setAll(
highlightButton,
markedButton,
label
);
Change the event for markedButton:
markedButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
marked.set(true);
}
});
(this will work just once, since for now you don't have implemented a way to reset marked to false again)
And finally, add a listener for any change inmarked property:
marked.addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
label.setText("Marked: "+newValue);
}
});
Instead of the listener, you can also use Bindings:
Label label=new Label();
label.textProperty().bind(Bindings.concat("Marked: ").concat(marked));