Use of Thread.sleep() in JavaFx Application - java

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

Related

JavaFx closing Stages unpredictable behaviour

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.

JavaFX - How to change Background Color for a specific time? [duplicate]

I show here an image of my welcome scene.
The current behavior of the Create New Project button is shown here:
Stage stage = (Stage)((Node)event.getSource()).getScene().getWindow();
stage.hide();
Parent root = FXMLLoader.load(getClass().getResource("/view/scene/configure/NewProjectConfigureScene.fxml"));
Scene scene = new Scene(root);
stage.setTitle("Configure New Project Settings");
stage.setScene(scene);
stage.show();
To explain it in words: the button is pressed -> I get the stage -> hide the current scene -> switch the stage to have a new scene -> re-display the stage. This behavior is working.
I am new to GUI programming so please bear with me as I explain what I need.
I am now trying to add a small feature where when the Create New Project button is pressed, a loading message appears in the current scene, I force the GUI to wait for a few seconds (so that the user has time to see this "loading" message) before continuing onto the next scene. The loading message would look something like this.
I really want to implement this feature because a loading message followed by a wait could be more intuitive and consequently improve my users experience when using the program.
My initial attempt was to do the following:
statusText.setText("Please wait..."); // statusText is the "loading message"
Stage stage = (Stage)((Node)event.getSource()).getScene().getWindow();
try {
Thread.sleep(2000);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
stage.hide();
Parent root = FXMLLoader.load(getClass().getResource("/view/scene/configure/NewProjectConfigureScene.fxml"));
Scene scene = new Scene(root);
stage.setTitle("Configure New Project Settings");
stage.setScene(scene);
stage.show();
I just read here that you can never sleep the UI thread because it will freeze the entire application. So I've been trying the approach mentioned in the first answer from the above link, which says to use javafx.concurrent.Task, however I have been trying for a long time to no avail.
How do I update the UI, forcibly wait for a few seconds, and then display a new scene?
Instead of sleeping the thread, use a PauseTransition and load your new scene after the pause has finished.
createNewProject.setOnAction(event -> {
statusText.setText("Please wait...");
PauseTransition pause = new PauseTransition(
Duration.seconds(1),
);
pause.setOnFinished(event -> {
Parent root = FXMLLoader.load(
getClass().getResource(
"/view/scene/configure/NewProjectConfigureScene.fxml"
)
);
Scene scene = new Scene(root);
stage.setTitle("Configure New Project Settings");
stage.setScene(scene);
stage.sizeToScene();
});
pause.play();
});
The above code assumes you have just a single stage, so you resize your "Welcome" stage to become the "Configure New Project Settings" stage.
You should try adding in ControlsFX this will help.
You can do the following:
Service<T> service = new Service<T>(){
#Override
protected Task<T> createTask() {
return new Task<T>() {
#Override
protected T call() throws Exception {
//Do your processing here. }
};
}
};
service.setOnSucceeded(event -> {//do your processing});
service.setOnFailed(event -> {//do your processing});
ProgressDialog pd = new ProgressDialog(service);
pd.setContentText("Please wait while the window loads...");
pd.initModality(Modality.WINDOW_MODAL);
pd.initOwner(stage);
service.start();
This will put your code on the background thread. ControlsFX dialogs start and stop with the service.

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 to pause javafx class

I am building an alarm and it consists of two parts
an animated button created in javafx class and the engine which is created normally
what I need is whenever user press the animated button that closes the button and fire up the engine then after the engine is closed there will be some time then animated button appears again and so on
so I used ::
notify_me.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
new engine();
Platform.exit();
}
});
and in order to repeat this process I used
Timer t = new Timer(0,new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
while(true){
javafx.launch(javafx.class);
//some extra code goes here including sleep for
//some time and check for engine window state
}
}
});
t.start();
but I am facing two problems:
some extra code isn`t implemented until platform is exited,
launch() cannot be called more than once
so how can I achieve that without using threads ?? thanks
You probably won't get around using Threads. I'd recommend not shutting down the fx application thread however. Just close all windows and show (some of) them again after the delay:
#Override
public void start(Stage primaryStage) {
Button btn = new Button("Hide me 5 sec");
// prevent automatic exit of application when last window is closed
Platform.setImplicitExit(false);
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
// timer should be a daemon (-> not prevent jvm shutdown)
Timer timer = new Timer(true);
btn.setOnAction((ActionEvent event) -> {
timer.schedule(new TimerTask() {
#Override
public void run() {
// make window reappear (needs to happen on the application thread)
Platform.runLater(primaryStage::show);
}
}, 5000l);
// hide window
primaryStage.close();
});
// allow exiting the application by clicking the X
primaryStage.setOnCloseRequest(evt -> Platform.exit());
primaryStage.show();
}

Progress Indicator won't display until main code stops

I've been struggling for days over a stupid issue, and I need your help. I'm simply trying to display a indeterminate process indicator like the one below, within a separate Stage, while my main code performs a loop. If a certain condition is met while looping, the progress stage should close and the main code continues.
Right now I'm able to open a new stage prior to the main code starting the loop, but the progress indicator won't display in the stage yet. But once the condition is met, then the indicator suddenly appears in the stage and rotates. BUT as soon as the main code begins running again the indicator freezes, yet remains visible.
I'll briefly explain what each code below does. How do I make it so that the loading stage appears initially WITH the indicator visible, and then that stage CLOSES when the showMessage() method is called? Basically I want to show the user that background looping is happening, until the main code reaches showMessage().
Main.java
I won't post it, as it only creates the FIRST GUI. It uses menu.fxml as the resource in FXMLLoader ... and menu.fxml uses Controller.java as controller.
Controller.java
This code changes the scene in the Main GUI to have a new layout, which asks the user to click one of the visible buttons. It then checks if the source button is Button1... and if it is then create/show a new stage which uses sample.fxml as the resource. It then runs the final file, Loop.java
public class Controller implements Initializable {
public Stage loadingStage;
#FXML
ProgressIndicator progressIndicator;
public void handlerButtonClick() throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("field.fxml"));
Parent rootOne = loader.load();
((Controller) loader.getController()).setPrimaryStage(primaryStage);
((Controller) loader.getController()).setPrimaryScene(scene);
sceneField = new Scene(rootOne, 420, 510);
primaryStage.setScene(sceneField);
primaryStage.setTitle("Import Data");
primaryStage.show();
}
public void fieldOption(ActionEvent e) throws Exception {
source = e.getSource();
if (source == Button1) {
Loop loopObj = new Main();
String[] args = {};
//Create new stage to contain the Progress Indicator
FXMLLoader loader2 = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root2 = loader2.load();
Controller2 controller = loader2.getController();
loadingStage = new Stage();
loadingStage.setTitle("Loading Stage");
Scene scene = new Scene(root2, 200, 200);
loadingStage.setScene(scene);
loadingStage.show();
//Runs the looper
loopObj.start(stage);
}
}
Sample.fxml
This code creates the Progress Indicator, and uses the controller Controller2
<AnchorPane prefHeight="200.0" prefWidth="200.0" xmlns:fx="http://javafx.com/fxml/1"
xmlns="http://javafx.com/javafx/2.2" fx:controller="Controller2">
<children>
<ProgressIndicator fx:id="progressIndicator" layoutX="78.0" layoutY="55.0" progress="-1.0"/>
</children>
</AnchorPane>
Controller2.java
This code implements Initializable in order to make the Progress Indicator visible:
public class Controller2 implements Initializable {
#Override
public void initialize(URL url, ResourceBundle rb) {
ProgressIndicator progressIndicator = new ProgressIndicator();
progressIndicator.setVisible(true);
}
}
Loop.java
This is the main code that performs the loop. The loading stage should display the Progress Indicator until Loop.java calls its showMessage() function, at which point the loading stage closes. I know that it seems that one of these methods aren't necessary, but its only because they're stripped for demo purposes.
public class Loop extends Application {
#Override
public void start(Stage ignored) throws Exception {
Platform.setImplicitExit(false);
for (int i = 0; i <= 100; i++) {
if (i == 75){
saveAttachment("Hello");
}
}
}
public static void saveAttachment(String subject) throws IOException, MessagingException {
showMessage(subject);
}
private static void showMessage(String subject) throws IOException, MessagingException {
// Close the loading stage here.
}
}
Notice how it uses public void start(Stage ignored). Main.java uses public void start(Stage primaryStage). Perhaps this is bad.
I'm so frustrated please help!
UPDATE
I've applied Brian's suggestions, and within Controller.java have added a new task like so:
Stage loadingStage = new Stage();
//create task object
Task<Void> task = new Task<Void>(){
#Override
protected Void call() throws Exception{
System.out.println("Background task started...");
FXMLLoader loader2 = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root2 = loader2.load();
Controller2 controller = loader2.getController();
loadingStage.setTitle("Hello World");
Scene scene = new Scene(root2, 450, 250);
System.out.println("HERE6");
loadingStage.setScene(scene);
System.out.println("HERE7");
loadingStage.show();
System.out.println("HERE8");
return null;
}
};
Thread th = new Thread(task);
th.setDaemon(true);
System.out.println("Starting background task...");
th.start();
The problem now is that it doesn't reach the printout line saying "HERE7". It successfully enters the task and continues the main code, but the task doesn't get beyond where it prints "HERE6" therefore no new stage is opened. What gives?
In Ctlr2 you have ProgressIndicator progressIndicator = new ProgressIndicator(); but if it's in the FXML file you don't create a new one. Just do #FXML ProgressIndicator progressIndicator; like you have in the first Ctlr for some reason??
But this won't solve your problem, you're running everything on the JavaFX Application thread (GUI thread). What is probably happening (I didn't really read the code) is that you're doing both things on the same thread and the progress indicator has to wait for main to finish and then it can update the GUI. You put tags for task and concurrency, that's what you need.
Check here and do a search for more examples.
https://stackoverflow.com/a/22847734/2855515

Categories

Resources