Why is there a big performance difference between switching to Alert and Stage?

I am trying to implement some animation in my project. When a user uses the application, sometimes he or she receives “ Alert ” dialogs for confirmation or Stage dialogs for data entry (and click the save button). After the event, I usually showed another Alert with "Success" (if it was successful).

Now, to eliminate a bunch of unnecessary “useless” windows / screens / pop-ups, I wanted to minimize Alert or Stage in the lower left corner of the screen, where the message “Success” will appear for about 3 seconds in the status bar. I implemented this successfully, but I noticed a huge performance difference between animation on Alert and animation on Stage .

Alert seems very lively, and the Stage is actually very volatile (even on a good PC). I read about caching and looked for related questions, but without much effect or solutions. I tried to make a JavaFX (Maven) example (based on some other examples I found) that you can find below.

You will see when you click the “Show alert” button and click “Yes” in the Alert window, Alert will go smoothly to the lower left corner of the screen. When you click the Show node button and click the Close button on the newly opened stage, the animation will be much more choppy than Alert .

Is there anything that can be done to smooth out the animation of the scene? I also tried to make the top AnchorPane invisible to see if there was any performance improvement, but it was exactly the same.

Scene.fxml:

 <?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import java.util.*?> <?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.40" fx:controller="proj.mavenproject1.FXMLController"> <children> <Button fx:id="button" layoutX="52.0" layoutY="90.0" onAction="#handleButtonAction" text="Show Alert" /> <Label fx:id="label" layoutX="126" layoutY="120" minHeight="16" minWidth="69" /> <Button fx:id="button1" layoutX="217.0" layoutY="90.0" onAction="#handleButtonAction2" text="Show Node" /> </children> </AnchorPane> 

testNode.fxml:

 <?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import java.util.*?> <?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.40" fx:controller="proj.mavenproject1.TestNodeController"> <children> <Button layoutX="262.0" layoutY="188.0" mnemonicParsing="false" onAction="#handleButtonAction" text="Close node" /> </children> </AnchorPane> 

FXMLController.java:

 package proj.mavenproject1; import java.io.IOException; import java.net.URL; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Label; public class FXMLController implements Initializable { @FXML private Label label; @FXML private void handleButtonAction(ActionEvent event) { Utilities.showYesNo("test", "this to test the closing animation of an alert", true); System.out.println("You clicked me!"); label.setText("Hello World!"); } @FXML private void handleButtonAction2(ActionEvent event) { try { URL url = getClass().getResource("/fxml/testNode.fxml"); Utilities.showDialog(url); } catch (IOException ex) { Logger.getLogger(FXMLController.class.getName()).log(Level.SEVERE, null, ex); } } @Override public void initialize(URL url, ResourceBundle rb) { // TODO } } 

TestNodeController.java:

 package proj.mavenproject1; import java.net.URL; import java.util.ResourceBundle; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; public class TestNodeController implements Initializable { @FXML private void handleButtonAction(ActionEvent event) { Utilities.closeStage(event, true); } /** * Initializes the controller class. */ @Override public void initialize(URL url, ResourceBundle rb) { // TODO } } 

Utilities.java:

 package proj.mavenproject1; import java.io.IOException; import java.net.URL; import java.util.Optional; import java.util.ResourceBundle; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.beans.value.WritableValue; import javafx.event.ActionEvent; import javafx.event.Event; import javafx.event.EventHandler; import javafx.fxml.FXMLLoader; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.CacheHint; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; import javafx.scene.control.DialogEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.VBoxBuilder; import javafx.stage.Modality; import javafx.stage.Screen; import javafx.stage.Stage; import javafx.stage.StageStyle; import javafx.stage.WindowEvent; import javafx.util.Duration; public class Utilities { public static boolean showYesNo(String title, String content, boolean animation) { Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setTitle(title); alert.setHeaderText(null); alert.setContentText(content); alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO); alert.setOnCloseRequest((DialogEvent we) -> { if (animation) { minimizeAlert(alert, animation); we.consume(); } }); Optional<ButtonType> result = alert.showAndWait(); return result.get() == ButtonType.YES; } public static void showDialog(URL url) throws IOException { final Stage myDialog = new Stage(); myDialog.initStyle(StageStyle.UTILITY); myDialog.initModality(Modality.APPLICATION_MODAL); Node n = (Node) FXMLLoader.load(url); Scene myDialogScene = new Scene(VBoxBuilder.create().children(n).alignment(Pos.CENTER).padding(new Insets(0)).build()); myDialog.setScene(myDialogScene); myDialog.showAndWait(); } private static void minimizeNode(Scene scene, boolean animation) { final int MILLIS = 750; //Node src = (Node) event.getSource(); AnchorPane rootPane = (AnchorPane) scene.lookup("#rootPane"); final Stage stage = (Stage) scene.getWindow(); //animation = false; //TODO: check if this thing slows down the program, seems like context menu slows down because of it if (animation) { WritableValue<Double> writableHeight = new WritableValue<Double>() { @Override public Double getValue() { return stage.getHeight(); } @Override public void setValue(Double value) { stage.setHeight(value); } }; WritableValue<Double> writableWidth = new WritableValue<Double>() { @Override public Double getValue() { return stage.getWidth(); } @Override public void setValue(Double value) { stage.setWidth(value); } }; WritableValue<Double> writableOpacity = new WritableValue<Double>() { @Override public Double getValue() { return stage.getOpacity(); } @Override public void setValue(Double value) { stage.setOpacity(value); } }; EventHandler onFinished = new EventHandler<ActionEvent>() { public void handle(ActionEvent t) { stage.close(); } }; double currentX = stage.getX(); double currentY = stage.getY(); DoubleProperty x = new SimpleDoubleProperty(currentX); DoubleProperty y = new SimpleDoubleProperty(currentY); x.addListener(new ChangeListener<Number>() { @Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { stage.setX(newValue.doubleValue()); } }); y.addListener(new ChangeListener<Number>() { @Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { stage.setY(newValue.doubleValue()); } }); KeyFrame keyFrameMove = new KeyFrame(Duration.millis(MILLIS), onFinished, new KeyValue(x, 0d), new KeyValue(y, Screen.getPrimary().getBounds().getMaxY() - 25)); KeyFrame keyFrameScale = new KeyFrame(Duration.millis(MILLIS), new KeyValue(writableWidth, 0d), new KeyValue(writableHeight, 0d)); KeyFrame keyFrameOpacity = new KeyFrame(Duration.millis(MILLIS), new KeyValue(writableOpacity, 0d)); Timeline timeline = new Timeline(keyFrameMove, keyFrameScale, keyFrameOpacity); if (rootPane != null) { rootPane.setVisible(false); //rootPane.getChildren().clear(); } timeline.play(); } else { stage.close(); } } public static void minimizeAlert(Alert alert, boolean animation) { final int MILLIS = 750; if (animation) { WritableValue<Double> writableHeight = new WritableValue<Double>() { @Override public Double getValue() { return alert.getHeight(); } @Override public void setValue(Double value) { alert.setHeight(value); } }; WritableValue<Double> writableWidth = new WritableValue<Double>() { @Override public Double getValue() { return alert.getWidth(); } @Override public void setValue(Double value) { alert.setWidth(value); } }; EventHandler onFinished = new EventHandler<ActionEvent>() { public void handle(ActionEvent t) { alert.setOnCloseRequest(null); alert.close(); } }; double currentX = alert.getX(); double currentY = alert.getY(); DoubleProperty x = new SimpleDoubleProperty(currentX); DoubleProperty y = new SimpleDoubleProperty(currentY); x.addListener((obs, oldX, newX) -> alert.setX(newX.doubleValue())); y.addListener((obs, oldY, newY) -> alert.setY(newY.doubleValue())); KeyFrame keyFrameMove = new KeyFrame(Duration.millis(MILLIS), onFinished, new KeyValue(x, 0d), new KeyValue(y, Screen.getPrimary().getBounds().getMaxY() - 25)); KeyFrame keyFrameScale = new KeyFrame(Duration.millis(MILLIS), new KeyValue(writableWidth, 0d), new KeyValue(writableHeight, 0d)); Timeline timeline = new Timeline(keyFrameMove, keyFrameScale); timeline.play(); } else { alert.close(); } } public static void closeStage(Event event, boolean animation) { Node src = (Node) event.getSource(); src.setCache(true); src.setCacheHint(CacheHint.SPEED); minimizeNode(src.getScene(), animation); } } 
+6
source share
1 answer

The only difference is the keyFrameOpacity animation in the case of the scene. If you delete it, the scene animation will be as smooth as the warning dialog. It is interesting, however, that the animation lags only when using opacity changes with scaling. Setting stage.setScene(null) to timeline.play() also makes the animation smooth, but it doesn't look very good.
I am not very familiar with JavaFx internal structures and impulse mechanics, but I found 2 solutions. One of them is to handle scaling and opacity changes in different phases:

  double currentWidth = stage.getWidth(); double currentHeight = stage.getHeight(); WritableValue<Double> writableValue = new WritableValue<Double>() { private Double internal = 1.0; private boolean flag = true; @Override public Double getValue() { return internal; } @Override public void setValue(Double value) { if(flag) { stage.setWidth(currentWidth * value); stage.setHeight(currentHeight * value); } else { stage.setOpacity(value); } internal = value; flag = !flag; } }; KeyFrame keyFrameMove = new KeyFrame(Duration.millis(MILLIS), onFinished, new KeyValue(x, 0d), new KeyValue(y, Screen.getPrimary().getBounds().getMaxY() - 25)); KeyFrame keyFrame = new KeyFrame(Duration.millis(MILLIS), new KeyValue(writableValue, 0d)); Timeline timeline = new Timeline(keyFrame, keyFrameMove); timeline.play(); 

The second is to use a separate thread to update all values. Something like that:

  double currentX = stage.getX(); double currentY = stage.getY(); double currentWidth = stage.getWidth(); double currentHeight = stage.getHeight(); new Thread(()->{ long initial = System.currentTimeMillis(); while(true) { long current = System.currentTimeMillis(); long delta = current - initial; if(delta > MILLIS) { break; } double prc = 1 - delta/(double)MILLIS; Platform.runLater(()->{ stage.setX(currentX*prc); stage.setY(currentY*prc+(1-prc)*(Screen.getPrimary().getBounds().getMaxY() - 25)); stage.setWidth(currentWidth*prc); stage.setHeight(currentHeight*prc); stage.setOpacity(prc); }); try { Thread.sleep(1000/60); } catch (InterruptedException e) { e.printStackTrace(); } } Platform.runLater(()-> stage.close()); }).start(); 
+1
source

All Articles