JavaFX: Bind multiple Charts to single data property - data-binding

In an attempt to use multiple views with one single backing model instance I tried to bind several charts to the same data property. But strangely only the last bound chart does show the data, while the first appears to be totally empty, although getData() on the first chart does yield the expected results, even after an update of the underlying property's content.
I wrote a short example to illustrate my problem hoping that someone may reveal my mistake to me and show me how to do it properly.
package javafx.databinding;
import java.util.ArrayList;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.PieChart.Data;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class TestDataBinding extends Application
{
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception
{
Property<ObservableList<Data>> sourceData =
new SimpleListProperty<Data>(FXCollections.observableList(new ArrayList<Data>()));
sourceData.getValue().add(new Data("first", 1));
sourceData.getValue().add(new Data("second", 2));
PieChart firstChart = new PieChart();
PieChart secondChart = new PieChart();
firstChart.dataProperty().bind(sourceData);
secondChart.dataProperty().bind(sourceData);
HBox layout = new HBox();
layout.getChildren().addAll(firstChart, secondChart);
Scene scene = new Scene(layout, 500d, 200d);
primaryStage.setScene(scene);
primaryStage.show();
System.out.println(firstChart.getData());
System.out.println(secondChart.getData());
Platform.runLater(new Task<Void>()
{
#Override
protected Void call() throws Exception
{
Thread.sleep(5000);
sourceData.getValue().get(0).setPieValue(5);
sourceData.getValue().get(1).setPieValue(3);
System.out.println(firstChart.getData());
System.out.println(secondChart.getData());
return null;
}
});
}
}
As excepted the logging results in
[Data[first,1.0], Data[second,2.0]]
[Data[first,1.0], Data[second,2.0]]
[Data[first,5.0], Data[second,3.0]]
[Data[first,5.0], Data[second,3.0]]
but the charts look like this

This is indeed very badly documented by JavaFX...
The PieChart.Data class is a mixture of data and view. For example the legends are stored as Text Nodes inside the data class. As you may know, a Node can only be contained in a single Parent element.
The following modified example works fine:
#Override
public void start(Stage primaryStage) throws Exception {
IntegerProperty first = new SimpleIntegerProperty(1);
IntegerProperty second = new SimpleIntegerProperty(1);
Data d11 = new Data("first", 0);
d11.pieValueProperty().bind(first);
Data d12 = new Data("first", 0);
d12.pieValueProperty().bind(second);
ObservableList<Data> sourceData1 = FXCollections.observableArrayList(d11,d12);
Data d21 = new Data("first", 0);
d21.pieValueProperty().bind(first);
Data d22 = new Data("first", 0);
d22.pieValueProperty().bind(second);
ObservableList<Data> sourceData2 = FXCollections.observableArrayList(d21,d22);
PieChart firstChart = new PieChart(sourceData1);
PieChart secondChart = new PieChart(sourceData2);
HBox layout = new HBox();
layout.getChildren().addAll(firstChart, secondChart);
Scene scene = new Scene(layout, 500d, 200d);
primaryStage.setScene(scene);
primaryStage.show();
System.out.println(firstChart.getData());
System.out.println(secondChart.getData());
new Thread(new Task<Void>() {
#Override
protected Void call() throws Exception {
Thread.sleep(5000);
first.set(5);
second.set(3);
System.out.println(firstChart.getData());
System.out.println(secondChart.getData());
return null;
}
}).start();
}

Related

Reset scatter chart's node color in a mouse-clicked event

I made this code that creates a scatter chart and allows me to change the color of a node on the plot when I click/select it.
package com.jpc.javafx.charttest;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CreateChart extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
//-------Create Chart--------------
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
XYChart.Series<Number,Number> dataSeries1 = new XYChart.Series();
ScatterChart chart = new ScatterChart(xAxis,yAxis);
dataSeries1.getData().add(new XYChart.Data( 1, 567));
dataSeries1.getData().add(new XYChart.Data( 5, 612));
dataSeries1.getData().add(new XYChart.Data(10, 800));
chart.getData().add(dataSeries1);
//-----Select node and change color -----
for(final XYChart.Data<Number,Number> data : dataSeries1.getData()) {
data.getNode().setOnMouseClicked(e-> {
//dataSeries1.getNode().lookup(".chart-symbol").setStyle("-fx-background-color: red"); that does not work
data.getNode().setStyle("-fx-background-color: blue" );
});
}
VBox vbox = new VBox(chart);
Scene scene = new Scene(vbox, 400, 200);
primaryStage.setScene(scene);
primaryStage.setHeight(300);
primaryStage.setWidth(1200);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
The problem is that when I select another point the previous one stays blue. So I need to reset all the nodes to the default color before I change the selected point's color.
I tried to add this:
dataSeries1.getNode().lookup(".chart-symbol").setStyle("-fx-background-color: red");
but I get:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
To summarize your requirement:
a visual property of a chart-symbol should be marked on user interaction
there should be only one such marked symbol
Sounds like a kind of selection mechanism - which is not supported for chart symbols out of the box, application code must take care of it. The task is
keep track of the (last) selected symbol
guarantee that at any time only a single symbol is selected
keep the visual state of un/selected as needed
The most simple implementation for the logic (the first two bullets) would be to keep a reference to the current selected and update it on user interaction. An appropriate instrument for the latter would be a PseudoClass: can be defined in the css and de/activated along with the logic.
Code snippets (to be inserted into your example)
// Pseudo-class
private PseudoClass selected = PseudoClass.getPseudoClass("selected");
// selected logic
private Node selectedSymbol;
protected void setSelectedSymbol(Node symbol) {
if (selectedSymbol != null) {
selectedSymbol.pseudoClassStateChanged(selected, false);
}
selectedSymbol = symbol;
if (selectedSymbol != null) {
selectedSymbol.pseudoClassStateChanged(selected, true);
}
}
// event handler on every symbol
data.getNode().setOnXX(e -> setSelectedSymbol(data.getNode()));
css example, to be loaded via a style-sheet f.i.:
.chart-symbol:selected {
-fx-background-color: blue;
}
One thing you can do is loop through the data and change the color for the one clicked and set all the other to null
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CreateChart extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
//-------Create Chart--------------
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
XYChart.Series<Number,Number> dataSeries1 = new XYChart.Series();
ScatterChart chart = new ScatterChart(xAxis,yAxis);
dataSeries1.getData().add(new XYChart.Data( 1, 567));
dataSeries1.getData().add(new XYChart.Data( 5, 612));
dataSeries1.getData().add(new XYChart.Data(10, 800));
chart.getData().add(dataSeries1);
//-----Select node and change color -----
for(final XYChart.Data<Number,Number> data : dataSeries1.getData()) {
data.getNode().setOnMouseClicked(e-> {
for(final XYChart.Data<Number,Number> data2 : dataSeries1.getData()) {
if(data == data2)
{
data2.getNode().setStyle("-fx-background-color: blue" );
}
else
{
data2.getNode().setStyle(null);
}
}
});
}
VBox vbox = new VBox(chart);
Scene scene = new Scene(vbox, 400, 200);
primaryStage.setScene(scene);
primaryStage.setHeight(300);
primaryStage.setWidth(1200);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}

how to make window pop up for short time then disappear it without any actionEvent jAVAfX

hi am using JavaFx i want to create a simple window that will appear at first the application will go start then it will stay for short time then it will disappear automatically without any event then my main window will appear now
can anyone help me in this idea
package x;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class AutoHideExmpl extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
VBox v = new VBox();
TextField fiiled = new TextField("asdasdf");
TextField d = new TextField("asdfasdf");
TextField gd = new TextField("asdf");
TextField da = new TextField("asdf");
TextField cd = new TextField("asdf");
v.getChildren().addAll(fiiled, d, gd, da, cd);
v.setMinSize(500, 500);
Scene c = new Scene(v);
primaryStage.setScene(c);
primaryStage.show();
Stage s1 = new Stage();
VBox v1 = new VBox();
TextField fiiled1 = new TextField("asdasdf");
TextField d1 = new TextField("asdfasdf");
TextField gd1 = new TextField("asdf");
TextField da1 = new TextField("asdf");
TextField cd1 = new TextField("asdf");
v1.getChildren().addAll(fiiled1, d1, gd1, da1, cd1);
v1.setMinSize(300, 300);
Scene c1 = new Scene(v1);
s1.setScene(c1);
s1.show();
Task t = new Task<Void>() {
#Override
protected Void call() throws Exception {
Thread.sleep(60000);
Platform.runLater(() -> {
s1.close();
});
System.out.println("hidding");
return null;
}
};
Thread ts = new Thread(t);
ts.start();
}
}
you can user this too
Thread ts = new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(60000);
Platform.runLater(() -> {
s1.close();
});
System.out.println("hidding");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
ts.start();
i hope this will help you if any ? write comment

How to change ImageView on KeyPressed

I am having difficulty trying to solve this, I am currently trying to figure out how to change the ImageView to another ImageView when I press a certain Key on my keyboard but I have no idea how to, I have already looked around almost everywhere I can think of but haven't got a clue.
Heres the code, this is a character class:
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import javafx.animation.Animation;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Character extends Application{
private static final int COLUMNS = 3;
private static final int COUNT = 3;
private static final int WIDTH = 48;
private static final int HEIGHT = 50;
ImageView imgView;
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
imgView = characterStill();
root.setOnKeyPressed(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
try {
imgView = characterWalking();
} catch (IOException ex) {}
}
});
root.setOnKeyReleased(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
try {
imgView = characterStill();
} catch (IOException ex) {}
}
});
root.getChildren().add(imgView);
Scene scene = new Scene(root, Menu.WIDTH, Menu.HEIGHT, Color.GREENYELLOW);
primaryStage.setScene(scene);
primaryStage.show();
}
public ImageView characterStill() throws IOException{
InputStream is = Files.newInputStream(Paths.get("C:\\Users\\Javier\\Desktop\\StickHero\\StillNinja.png"));
Image characterStill = new Image(is);
ImageView stillView = new ImageView(characterStill);
return stillView;
}
public ImageView characterWalking() throws IOException{
final InputStream is = Files.newInputStream(Paths.get("C:\\Users\\Javier\\Desktop\\StickHero\\WalkingNinja.png"));
final Image characterWalking = new Image(is);
ImageView charView = new ImageView(characterWalking);
charView.setViewport(new Rectangle2D(0, 0, WIDTH, HEIGHT));
final Animation animation = new AnimationGen(charView, Duration.millis(300), COUNT, COLUMNS, 0, 0, WIDTH, HEIGHT);
animation.setCycleCount(Animation.INDEFINITE);
animation.play();
return charView;
}
}
In Java, all objects are accessed via a reference. It is important to keep the distinction between the object in memory, and the reference to the object, clear.
When you execute
new ImageView(characterStill);
a new ImageView object is created in memory. When you then (effectively) do
imgView = new ImageView(characterStill);
you assign the variable imgView a reference to that object. You can think of the reference as the memory location of the object (though in reality it doesn't necessarily have to be implemented that way).
What happens in your code at initialization is effectively the following:
imgView = new ImageView(characterStill);
root.getChildren().add(imgView);
So imgView contains a reference to an ImageView object displaying your characterStill image. You then pass that reference to the child list of root, so now the child list of root contains a reference to that same ImageView object.
Now, when the user presses the RIGHT key, you
(effectively) execute
imgView = new ImageView(characterWalking);
This creates a new ImageView object in memory, and assigns a reference to it (think: memory location of this new object) to the variable imgView.
However, the child list of root hasn't changed: it still contains the original reference to the first ImageView object. So nothing changes in your UI.
You could fix this by replacing the child list of root with the newly created ImageView, i.e.:
root.setOnKeyPressed(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
try {
imgView = characterWalking();
root.getChildren().setAll(imgView);
} catch (IOException ex) {}
}
});
However, this is not very efficient. On every key press, you create a brand new object in memory, from an image freshly loaded from the hard drive. Then you discard the previous object and put the new object in the UI.
A better way is just to use a single ImageView object, and replace the image which it displays. You can do this by calling
imgView.setImage(...);
To make things even more efficient, instead of loading the image from disk every time, you could just load the images at startup and then reuse them. This code looks like
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import javafx.animation.Animation;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Character extends Application{
private static final int COLUMNS = 3;
private static final int COUNT = 3;
private static final int WIDTH = 48;
private static final int HEIGHT = 50;
private ImageView imgView;
private Image characterStill ;
private Image characterWalking ;
private Animation animation ;
public void start(Stage primaryStage) throws Exception {
imgView = new ImageView();
characterStill = loadCharacterStill();
characterWalking = loadCharacterWalking();
imgView.setViewport(new Rectangle2D(0, 0, WIDTH, HEIGHT));
animation = new AnimationGen(charView, Duration.millis(300), COUNT, COLUMNS, 0, 0, WIDTH, HEIGHT);
animation.setCycleCount(Animation.INDEFINITE);
imgView.setImage(characterStill);
Group root = new Group();
root.setOnKeyPressed(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
imgView.setImage(characterWalking);
animation.play();
}
});
root.setOnKeyReleased(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
imgView.setImage(characterStill);
animation.stop();
}
});
root.getChildren().add(imgView);
Scene scene = new Scene(root, Menu.WIDTH, Menu.HEIGHT, Color.GREENYELLOW);
primaryStage.setScene(scene);
primaryStage.show();
}
public Image loadCharacterStill() throws IOException{
InputStream is = Files.newInputStream(Paths.get("C:\\Users\\Javier\\Desktop\\StickHero\\StillNinja.png"));
Image characterStill = new Image(is);
return characterStill ;
}
public Image loadCharacterWalking() throws IOException{
final InputStream is = Files.newInputStream(Paths.get("C:\\Users\\Javier\\Desktop\\StickHero\\WalkingNinja.png"));
final Image characterWalking = new Image(is);
return characterWalking ;
}
}
Obviously, since I don't have access to your images or your AnimationGen class, I haven't tested this; it should give you the idea though.

Make JavaFX wait and continue with code

Basically I am trying to make a short effect using JavaFX. I have the shape of a heart (added together from two circles and a polygon) that I can vary in size using the double value p. "Standart Size" would be p = 1.0;.
I am trying to add a pumping effect to the heart. I have the method pumpOnce():
public void pumpOnce(){
p = p + 1;
initHeart();
//Here goes what ever it takes to make stuff working!!
p = p - 1;
initHeart();
}
initHeart() draws the heart based on p.
I have found out that Thread.sleep(); or similar methods will not work due to the thread philosophy in JavaFX.
But what can I use instead?
The JavaFX animations are probably the way to go, but the "thread philosophy" in JavaFX isn't hard to work with if you want to roll your own, or do other, more complicated things in background threads.
The following code will pause and change the value in a label (full disclosure, I'm reusing code I wrote for another question):
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javax.xml.datatype.Duration;
public class DelayWithTask extends Application {
private static Label label;
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
label = new Label();
label.setText("Waiting...");
StackPane root = new StackPane();
root.getChildren().add(label);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
delay(5000, () -> label.setText("Hello World"));
}
public static void delay(long millis, Runnable continuation) {
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
try { Thread.sleep(millis); }
catch (InterruptedException e) { }
return null;
}
};
sleeper.setOnSucceeded(event -> continuation.run());
new Thread(sleeper).start();
}
}
The basic JavaFX background tool is the Task, any JavaFX application that actually does anything will probably be littered with these all over. Learn how to use them.
Dave's solution is great for general purpose off thread based work in JavaFX.
If you wish to use the animation facilities of JavaFX, the solutions below demonstrate this using a Timeline or a ScaleTransition. The timeline implements a discrete scale of the UI element, so every quarter of a second the UI element is scaled larger or back to it's original size. The scale transition implements a smooth scale of the UI element, so the UI element gradually gets larger then smaller using an interpolated scale factor with the default easing interpolator.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class BeatingHeart extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(Stage stage) {
ImageView heart = new ImageView(HEART_IMAGE_LOC);
animateUsingTimeline(heart);
// animateUsingScaleTransition(heart);
StackPane layout = new StackPane(heart);
layout.setPrefWidth(heart.getImage().getWidth() * 2);
layout.setPrefHeight(heart.getImage().getHeight() * 2);
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
private void animateUsingTimeline(ImageView heart) {
DoubleProperty scale = new SimpleDoubleProperty(1);
heart.scaleXProperty().bind(scale);
heart.scaleYProperty().bind(scale);
Timeline beat = new Timeline(
new KeyFrame(Duration.ZERO, event -> scale.setValue(1)),
new KeyFrame(Duration.seconds(0.5), event -> scale.setValue(1.1))
);
beat.setAutoReverse(true);
beat.setCycleCount(Timeline.INDEFINITE);
beat.play();
}
private void animateUsingScaleTransition(ImageView heart) {
ScaleTransition scaleTransition = new ScaleTransition(
Duration.seconds(1), heart
);
scaleTransition.setFromX(1);
scaleTransition.setFromY(1);
scaleTransition.setFromZ(1);
scaleTransition.setToX(1.1);
scaleTransition.setToY(1.1);
scaleTransition.setToZ(1.1);
scaleTransition.setAutoReverse(true);
scaleTransition.setCycleCount(Animation.INDEFINITE);
scaleTransition.play();
}
private static final String HEART_IMAGE_LOC =
"http://icons.iconarchive.com/icons/mirella-gabriele/valentine/128/Heart-red-icon.png";
// icon obtained from: http://www.iconarchive.com/show/valentine-icons-by-mirella-gabriele/Heart-red-icon.html
// icon license: Free for non-commercial use, commercial use not allowed.
}

usrpagestage.initStyle(StageStyle.UTILITY) does not displaying contents fully

Am in attempt to design a pop up window. I designed it and it works, but with a small problem.
This is a part of code of the pop up window :
public class Warning extends BorderPane {
public Warning() {
setCenter(addVBox2());
}
private VBox addVBox2() {
VBox vbox = new VBox();
vbox.setPadding(new Insets(15,10,15,10));
vbox.setSpacing(10);
Label l1 = new Label("WARNING");
l1.setFont(Font.font("Calibri", FontWeight.BOLD, 20));
l1.setTextFill(Color.BLACK);
l1.setUnderline(true);
Label l2 = new Label("Try other User Name..!");
l2.setFont(Font.font("Calibri", FontWeight.BOLD, 18));
l2.setTextFill(Color.RED);
vbox.getChildren().addAll(l1, l2);
return vbox;
}
And this is how I call it :
setEffect(new BoxBlur(5, 10, 10));
Stage usrpagestage = new Stage();
usrpagestage.setMaxHeight(100);
usrpagestage.setMaxWidth(300);
usrpagestage.initStyle(StageStyle.UTILITY);
usrpagestage.setScene(new Scene(new Warning()));
usrpagestage.show();
usrpagestage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent t) {
setEffect(new BoxBlur(0, 0, 0));
}
});
The pop up windows works when it is supposed to. But the contents in it is not fully displayed. This is the screen shot :
How to solve this issue ?
Remove the two lines:
usrpagestage.setMaxHeight(100);
usrpagestage.setMaxWidth(300);
After that, the stage will revert to its default behavior of automatically sizing to fit the initial scene content.
No, it didn't worked.
Yep, it didn't worked at all :-)
It should have worked (letting the stage autosize itself should be the correct solution).
It didn't work because of a bug in the JavaFX layout libraries:
RT-31665 Size of stage with style UTILITY is not correctly calculated.
You can workaround the bug by manually calling stage.sizeToScene().
Sample Code
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.*;
import javafx.scene.web.WebView;
import javafx.stage.*;
public class WarningSample extends Application {
#Override public void start(Stage stage) throws Exception {
final WebView view = new WebView();
view.getEngine().load("http://www.google.com");
stage.setScene(new Scene(view));
stage.show();
Stage warningStage = new Stage();
warningStage.initStyle(StageStyle.UTILITY);
warningStage.setScene(new Scene(new Warning()));
// this workaround allows the stage to be sized correctly.
warningStage.sizeToScene();
warningStage.show();
}
public static void main(String[] args) { launch(); }
private class Warning extends VBox {
public Warning() {
setPadding(new Insets(15, 10, 15, 10));
setSpacing(10);
Label heading = new Label("WARNING");
heading.setFont(Font.font("Calibri", FontWeight.BOLD, 20));
heading.setTextFill(Color.BLACK);
heading.setUnderline(true);
Label body = new Label("Try other User Name..!");
body.setFont(Font.font("Calibri", FontWeight.BOLD, 18));
body.setTextFill(Color.RED);
getChildren().addAll(heading, body);
}
}
}

Resources