JavaFX: Opening a stage after delay on button click - javafx

sorta new to Javafx. I'm running into a problem when in my controller class for my main stage, Im trying to: after clicking on a button, start a delay that will then open a new stage.
The problem comes to when loading the FXML file within the button action event and in the run() part of the Scheduler that I'm using for the delay.
I can't throw the exception in the run() because it clashes with itself?
And I can't catch it either in the Parent Root by doing :
Parent root = null;
try {
root = FXMLLoader.load(getClass().getResource("Popup.fxml"));
} catch (IOException e) {
e.printStackTrace();
}
because it just flat out won't run. I think Im approaching this poorly or just the wrong way entirely.
I can't seem to get this to work. The stage runs fine just on button click but I need the delay.
Code for Controller Class:
public class Controller implements Initializable {
public static int seconds;
public static boolean yes = false;
public String strtime = String.format("Current Date/Time : %tc", new Date());
#FXML
Button remind;
#FXML
Label secondsuntil,date;
#FXML
TextField Day, Minute, remindname;
#FXML
private Button Butt;
static String Nameoftask;
int days, minutes;
#Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
date.setText(strtime);
}
#FXML
private void handleButtonAction(ActionEvent actionEvent) throws IOException {
final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> countdown = scheduler.schedule(new Runnable() {
#Override
public void run(){
Stage primaryStage = new Stage();
Parent root = FXMLLoader.load(getClass().getResource("Popup.fxml"));
primaryStage.setTitle(Nameoftask);
primaryStage.initModality(Modality.NONE);
primaryStage.setScene(new Scene(root, 200,50));
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
double width = screenSize.getWidth();
double height = screenSize.getHeight();
primaryStage.setX(width);enter code here
primaryStage.setY(height*-1);
primaryStage.show();
try {
FXMLLoader loader = new
FXMLLoader(getClass().getResource("Popup.fxml"));
loader.setController(new Controller());
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}}, 1, TimeUnit.SECONDS);
}
}

Related

JavaFX - How to switch to another tab with mouse click event [duplicate]

So I'm trying to load and save Images into an imageView where the location of the image is chosen through a file browser. I've been working on this for several days now and I'm gonna have a stroke if I can't get it fixed. I've tried everything I can think of. Thank you in advance for helping.
UPDATED:
Here is my main class:
public class Main extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
public Main(){}
#Override
public void start(Stage primaryStage) throws Exception{
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Help Please");
initRootLayout();
showScreen();
}
public void initRootLayout(){
try{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("view/RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
RootLayout controller = loader.getController();
controller.setMain(this);
primaryStage.show();
}catch(Exception e ){e.printStackTrace();}
}
public void showScreen(){
try{FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("view/sample.fxml"));
BorderPane sample = (BorderPane)loader.load();
rootLayout.setCenter(sample);
Controller controller = loader.getController();
controller.setMain(this);
}catch (Exception e){e.printStackTrace();}
}
public Stage getPrimaryStage(){return primaryStage;}
public static void main(String[] args) {
launch(args);
}
}
Here is the rootLayout:
public class RootLayout {
private Main main;
private Controller controller = new Controller();
public void setMain(Main main){this.main = main;}
#FXML
private void handleOpen(){
FileChooser fileChooser = new FileChooser();
FileChooser.ExtensionFilter extensionFilter = new FileChooser.ExtensionFilter(
"PNG files (*.png)","*png");
fileChooser.getExtensionFilters().add(extensionFilter);
File file = fileChooser.showOpenDialog(main.getPrimaryStage());
if(file!= null){
controller.updateImage(file.toURI().toString());
}
}
}
And here is the controller:
public class Controller implements Initializable {
#FXML
ImageView imageView = new ImageView();
String imageURL;
Main main = new Main();
public void setMain(Main main){
this.main = main;
}
public void updateImage(String url){
if(url.length()>=1){
Image image = new Image(url);
imageView.setImage(image);
System.out.println(url);
}
else{
System.out.println(url);
System.out.println("image invalid");
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {
}
}
Two things:
Never assign a field whose value is to be injected by an FXMLLoader (e.g. #FXML fields). Doing so is a waste of resources at best and introduces subtle bugs at worst. For instance, if you were to leave the imageView field uninitialized you'd be getting a NullPointerException which would indicate a problem with your setup. Since you do initialize the field, however, you don't get any errors and there's a false impression of the code working.
In your RootLayout controller class, you have:
private Controller controller = new Controller();
That instance of Controller you just created is not linked to any FXML file. And since you initialize the imageView field (see first point) you end up updating an ImageView which is not being displayed anywhere; this is where not initializing said field would have given a nice indication of there being a problem. The solution is to pass the Controller instance created by the FXMLLoader to the RootLayout instance created by the other FXMLLoader.
Also, in the same class you have:
Main main = new Main();
Which is also unnecessary since the created instance of Main is both not the correct instance and is replaced by the call to #setMain(Main) almost immediately.
Assuming your FXML files (which you did not provide) are correct, the Java classes should look more like:
Main.java
public class Main extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
private RootLayout rootLayoutController;
public Main() {}
#Override
public void start(Stage primaryStage) throws Exception {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Help Please");
initRootLayout();
showScreen();
}
public void initRootLayout() {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("view/RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
// store RootLayout instance in field so #showScreen()
// can reference it
rootLayoutController = loader.getController();
rootLayoutController.setMain(this);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public void showScreen() {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("view/sample.fxml"));
BorderPane sample = (BorderPane) loader.load();
rootLayout.setCenter(sample);
Controller controller = loader.getController();
controller.setMain(this);
// set Controller instance on RootLayout instance
rootLayoutController.setController(controller);
} catch (Exception e) {
e.printStackTrace();
}
}
public Stage getPrimaryStage() {
return primaryStage;
}
public static void main(String[] args) {
launch(args);
}
}
RootLayout.java
public class RootLayout {
private Main main;
private Controller controller;
public void setMain(Main main) {
this.main = main;
}
public void setController(Controller controller) {
this.controller = controller;
}
#FXML
private void handleOpen() {
FileChooser fileChooser = new FileChooser();
// Note extensions should be prefixed with "*."
FileChooser.ExtensionFilter extensionFilter =
new FileChooser.ExtensionFilter("PNG files (*.png)", "*.png");
fileChooser.getExtensionFilters().add(extensionFilter);
File file = fileChooser.showOpenDialog(main.getPrimaryStage());
if (file != null) {
controller.updateImage(file.toURI().toString());
}
}
}
Controller.java
public class Controller implements Initializable {
#FXML ImageView imageView; // leave uninitialized, will be injected
String imageURL;
Main main;
public void setMain(Main main) {
this.main = main;
}
public void updateImage(String url) {
if (url.length() >= 1) {
Image image = new Image(url);
imageView.setImage(image);
System.out.println(url);
} else {
System.out.println(url);
System.out.println("image invalid");
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {}
}
Note: Did not test new code.

Can we change the scene in Java Scene Builder 2.0 without using button after 5 seconds

Basically i am using JavaFX scene buidler 2.0 i want to change the scene from one to other without using any button for them.
Main File
public class OurFirstProject extends Application {
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
stage.setFullScreenExitHint("");
//stage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
Scene scene = new Scene(root, screenBounds.getWidth(), screenBounds.getHeight());
stage.setScene(scene);
stage.setFullScreen(true);
stage.show();
public static void main(String[] args) {
Application.launch(args);
}
}
public class FXMLDocumentController implements Initializable {
#FXML
private void change(ActionEvent event) throws IOException {
Parent sceneChange = FXMLLoader.load(getClass().getResource("Change.fxml"));
Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
Scene changeScene = new Scene(sceneChange, screenBounds.getWidth(), screenBounds.getHeight());
Stage Window = (Stage) ((Node) event.getSource()).getScene().getWindow();
Window.setScene(changeScene);
Window.setFullScreen(true);
Window.show();
}
int a = 0;
#FXML
public Button helloButton;
#FXML
private Label ourLabel;
#FXML
private void printHello(ActionEvent e) {
a++;
if (a % 2 == 0) {
ourLabel.setText("Hello World! Kyun" + a);
} else {
ourLabel.setText("Hello Dunia" + a);
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
enter code here
}
The Scene Mentioned in the FXML File Name"change", i want to Run this Scen without Using Button I wanna run this on the delay of five second of FIrst scene.
You can use a Timer() like,
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {// After this you can add your change.fxml load code
Parent root = null;
try {
root = fxmlLoader.load();
}catch(Exception e)
{
//Exception catch code here
}
primaryStage.show();//Here you can write your show code like window.show()
}
});
}
},5000);// 5000- time delay in milliseconds

Update UI in method called from ActionEvent thread using JavaFX

I want to change the stage when a user logs in the application.
I have created a thread in the Action and inside it I use Platform.runLater to update the stage and show the new one. This is done in the called method.
So I have the following code:
Logincontroller
private Stage primaryStage
#FXML
void btnLoginAction(ActionEvent event) throws ClassNotFoundException {
Runnable loginRunnable = new Runnable() {
public void run() {
....
if (user exists and password ok){
loadMainwindow();
}else{
show alert
}
};
Thread loginThread = new Thread(loginRunnable);
loginThread.start();
}
private void loadMainWindow() throws IOException {
dummyStage = (Stage) (btnLogin.getScene()).getWindow();
//I get the root borderpain from the Main class
BorderPane root = Main.getRoot();
//I load the anchorpanes i will use in the new stage
AnchorPane menuPane =
FXMLLoader.load(getClass().getResource("/views/Menu.fxml"));
AnchorPane centerPane =
FXMLLoader.load(getClass().getResource("/views/Home.fxml"));
//I set the anchorpanes to the root
root.setLeft(menuPane);
root.setCenter(centerPane);
Platform.runLater(new Runnable() {
public void run() {
primaryStage.show();
}
});
}
And I´m having the following error:
Exception in thread "Thread-3" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3
at javafx.graphics/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:291)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:493)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:206)
at javafx.graphics/javafx.scene.layout.BorderPane$BorderPositionProperty.invalidated(BorderPane.java:692)
at javafx.base/javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
at javafx.base/javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:147)
at javafx.graphics/javafx.scene.layout.BorderPane.setLeft(BorderPane.java:325)
at com.sener.dbgui.controller.LoginController.loadMainWindow(LoginController.java:90)
at com.sener.dbgui.controller.LoginController.access$4(LoginController.java:81)
at com.sener.dbgui.controller.LoginController$1.run(LoginController.java:63)
at java.base/java.lang.Thread.run(Thread.java:844)
Line 81 is the "root.setLeft(menuPane)" line.
So I guess the problem is that when modifying the root borderpane the JAVAFX thread must be running. This is, I must include the "root.set..." statements in the Platform.runLater method.
Nonetheless, this would imply in setting multiple variables for root, menuPane and centerPane to private in the controller class so that Platform.runLater process could access them and all the FXMLLoader, getwindow() and getRoot() methods could be decoupled from Platform.runLater.
So, is it better to create set this variables to private or just call the method inside the Platform.runLater?
OPTION 1. CALL METHOD INSIDE Platform.runLater
#FXML
void btnLoginAction(ActionEvent event) throws ClassNotFoundException {
Runnable loginRunnable = new Runnable() {
public void run() {
....
if (user exists and password ok){
Platform.runLater(new Runnable() {
public void run() {
loadMainwindow();
}
});
}else{
show alert
}
};
Thread loginThread = new Thread(loginRunnable);
loginThread.start();
}
If decoupling FXMLLoader, getWindow() and getRoot methods from Platform.runLater the code of the method would look like this (I would first create private variables for AnchorPanes "menuPane" and "centerPane", BorderPane "root" just like with "primaryStage" variable):
OPTION 2. CALL METHOD AND DECOUPLE FMLXLOADERS, GETROOT() AND GETWINDOW() METHODS FROM Platform.runLater()
private AnchorPane menuPane, centerPane;
private Stage dummyStage;
private BorderPane root;
#FXML
void btnLoginAction(ActionEvent event) throws ClassNotFoundException {
Runnable loginRunnable = new Runnable() {
public void run() {
....
if (user exists and password ok){
loadMainwindow();
}else{
show alert
}
};
Thread loginThread = new Thread(loginRunnable);
loginThread.start();
}
private void loadMainWindow() throws IOException {
root = Main.getRoot();
primaryStage = (Stage) (btnLogin.getScene()).getWindow();
menuPane = FXMLLoader.load(getClass().getResource("/views/Menu.fxml"));
centerPane = XMLLoader.load(getClass().getResource("/views/Home.fxml"));
Platform.runLater(new Runnable() {
public void run() {
root.setLeft(menuPane);
root.setCenter(centerPane);
primaryStage.toFront();
primaryStage.show();
}
});
}
I would like to know which option is the correct one. Or maybe these are wrong and there´s another solution to this.

How to prepare FXML Controller to be GC

I have an issue in my application. Any Controllers are garbage collected.
I was prepared one, pretty simple with clear function, but it is no removed from memory.
#Log4j2
public class DialogOpenProjectController extends DialogPane implements Initializable, FXMLController, FXMLPane, FXMLDialogController {
#FXML
private ObjectProperty<ResourceBundle> languageBundle = new SimpleObjectProperty<>();
#FXML
private JFXTabPane TAB_PANE_OPEN_PROJECT;
#FXML
private JFXButton BUTTON_CONFIRM;
private Tab tabOpenNewProject;
private Tab tabOpenExistingProject;
private Stage stage;
private ChangeListener<? super ResourceBundle> languageListener = this::languageChange;
private ChangeListener<? super Tab> selectedTabListener = this::selectedTabChanged;
{
tabOpenExistingProject = new Tab();
tabOpenNewProject = new Tab();
}
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
log.trace(LogMessages.MSG_CTRL_INITIALIZATION);
this.languageBundle.setValue(rb);
initTabs();
addSelectedTabListener();
addDisableButtonListener();
addLanguageListener();
log.trace(LogMessages.MSG_CTRL_INITIALIZED);
}
#FXML
public void cancel() {
this.stage.close();
clean();
}
#FXML
public void confirm() {
if (TAB_PANE_OPEN_PROJECT.getSelectionModel().getSelectedItem().equals(tabOpenNewProject)) {
actionNewProject();
} else if (TAB_PANE_OPEN_PROJECT.getSelectionModel().getSelectedItem().equals(tabOpenExistingProject)) {
actionOpenProject();
} else {
//To to show error
}
this.stage.close();
clean();
}
private void initTabs() {
TAB_PANE_OPEN_PROJECT.getSelectionModel().select(tabOpenNewProject);
}
private void addSelectedTabListener() {
TAB_PANE_OPEN_PROJECT.getSelectionModel().selectedItemProperty().addListener(selectedTabListener);
}
private void addDisableButtonListener() {
//nothing to do temporary
}
private void clean() {
this.languageBundle.removeListener(languageListener);
languageBundle.unbind();
languageBundle.setValue(null);
TAB_PANE_OPEN_PROJECT.getSelectionModel().selectedItemProperty().removeListener(selectedTabListener);
TAB_PANE_OPEN_PROJECT.getSelectionModel().clearSelection();
TAB_PANE_OPEN_PROJECT.getTabs().clear();
BUTTON_CONFIRM.disableProperty().unbind();
selectedTabListener = null;
languageListener = null;
tabOpenNewProject = null;
tabOpenExistingProject = null;
stage = null;
getChildren().clear();
}
private void addLanguageListener() {
this.languageBundle.addListener(languageListener);
}
private void languageChange(ObservableValue<? extends ResourceBundle> observable, ResourceBundle oldValue, ResourceBundle newValue) {
reloadElements();
}
private String getValueFromKey(String key) {
return this.languageBundle.getValue().getString(key);
}
private void reloadElements() {
// Nothing to do
}
public void setStage(Stage stage) {
this.stage = stage;
}
private void selectedTabChanged(ObservableValue<? extends Tab> observable, Tab oldValue, Tab newValue) {
if (newValue.equals(tabOpenNewProject)) {
BUTTON_CONFIRM.setText(getValueFromKey(Keys.CREATE));
} else if (newValue.equals(tabOpenExistingProject)) {
BUTTON_CONFIRM.setText(getValueFromKey(Keys.OPEN));
}
}
}
For loading FXML files I use Singleton Class ScreenManager. This method is called to load this Dialog :
public void showNewDialog( FilesFXML fxml) {
FXMLLoader loader = new FXMLLoader(getClass().getResource(fxml.toString()), context.getBundleValue());
try {
Stage dialogStage = new Stage();
AnchorPane dialogwindow = (AnchorPane) loader.load();
FXMLDialogController controller = loader.getController();
dialogStage.initModality(Modality.WINDOW_MODAL);
dialogStage.initOwner(this.getStage());
Scene scene = new Scene(dialogwindow);
dialogStage.setScene(scene);
dialogStage.setResizable(false);
controller.setStage(dialogStage);
dialogStage.showAndWait();
} catch (Exception ex) {
log.error(ex.getMessage());
log.error(ex.getCause());
ex.printStackTrace();
}
}
I was checked it in VisualVM and I see one 1 of this controller and 2 lambdas ( I suppose it consiste initialization of 2 listener)
But event clear function is called this dialog is still in memory and cannot be garbage collected. I have no more idea how to force removing it. It is reallegrave, because it consist all of my Controllers.
I'm pretty confused what you are asking and what you are talking about. Nevertheless, if that dialog window is open, there is no way it can be garbage collected.
The JavaFX main API is holding a strong reference of the stage (maybe the scene too).
The stage/scene is holding strong reference of all the nodes specified in your FXML file.
Some of those nodes are holding strong reference of the controller because you have specified action event handlers in the controller.
In order to make the controller eligible for garbage collection, you need to break this chain somewhere.
The easiest way is to close the dialog window. When the window is closed, because the JavaFX engine does not need to manage render pulses anymore, it will release its reference to the stage. At that point, the whole thing under that stage is eligible for garbage collection, if nothing else (that itself is not eligible for garbage collection) is holding strong references of them.

JavaFX stage.setOnCloseRequest without function?

This is my Start-Methode. First i create a stage and set a title and a scene. And i wanna create a dialog if someone wants to close the window on the window-close-btn [X]. i thought i will catch this event with the setOnCloseRequest()-function. But i still can close all stages i open while runtime.
#Override
public void start(final Stage primaryStage) throws Exception {
primaryStage.setTitle("NetControl");
primaryStage.setScene(
createScene(loadMainPane())
);
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(final WindowEvent event) {
//Stage init
final Stage dialog = new Stage();
dialog.initModality(Modality.APPLICATION_MODAL);
// Frage - Label
Label label = new Label("Do you really want to quit?");
// Antwort-Button JA
Button okBtn = new Button("Yes");
okBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
dialog.close();
}
});
// Antwort-Button NEIN
Button cancelBtn = new Button("No");
cancelBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
primaryStage.show();
dialog.close();
}
});
}
});
primaryStage.show();
}
private Pane loadMainPane() throws IOException {
FXMLLoader loader = new FXMLLoader();
Pane mainPane = (Pane) loader.load(
getClass().getResourceAsStream(ContentManager.DEFAULT_SCREEN_FXML)
);
MainController mainController = loader.getController();
ContentManager.setCurrentController(mainController);
ContentManager.loadContent(ContentManager.START_SCREEN_FXML);
return mainPane;
}
private Scene createScene(Pane mainPane) {
Scene scene = new Scene(mainPane);
setUserAgentStylesheet(STYLESHEET_MODENA);
return scene;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Application.launch(args);
}
Are there any other functions to catch the window-events?
or isnt it logical to run the CloseRequest on the primaryStage, i read something with platforms (but i dont know if it necessary for my problem)?
In the onCloseRequest handler, call event.consume();.
That will prevent the primary stage from closing.
Remove the primaryStage.show(); call from the cancel button's handler and add a call to primaryStage.hide(); in the OK button's handler.

Resources