I want to implement some kind of notification system in my application but I have trouble with the calculation of the actual position of my notification. All notifications should appear in a separate stage and each notification should be aligned among themselves and each notification is a simple VBox with two labels (title and message).
I created a little standalone application with the issue I have.
As soon as you press the button on the main stage, a VBox will be created and added to a second notification stage. As soon as a seconds notification needs to be added, this second notification should be below the first notification and so on. Therefore I need to find the height of the first notification in order to position the second notification underneath.
I know I could use a VBox instead, but in my application the notification should make a smooth animation and push the other notifications further down. I removed the whole animation and removing part of notifications so the example stays as small as possible.
The problem is that all notification boxes have the same height – but they don’t (if you modify the text and make it longer / smaller).
package whatever; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.stage.Screen; import javafx.stage.Stage; import javafx.stage.StageStyle; public class NotificationTest { private Stage notificationStage; private Pane contentPane; private static final Integer NOTIFICATION_WIDTH = 250; private Double notificationOffset = 0.0; private static final Integer SPACING_BETWEEN_NOTIFICATIONS = 20; public void start() { Stage mainStage = new Stage(); TextField textField = new TextField("Some long text for testing purpose with even more letters in oder to create at least one linebreak..."); Button button = new Button("Add Notification"); button.setOnAction(actionEvent -> { addNotification(textField.getText()); }); VBox vBox = new VBox(10); vBox.getChildren().addAll(textField, button); mainStage.setScene(new Scene(vBox, 300, 300)); mainStage.show(); } private void addNotification(String text) { if(notificationStage == null) { notificationStage = new Stage(); notificationStage.setWidth(NOTIFICATION_WIDTH); notificationStage.setHeight(Screen.getPrimary().getVisualBounds().getHeight() - 50); notificationStage.setX(Screen.getPrimary().getVisualBounds().getWidth() - 260); notificationStage.setY(50); contentPane = new Pane(); contentPane.setStyle("-fx-background-color: transparent"); notificationStage.setScene(new Scene(contentPane)); notificationStage.initStyle(StageStyle.TRANSPARENT); notificationStage.getScene().setFill(Color.TRANSPARENT); notificationStage.show(); } VBox notificationBox = new VBox(10); notificationBox.setMaxWidth(NOTIFICATION_WIDTH); notificationBox.setMinWidth(NOTIFICATION_WIDTH); notificationBox.setStyle("-fx-background-radius: 10; -fx-background-color: red"); notificationBox.getChildren().add(new Label("Title of Notification")); Label message = new Label(text); message.setWrapText(true); notificationBox.getChildren().add(message); notificationBox.setLayoutY(notificationOffset); contentPane.getChildren().add(notificationBox); // Needs to be done - otherwise the height would be 0 contentPane.layout(); System.out.println(notificationBox.getHeight()); notificationOffset += notificationBox.getHeight() + SPACING_BETWEEN_NOTIFICATIONS; } }
I used the ScenicView Tool to verify the height and it says that the height is 79, but the System.out tells me the height is 10.4. The 79 value seems to be correct, but how can I get this value in my application?
Advertisement
Answer
The short answer is use applyCss()
:
contentPane.applyCss(); contentPane.layout();
From the documentation:
If required, apply styles to this Node and its children, if any. This method does not normally need to be invoked directly but may be used in conjunction with Parent.layout() to size a Node before the next pulse
The long and better answer is to use a VBox
or a ListView
.
To add layout animation use LayoutAnimator.java. You can find more details here.
Edit: an mre of using LayoutAnimator
to animate newly added notifications:
import javafx.application.Application; import javafx.scene.*; import javafx.scene.control.*; import javafx.scene.layout.*; import javafx.stage.*; public class NotificationTest extends Application { private Stage notificationStage; private Pane contentPane; private static final int NOTIFICATION_WIDTH = 250, SPACING_BETWEEN_NOTIFICATIONS = 20; private static final String LONG_TEXT = "Some long text for testing purpose with even more letters in oder to create " + "at least one linebreak..."; private int counter = 0; @Override public void start(Stage mainStage) throws Exception { mainStage = new Stage(); TextField textField = new TextField(LONG_TEXT); Button button = new Button("Add Notification"); button.setOnAction(actionEvent -> { addNotification(textField.getText()); }); VBox vBox = new VBox(10, textField, button); mainStage.setScene(new Scene(vBox, 300, 300)); mainStage.show(); } private void addNotification(String text) { if(notificationStage == null) { notificationStage = new Stage(); notificationStage.setWidth(NOTIFICATION_WIDTH); notificationStage.setX(Screen.getPrimary().getVisualBounds().getWidth() - 260); notificationStage.setY(50); contentPane = new VBox(SPACING_BETWEEN_NOTIFICATIONS); contentPane.setStyle("-fx-background-color: transparent"); notificationStage.setScene(new Scene(contentPane)); notificationStage.initStyle(StageStyle.TRANSPARENT); //animate using LayoutAnimator https://gist.github.com/jewelsea/5683558 LayoutAnimator ly = new LayoutAnimator(); ly.observe(contentPane.getChildren()); notificationStage.show(); } VBox notificationBox = new VBox(10); notificationBox.setMaxWidth(NOTIFICATION_WIDTH); notificationBox.setMinWidth(NOTIFICATION_WIDTH); notificationBox.setStyle("-fx-border-color: black"); notificationBox.getChildren().add(new Label("Title of Notification")); Label message = new Label(counter++ + ": " +text); message.setWrapText(true); notificationBox.getChildren().add(message); contentPane.getChildren().add(0, notificationBox); } public static void main(String[] args) { launch(null); } }