Animate adding components to a pane - java

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?

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

Related

How to create textAreas in javaFX

I'm working on a text box that will give information about a subject in it, but I want 1 long string to have all of the information stored in it. I would like for this string to be returned to the "next line" in that box, I'm essentially trying to create a text box in JavaFX
So I did some more digging, and it turns out, the thing I'm looking for is called a "Text Area"
package com.jenkov.javafx.controls;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TextAreaExperiments extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("TextArea Experiment 1");
TextArea textArea = new TextArea();
VBox vbox = new VBox(textArea);
Scene scene = new Scene(vbox, 200, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
source received from: http://tutorials.jenkov.com/javafx/textarea.html
If you're basically looking to create a TextField try following:
Label label1 = new Label("Name:");
TextField textField = new TextField ();
HBox hb = new HBox();
hb.getChildren().addAll(label1, textField);
hb.setSpacing(10);

Switching from a horizontal to a vertical layout with three radio buttons

Using JavaFX 8, I would like to have a responsive layout with three radio buttons that changes from a horizontal layout to a vertical layout as soon as there is not enough horizontal space to put all radio buttons next to each other.
This means the default layout should be like this:
(x) Radio button 1 ( ) Radio button 2 ( ) Radio button 3
And as soon as there is not enough horizontal space to put all three buttons on a single line, I want the layout to change to this:
(x) Radio button 1
( ) Radio button 2
( ) Radio button 3
Note that I want to avoid intermediate states like:
(x) Radio button 1 ( ) Radio button 2
( ) Radio button 3
I have tried to achieve the desired behaviour with a flow pane that changes its orientation dynamically but I encountered small layout glitches when there was another control (e.g. a text area) positioned underneath the flow pane. Here's the code I have used:
import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ResponsiveLayout extends Application {
RadioButton radioButton1 = new RadioButton("Radio button 1");
RadioButton radioButton2 = new RadioButton("Radio button 2");
RadioButton radioButton3 = new RadioButton("Radio button 3");
FlowPane flowPane = new FlowPane(radioButton1, radioButton2, radioButton3);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
flowPane.setHgap(10);
flowPane.setPrefWrapLength(60);
flowPane.widthProperty().addListener((obs, oldWidth, newWidth) -> updateOrientation(newWidth));
VBox container = new VBox(flowPane, new TextArea());
Scene scene = new Scene(container, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private void updateOrientation(Number paneWidth) {
double childrenPadding = flowPane.getHgap() * (flowPane.getChildren().size() - 1);
int extraPadding = 5;
double childrenWidth = radioButton1.getWidth() +
radioButton2.getWidth() +
radioButton3.getWidth() +
childrenPadding +
extraPadding;
if (paneWidth.doubleValue() < childrenWidth) {
flowPane.setOrientation(Orientation.VERTICAL);
} else {
flowPane.setOrientation(Orientation.HORIZONTAL);
}
}
}
When I run this application and carefully change the width of the scene, I can see the following layout glitch right after the orientation of the flow pane changed from vertical to horizontal:
As you can see, there is an unwanted empty space between the radio buttons and the text area. It vanishes when I increase the scene width further or if the window loses focus. In a bigger application with more controls and containers around the flow pane, I can see even more layout glitches so I'm wondering if there is a better approach to achieving the responsiveness I need.
What could I do to improve the flow pane behavior? Or is there a more suitable layout pane that I could use for this?
I'd appreciate any support. Thanks.
You could toggle between VBox and HBox. See if this meets your requirements.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ResponsiveLayout extends Application
{
RadioButton radioButton1 = new RadioButton("Radio button 1");
RadioButton radioButton2 = new RadioButton("Radio button 2");
RadioButton radioButton3 = new RadioButton("Radio button 3");
HBox hBox = new HBox(radioButton1, radioButton2, radioButton3);
StackPane stackPane = new StackPane(hBox);
VBox vBox = new VBox();
boolean isHBox = true;
double controlWidth = -1;
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage)
{
hBox.setMaxSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
stackPane.setAlignment(Pos.CENTER_LEFT);
VBox container = new VBox(stackPane, new TextArea());
Scene scene = new Scene(container, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
container.widthProperty().addListener((obs, oldWidth, newWidth) -> updateOrientation(newWidth));
}
private void updateOrientation(Number paneWidth)
{
if (isHBox) {
if (hBox.getWidth() == stackPane.getWidth()) {
controlWidth = hBox.getWidth();
vBox.getChildren().addAll(hBox.getChildren());
stackPane.getChildren().clear();
stackPane.getChildren().add(vBox);
isHBox = false;
}
}
else {
if (controlWidth <= stackPane.getWidth()) {
hBox.getChildren().addAll(vBox.getChildren());
stackPane.getChildren().clear();
stackPane.getChildren().add(hBox);
isHBox = true;
}
}
}
}

JavaFX GridPane Object Alignment

I am trying to use JavaFX to create a scene with the program's title positioned at the top-center, and buttons in a vertical line along the left side of the scene. However, both of these elements are displayed clustered up in the top-right of the scene, instead of where I want them to be.
How can I get these elements to be displayed where I want them to?
Here is how I try to set the program title's position:
grid.add(gameTitle, 0, 0);
GridPane.setHalignment(gameTitle, HPos.CENTER);
GridPane.setValignment(gameTitle, VPos.TOP);
I try to set the VBox object similarly:
grid.getChildren().add(buttonBox);
GridPane.setHalignment(buttonBox, HPos.LEFT);
GridPane.setValignment(buttonBox, VPos.CENTER);
This is what is displayed:
My entire MainMenu class. (This class is called in my Main class to construct the scene):
package scenes;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.application.Platform;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class MainMenu {
public Pane getMainMenuPane() {
// Create the scene grid
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
// Set the game title to the top center
Text gameTitle = new Text("Bandit King");
Font titleFont = new Font(75);
gameTitle.setFont(titleFont);
//
grid.add(gameTitle, 0, 0);
GridPane.setHalignment(gameTitle, HPos.CENTER);
GridPane.setValignment(gameTitle, VPos.TOP);
// Create Button objects and put in VBox
Button[] buttArr = makeButtons();
VBox buttonBox = new VBox();
buttonBox.getChildren().addAll(buttArr);
buttonBox.setSpacing(10);
// add Button VBox to GridPane
grid.getChildren().add(buttonBox);
GridPane.setHalignment(buttonBox, HPos.LEFT);
GridPane.setValignment(buttonBox, VPos.CENTER);
return (Pane) grid;
}
private Button[] makeButtons() {
// Create buttons
Button start = new Button("Start a New Game");
Button load = new Button("Load a Saved Game");
Button exit = new Button("Exit the Game");
// set Button actions
start.setOnAction( a -> {
System.out.println("WIP- start game.");
});
load.setOnAction( a -> {
System.out.println("WIP- load game");
});
exit.setOnAction( a -> {
Platform.exit();
System.exit(0);
});
// return Button[] array
Button[] buttArr = {start, load, exit};
return buttArr;
}
}
My Main class (Displays the scene):
package central;
import javafx.stage.Stage;
import scenes.*;
import controllers.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
public class Main extends Application {
// Get scene panes
private static Pane mainMenu = new MainMenu().getMainMenuPane();
// Create SceneController object.
private static Scene scene = new Scene(mainMenu, 1600, 900);
public static SceneController SceneControl = new SceneController(scene);
#Override
public void start(Stage stage) {
stage.setTitle("Bandit King");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The default cell you add the children of a GridPane to is (0, 0) which is what you do in this line:
grid.getChildren().add(buttonBox);
you need to change this to
grid.add(buttonBox, 0, 1);
to set the row index to 1. (There are alternatives to assigning the row index this way, but this is the most convenient option in this case.)
This won't result in the first column taking the full width of the GridPane though. If you also want the first column to take all the width available, you need to specify this by adding ColumnConstraints:
ColumnConstraints constraints = new ColumnConstraints();
constraints.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().add(constraints);
As far as what I noticed, you added all the nodes in a column and set there positions, but you did not specify how much the column needs to be stretched. GridPane column will not stretch automatically by itself unless specified.
You can debug your program, by enabling the gridLinesVisible of GridPane property to true.
grid.setGridLinesVisible(true);
You need to specify the columnConstraints, to let the GridPane column stretch to the available width.
ColumnConstraints constraint = new ColumnConstraints();
constraint.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().add(constraint);

Put a Scrollbar into to Pane JavaFX

I'm just working on a little project and started to get in JavaFX.
I have a problem and I don't know any solutions..
So I managed it to bring a Scrollbar(incl Listener) into the root.
But the listener doesn't work.
I want to add many comboboxes one below the other and when I reached the scenesize(LayoutX) it should be possible to scrolldown.
How can I solve my problem?
package application;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Pane;
import javafx.scene.text.*;
import javafx.stage.Stage;
import application.XMLActions;;
public class Main extends Application {
/**
* Globale Variablen
*/
int abstandszaehler = 0;
private Pane root = new Pane();
private ScrollBar sB = new ScrollBar();
private XMLActions xA = new XMLActions();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("XML-Zeilenersteller");
primaryStage.setResizable(false);
/**
* Elemente für die Root
*/
//Buttons
Button newXMLLine = new Button();
newXMLLine.setText("Einfügen neuer XML-Zeile");
newXMLLine.setLayoutX(735);
newXMLLine.setLayoutY(80);
newXMLLine.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
setComboBox();
}
});
Button newXMLDocument = new Button();
newXMLDocument.setText("Erstelle XML-Dokument");
newXMLDocument.setLayoutX(735);
newXMLDocument.setLayoutY(550);
newXMLDocument.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Test2");
}
});
//Überschrift
Text header = new Text(105, 50, "XML Lines Creator");
header.setFont(new Font(30)); header.setStyle("-fx-underline: true;");
//Scrollbar
sB.setLayoutX(715);
sB.setLayoutY(80);
sB.setMin(0);
sB.setOrientation(Orientation.VERTICAL);
sB.setPrefHeight(500);
sB.setMax(360);
sB.setUnitIncrement(30);
sB.setBlockIncrement(35);
sB.valueProperty().addListener((ObservableValue<? extends Number> ov,
Number old_val, Number new_val) -> {
System.out.println(-new_val.doubleValue());
});
/**
* Hauptseite als Root
* Rootbearbeitungen
*/
root.setStyle("-fx-background-color: lightsteelblue");
root.getChildren().addAll(sB,newXMLDocument,header,newXMLLine );
//Scene setzen
Scene mainScene = new Scene(root, 900, 600);
primaryStage.setScene(mainScene);
primaryStage.show();
}
public void setComboBox(){
ComboBox cB = new ComboBox(xA.getList());
root.getChildren().add(cB);
cB.setLayoutX(80);
cB.setLayoutY(80 + abstandszaehler);
abstandszaehler = abstandszaehler + 30;
}
}
EDIT 1:
I got a little progress with that code in the listener:
root.setLayoutY(-new_val.doubleValue());
Replace the Pane with a ScrollPane. On the ScrollPane you can define the policy for your scrollbar.
If you define it like this, it will behave like a Pane:
ScrollPane sp = new ScrollPane();
sp.setHbarPolicy(ScrollBarPolicy.NEVER);
sp.setVbarPolicy(ScrollBarPolicy.NEVER);
Take a look at this article by Oracle.
First, if you wish to layout controls vertically consider using VBox. This VBox should then be enclosed by a ScrollPane.
If you then set the VBox's prefHeight to Control.USE_COMPUTED_SIZE and maxHeight to Double.POSITIVE_INFINITY the VBox should resize to fit it's content without limit, and the enclosing ScrollPane should show and hide scrollbars as necessary.

JavaFX TilePane setPrefColumn/Row methods not working?

I'm trying to make a Suduko board in JavaFX. I heard TilePane is especially good for this because the entire idea behind TilePane is that each 'Tile' is of uniform size. Great, that's exactly how a Suduko board, chess board, checkers, Tic Tac Toe, Battleship, etc. Sounds like TilePane is the must have pane for any kind of board game app.
Or is it?
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.image.Image;
import javafx.scene.layout.TilePane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SudukoSolver extends Application
{
Stage window;
Scene scene;
private final int TEXTFIELD_WIDTH = 32;
private final int TEXTFIELD_HEIGHT = 32;
#Override public void start(Stage window) throws Exception
{
this.window = window;
window.setTitle("Suduko Solver");
window.setOnCloseRequest(e -> closeProgram());
// Does setting this to false defeat the purpose of TilePane?
window.setResizable(false);
VBox root = new VBox();
//root.setAlignment(Pos.CENTER);
TilePane tiles = new TilePane();
tiles.setAlignment(Pos.CENTER);
// Does not appear to do anything.
tiles.setPrefColumns(9);
tiles.setPrefRows(9);
// Add all the tiles to the Pane.
root.getChildren().add(tiles);
for (int i = 0; i < 81; i++)
{
TextField textBox = new TextField();
textBox.setMinHeight(TEXTFIELD_HEIGHT);
textBox.setMaxHeight(TEXTFIELD_HEIGHT);
textBox.setMinWidth(TEXTFIELD_WIDTH);
textBox.setMaxWidth(TEXTFIELD_WIDTH);
textBox.setTextFormatter(new TextFormatter<String>((Change change) ->
{
String newText = change.getControlNewText();
if (newText.length() > 1)
{
return null ;
}
else if (newText.matches("[^1-9]"))
{
return null;
}
else
{
return change ;
}
}));
tiles.getChildren().add(textBox);
}
scene = new Scene(root, 600, 750);
window.setScene(scene);
window.show();
}
/**
* This method is called when the user wishes to close the program.
*/
private void closeProgram()
{
Platform.exit();
}
public static void main(String[] args)
{
launch(args);
}
}
Notice how clearly this is not a 9x9 grid.
Any help would be greatly appreciated. Thank you!
In your code the width of your TilePane is determided by the parent VBox rather than by the prefColumns property of the TilePane.
From the javadoc of prefColumns:
This value is used only to compute the preferred size of the tilepane and may not reflect the actual number of rows columns, which may change if the tilepane is resized to something other than its preferred height width.
(Some errors in the doc fixed by me.)
You need to use a parent that doesn't resize the TilePane. (VBox resizes it's children by default.) Use VBox.setFillWidth to change this behaviour:
root.setFillWidth(false);

Categories

Resources