JavaFx closing Stages unpredictable behaviour - java

So I have a Program that is based on Hydra. A Window pops up, when trying to close it, it closes but two more windows pop up in its place. There are two ways to close the window either by pressing the close button or the red cross.
My problem is the program is behaving very unpredictable sometimes it will close it and open 2 new ones sometimes it will not close and not open a new one.
WARNING!!!! if you execute this code you will have to kill the program via Task Manager or IDE.
#Override
public void start(Stage stage) throws Exception {
this.stage = stage;
this.stage.setTitle("Hail Hydra");
placeNewHeadRandomly();
this.stage.setScene(growHead());
this.stage.setOnCloseRequest(e -> {
cutOffHead();
});
this.stage.show();
}
private void placeNewHeadRandomly() {
// not important for this question but randomly changes windows X and Y.
}
private Scene growHead() {
// not important for this question creates a window with a button that calls cutOffHead();
VBox vbox = new VBox();
vbox.setPrefWidth(WINDOW_WIDTH);
vbox.setPrefHeight(WINDOW_HEIGHT);
vbox.setAlignment(Pos.CENTER);
vbox.setSpacing(10);
Label warning = new Label("Cut off one hydra head, two more will grow back in its place.");
Button sword = new Button("close");
sword.setOnAction(e -> cutOffHead());
vbox.getChildren().addAll(warning, sword);
return new Scene(vbox);
}
private void cutOffHead() {
this.stage.close();
try {
start(new Stage());
start(new Stage());
} catch (Exception e) {
e.printStackTrace();
}
}

You call start(new Stage()) in a row, but it's the same method of the same object. In the beginning of start you save the parameter into this.stage field. So, the first call saves into this field the result of the first new Stage() and then later you overwrite it wth the result of the second new Stage(). Now you have 2 new stages open, but this.stage is referencing only the second one.

Related

JavaFX: Button event still executes without having to press the button for a second time

my problem is the following:
I have a JavaFX application with a Button called "bindB";
Button bindB = new Button("None");
bindB.setOnAction(event -> {
bindB.setText("...");
BindKey.bindKey(scene, bindB);
});
with the text "None". Is this button pressed, his text first changes to "..."
and by then calling the method "BindKey.bindKey();", the text will change to the
name of the key, the user is pressing on his keyboard.
This is the code of the method "BindKey.bindKey();":
public static void bindKey(Scene scene, Button bindB){
scene.setOnKeyPressed(event -> {
bindB.setText(String.valueOf(event.getCode()));
});
}
As you can see, in the args of the method we give the button "bindB", so that the method knows
what button to change the name of, aswell as the current scene.
This code does work, but the problem is, that even after the button was
pressed, and its text has already be changed, the text still changes to the name of different
keys if you press them afterwards WITHOUT having to press the button a second time.
I thought that you had to end the "setOnAction" event by calling
event.consume();
but that didnt work...
So how do I make the buttons text only change, if the button has actually been pressed a second or third time?
Otherwise, the task which the button performs is toggled by EVERY key because technically
every key is the toggle key as the task reads the name of the button to know what key is for toggling.
Full code example:
Main class:
public class Main {
// Initialize GUI
public static void main(String[] args) {
GUI gui = new GUI();
gui.run();
}
}
GUI class:
public class GUI extends Application {
public void run() {
launch();
}
#Override
public void start(Stage window) throws Exception {
// When closing Window
window.setOnCloseRequest(event -> {
exitApplication(window);
});
// GridPane
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10,10));
grid.setVgap(15);
grid.setHgap(30);
// Scene
Scene scene = new Scene(grid, 200, 200);
window.setScene(scene);
// Bind Button
Button bindB = new Button("None");
GridPane.setConstraints(bindB, 1, 1);
bindB.setOnAction(event -> {
bindB.setText("...");
BindKey.bindKey(scene, bindB);
});
// Add to Grid
grid.getChildren().addAll(bindB);
// Show Window
window.show();
}
// Provide a clean exit
private void exitApplication(Stage window){
window.close();
Platform.exit();
System.exit(0);
}
}
BindKey class:
public class BindKey {
// Changes Buttons text
public static void bindKey(Scene scene, Button bindB){
scene.setOnKeyPressed(event -> {
bindB.setText(String.valueOf(event.getCode()));
});
}
}
I am not to 100% sure if this is the problem, but I think the only thing you have to do is the following:
scene.setOnKeyPressed(event -> {
bindB.setText(String.valueOf(event.getCode()));
scene.setOnKeyPressed(null);
});
}
You just have to remove the Key-Listener from the scene after you set the text. Because otherwise it will listen for keys the entire time.

Use of Thread.sleep() in JavaFx Application

I try to make an application that shows a button. When I click the button, the scene should show some text (add a label) for a few seconds, and then the text should disappear (removing the label from the scene). But in fact when I click the button, nothing happens.
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
StackPane pane = new StackPane();
Button btn = new Button();
Label lb = new Label("Start");
pane.getChildren().addAll(btn);
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show();
btn.setOnAction(e->{
pane.getChildren().addAll(lb);
try {
Thread.sleep(300);
} catch (Exception ex) {
ex.printStackTrace();
}
pane.getChildren().removeAll(lb);
});
}
public static void main(String[] args) {
launch(args);
}
}
Use a PauseTransition:
btn.setOnAction(e->{
pane.getChildren().addAll(lb);
PauseTransition pause = new PauseTransition(Duration.seconds(0.3));
pause.setOnFinished(e -> pane.getChildren().removeAll(lb));
pause.play();
});
The reason your approach doesn't work, is that JavaFX effectively uses a single thread for rendering and handling user events. It will update the screen at a fixed interval (60 times per second in the current implementation), but for synchronization reasons has to wait for any pending events that are currently being handled to complete first. So you original code adds the label, pauses for 0.3 seconds, and then removes the label. The FX Application Thread is occupied for this whole process, so the FX framework never has an opportunity to redraw the scene while it is happening.
The bottom line here is that you should never block the FX application thread, by calling sleep() or by executing long-running operations.
Update in response to additional question in comment:
To disable all event handling, you can call setDisable(true) on the root node of the scene. So to prevent event handling while the label is shown:
btn.setOnAction(e->{
pane.getChildren().addAll(lb);
pane.setDisable(true);
PauseTransition pause = new PauseTransition(Duration.seconds(0.3));
pause.setOnFinished(e -> {
pane.getChildren().removeAll(lb));
pane.setDisable(false);
});
pause.play();
});

JavaFX scroll started and ended

I have a very costly action to do on a mouse scroll on a pane. I currently use
pane.setOnScroll({myMethod()}).
The problem is that if you scroll a lot it computes everything many times. So what I want is to do my actions only when the scroll is finished. I hoped to use setOnScrollStarted, save the starting value and setOnScrollFinished to do my actions.
But I don't know why these two methods are never called. As a test I used
pane.setOnScroll({System.out.println("proof of action"});
and it was clearly never called.
Any idea on how to call my method only at the end of the scroll?
Thanks in advance, A
From the javadoc of ScrollEvent (emphasis mine):
When the scrolling is produced by a touch gesture (such as dragging a
finger over a touch screen), it is surrounded by the SCROLL_STARTED
and SCROLL_FINISHED events. Changing number of involved touch points
during the scrolling is considered a new gesture, so the pair of
SCROLL_FINISHED and SCROLL_STARTED notifications is delivered each
time the touchCount changes. When the scrolling is caused by a mouse
wheel rotation, only a one-time SCROLL event is delivered, without the
started/finished surroundings.
A possible workaround:
Increment a counter variable every time a scroll is detected. In the listener start a new thread that waits 1 second and performs the action that you want only if the counter equals to 1 (the last scrolling) then decrements the counter.
I created a Gist, but I copy here the code:
public class ScrollablePane extends Pane {
private Integer scrollCounter = 0;
private final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollEnded = new SimpleObjectProperty<>();
public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollEndedProperty() {
return onScrollEnded;
}
public ScrollablePane() {
this.setOnScroll(e -> {
scrollCounter++;
Thread th = new Thread(() -> {
try {
Thread.sleep(1000);
if (scrollCounter == 1)
onScrollEnded.get().handle(e);
scrollCounter--;
} catch (Exception e1) {
e1.printStackTrace();
}
});
th.setDaemon(true);
th.start();
});
}
public void setOnScrollEnded(EventHandler<? super ScrollEvent> handler) {
onScrollEnded.setValue(handler);
}
}
To use it:
public class MyApplication extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
ScrollablePane pane = new ScrollablePane();
pane.setOnScrollEnded(e -> System.out.println("Scroll just has been ended"));
root.setCenter(pane);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}

How do you change the text in a button from another window (JavaFX + FXML)?

I am very new to using JavaFX and have having some trouble using JavaFX with FXML. I am creating a program with a "Setup" button that when clicked, opens a new window with the connection (to an Arduino) settings. On the menu is another button ("Connect") that connects to the board. This closes the window. I'm looking to have this change the text of the original "setup" button to "disconnect", however, I don't seem to be able to access the button from the "setup window". Whenever I click "connect" I get the following error:
Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
I read online that this is a wrapper for a null pointer exception. I assume that the "setup" button is null and that's why I can't change it, but I can't work out why.
Here is an excerpt from MainController.java:
#FXML
protected void setUpConnection(ActionEvent e) {
SetupController setupController = new SetupController();
setupController.init(this);
}
The above method gets called when the "setup" button is clicked (set in the file: setupMenu.fxml). This then opens up the separate window. Here is the code in SetupController.java that opens the window:
private void openSetupWindow() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("setupMenu.fxml"));
Parent root1 = (Parent)loader.load();
Stage stage = new Stage();
stage.setTitle("Setup Connection");
stage.setScene(new Scene(root1));
stage.show();
} catch(Exception exc) {
exc.printStackTrace();
}
}
When the connect button is clicked, the following method (in SetupController.java) is called:
private void changeButtonText(ConnectionEventType e) {
Button b = main.getSetupButton();
if(e == ConnectionEventType.CONNECT) {
b.setText("Disconnect");
}
else {
b.setText("Setup Connection...");
}
}
(main is the MainController object that was passed in to setupController.init() )
The above code is where I am getting the error.
Just to clarify, I have 2 separate fxml files, one for the main window and one for the pop up. sample.fxml(the main window) has its controller set to MainController and is set up in Main.java (below):
#Override
public void start(Stage primaryStage) throws Exception{
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
GridPane root = loader.load();
Scene scene = new Scene(root, 1200, 900);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setTitle("Nest Control");
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
Am I trying to access the button incorrectly? Is anyone able to help? Like I said, I don't have much experience with using JavaFX or FXML.
I think the answer you are looking for is to store the controllers for each window you open so you can access the variables within the controllers, however without the rest of your code would be hard to advise you, but heres an example of what i mean:
private SetupController yourController;
#Override
public void start(Stage primaryStage) throws Exception{
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
GridPane root = loader.load();
this.yourController=loader.<SetupController>getController();
Scene scene = new Scene(root, 1200, 900);
}
}
You could then pass the variable yourController to other instances in a Model-view-controller type way and access its methods.
or something like this in your case :
private void changeButtonText(ConnectionEventType e) {
Button b = this.yourController.getButton(); //a method that returns your #FXML button object in your controller
if(e == ConnectionEventType.CONNECT) {
b.setText("Disconnect");
}
else {
b.setText("Setup Connection...");
}
}
Or alternatively have a specific method within the controller that will set the text of the button without having to return the button object.
See the examples here and here
However please note the error you get seems to typically attributed to missing #FXML annotations so maybe make sure in this instance that you have annotated all the variables in any controllers also. See here for more details.

JavaFX - Dialog box displays blank on first time displayed

In my javafx application I have a dialog box which shows the results from a previous action. When first displayed the window just shows as white without the contents. After resizing the contents display, and on the subsequent times the dialog is brought up it behaves normally. I'm sure it's something simple, but it's been bugging me for days while I move to work on other parts. My initialize method and Constructor are empty and something tells me this may be the issue.
In MainApp extends Application:
called from another dialog stage controller after calling close.
public void showEDResult(List<String> path) {
try {
// Load the fxml file and create a new stage for the popup
FXMLLoader loader = new FXMLLoader(MainApp.class.getResource("view/EDResultLayout.fxml"));
VBox page = (VBox) loader.load();
Stage dialogStage = new Stage();
dialogStage.setTitle("Edit Distance Result");
//dialogStage.initModality(Modality.WINDOW_MODAL);
dialogStage.initOwner(primaryStage);
Scene scene = new Scene(page);
dialogStage.setScene(scene);
// Set reference to stage in controller
//BUG -- when first displayed results don't show up until resize window
EDResultController controller = loader.getController();
controller.setDialogStage(dialogStage);
controller.setMainApp(this);
// give controller reference to result
controller.setResult(path);
// give controller reference to scene (cursor)
// Show the dialog and wait until the user closes it
dialogStage.showAndWait();
} catch (IOException e) {
// Exception gets thrown if the fxml file could not be loaded
e.printStackTrace();
}
}
In class ResultController:
public void setResult(List<String> result) {
numStepsLabel.setText(Integer.toString(result.size()-2));
//TODO -- work on layout
String str = buildResultString(result);
pathLabel.setText(str);
}

Categories

Resources