Is there a condition where a node in a javaFX scene will not be shown? - javafx

I decided to create a JavaFX calculator that includes Buttons and Labels and a Pane as my root Parent.
I'm still stuck with the layout, here is a rough sketch of the expected result:
Bear with my poor graphics.
The problem is that I see a blank screen when I run the following code:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.scene.text.*;
import javafx.scene.paint.Color;
import javafx.geometry.Pos;
/**
* This class is meant to serve as a simple calculator with basic funtions
* to try myself out with javafx
*/
public class CalculatorFX extends Application{
Label question; // This is the label that shows the question
Label answer; // This is the label that shows the answer
Button[] buttons = new Button[19]; // This is the buttons of the calculator
/**
* This is the root parent or container. It is defined out here because
* I hope to know its length and width are crucial for the layout.
*/
Pane root = new Pane();
/**
* This is the length of the Pane
*/
double rootHeight = root.getHeight();
/**
* This is the width of the Pane
*/
double rootWidth = root.getWidth();
public static void main(String[] args){
launch(args);
}
/**
* This start routine sets up the GUI
*/
public void start(Stage stage){
question = new Label("Type your question");
answer = new Label("Answer:");
question.setTextFill(Color.BLACK);
question.setFont( Font.font(null, FontWeight.BOLD, 18) );
question.setStyle("-fx-font: 15pt sans-serif; -fx-padding: 7px; -fx-border-color: darkred; -fx-border-width: 2px; -fx-text-fill: darkred; -fx-background-color: pink; ");
/* Initializing the numerical buttons first using a loop */
// for(int i = 0; i < 10; i++){
// buttons[i] = new Button(i + "");
// }
/* They are configured like this in the way, they would appear on
* the parent node */
buttons[0] = new Button("CLEAR");
buttons[1] = new Button("DEL");
buttons[2] = new Button("CONT");
buttons[3] = new Button("7");
buttons[4] = new Button("8");
buttons[5] = new Button("9");
buttons[6] = new Button("*");
buttons[7] = new Button("4");
buttons[8] = new Button("5");
buttons[9] = new Button("6");
buttons[10] = new Button("/");
buttons[11] = new Button("1");
buttons[12] = new Button("2");
buttons[13] = new Button("3");
buttons[14] = new Button("-");
buttons[15] = new Button("0");
buttons[16] = new Button(".");
buttons[17] = new Button("=");
buttons[18] = new Button("+");
/* Here we set the position of the children */
double unitX = rootWidth/4;
double unitY = rootHeight/9;
double nextX = rootWidth;
double nextY = rootHeight;
for(int lineNum = 6; lineNum >= 0; lineNum--){
nextY = nextY - unitY;
nextX = rootWidth - unitX;
for(int element = 4; element >=0 ; element--){
if(lineNum == 1){// Then we need to fill the lines
if( element == 1)
continue;
//buttons[lineNum + element + 8].relocate(nextX, nextY);
}
if(lineNum == 0){
answer.relocate(0, nextY - unitY);
System.out.println("Relocated answer label");
question.relocate(0,0);
System.out.println("Relocated answer label");
break; // This breaks out of two for loops because
// this is the last lineNum.
}
//buttons[lineNum + element + 8].relocate(nextX, nextY);
nextX = nextX - unitX;
}
}
/* There is also a need to resize the children */
question.setManaged(false);
question.resize(rootWidth, 2*unitY);
System.out.println("Resized label question");
answer.setManaged(false);
answer.resize(rootWidth, 2*unitY);
System.out.println("Resized label answer");
// for(int i = 0; i < buttons.length; i++){
// buttons[i].setManaged(false);
// buttons[i].resize(unitX, unitY);
// }
/* Time to configure them on our root */
root.setPrefWidth(400);
root.setPrefHeight(800);
root.getChildren().addAll(question, answer);
// for(int i = 0; i < buttons.length; i++){
// root.getChildren().add(buttons[i]);
// }
Scene scene = new Scene(root);
scene.getStylesheets().add("myStyle.css");
stage.setScene(scene);
stage.setTitle("CalculatorFX");
stage.show();
} // End of start
} // End of class CalculatorFX
Now, myStyle.css contains:
Button {
-fx-font: bold 16pt "Times New Roman";
-fx-text-fill: darkblue;
}
Label {
-fx-font: 15pt sans-serif;
-fx-padding: 7px;
-fx-border-color: darkred;
-fx-border-width: 2px;
-fx-text-fill: darkred;
-fx-background-color: pink;
}
Help me. Why do I see a blank screen?

To answer your main question:
Yes there is!
In JavaFX, Nodes have visible property:
Specifies whether this Node and any subnodes should be rendered as part of the scene graph.
You control it using the setVisible method.
To help you get started:
As Zephyr and Jamed_D pointed out, you are doing too much work.
I believe there are several correct ways to go about what you are trying to achieve.
A visual editor like SceneBuilder comes to mind. It could help you get a deeper understanding of JavaFX's components. Especially, if building the GUI programmatically is not a must for you.
Here is a simple example to help you get started with one of the ways:
public class App extends Application {
public static void main(String[] args) {
launch();
}
#Override
public void start(Stage stage) {
var label1 = new Label("Type your question: ");
var label2 = new Label("Answer: ");
label1.setPrefHeight(40);
label2.setPrefHeight(40);
label1.setPadding(new Insets(0, 10, 0, 10));
label2.setPadding(new Insets(0, 10, 0, 10));
HBox label1HB = new HBox(label1);
HBox label2HB = new HBox(label2);
VBox vBox = new VBox();
vBox.setFillWidth(true);
vBox.setPrefWidth(Double.MAX_VALUE);
vBox.setPrefHeight(Double.MAX_VALUE);
vBox.getChildren().addAll(label1HB, label2HB);
List<HBox> rows = new ArrayList<>();
for (int row = 0; row < 6; row++) {
HBox hbox = new HBox();
for (int col = 0; col < 4; col++) {
Button button = new Button(row + " " + col);
button.setMaxWidth(Double.MAX_VALUE);
button.setMaxHeight(Double.MAX_VALUE);
HBox.setHgrow(button, Priority.ALWAYS);
hbox.getChildren().add(button);
}
VBox.setVgrow(hbox, Priority.ALWAYS);
rows.add(hbox);
}
vBox.getChildren().addAll(rows);
// Uncomment the following line to see the visible property in action.
// rows.get(2).setVisible(false);
var scene = new Scene(vBox, 400, 800);
stage.setScene(scene);
stage.show();
}
}
I personally like wrapping my nodes within a VBox or an Hbox because it allows me to change the scene's size without worrying about resizing everything again.
You can find many tutorials online explaining how to exploit different JavaFX layouts' properties to achieve better as well as cleaner GUI and code.
It should be mentioned that you are going to have to take care of:
A couple of issues, like what happens when the window is too small to fit your entire content?
Some specifics, like if you want some of your nodes to have a fixed width and height.
etc.

Related

JavaFX - Check the position of a label based on a mouse click

I try to write a code that find the label on which one have clicked.
Using an event listener, I got the positions of the event using getX() and getY().
However, I cannot find the adequate methods for the label positions in order to compare them.
Below is my code, and its ouput.
public class Beta extends Application {
final Label[] answerLabel = new Label[4];
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.setGridLinesVisible(true);
final int numCols = 7 ;
final int numRows = 12 ;
//final Label[] answerLabel = new Label[4];
for (int i = 0; i < numCols; i++) {
ColumnConstraints colConst = new ColumnConstraints();
colConst.setPercentWidth(100.0 / numCols);
root.getColumnConstraints().add(colConst);
}
for (int i = 0; i < numRows; i++) {
RowConstraints rowConst = new RowConstraints();
rowConst.setPercentHeight(100.0 / numRows);
root.getRowConstraints().add(rowConst);
}
for(int i = 0; i<4; i++){
answerLabel[i] = new Label();
answerLabel[i].setMaxWidth(Double.MAX_VALUE);
answerLabel[i].setMaxHeight(Double.MAX_VALUE);
answerLabel[i].setStyle("-fx-background-color: blue;-fx-font-size: 7pt;-fx-padding: 0;");
answerLabel[i].setPadding(new Insets(10));
answerLabel[i].setCursor(Cursor.HAND);
root.add(answerLabel[i], 3, i +5, 1, 1);
answerLabel[i].setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
answerLabelPressed(e);
}
});
}
primaryStage.setScene(new Scene(root, 900, 500));
primaryStage.show();
}
private void answerLabelPressed(MouseEvent e)
{
int labelSelected;
double px = e.getX();
double py = e.getY();
System.out.println("px = " + px + " py = " + py);
for (labelSelected = 0; labelSelected < 4; labelSelected++)
{
System.out.println("answerLabel[labelSelected].getLayoutX() = " + answerLabel[labelSelected].getLayoutX());
System.out.println("answerLabel[labelSelected].getLayoutY() = " + answerLabel[labelSelected].getLayoutY());
}
}
public static void main(String[] args) {
launch(args);
}
}
px = 42.0 py = 7.0
answerLabel[labelSelected].getLayoutX() = 386.0
view.answerLabel[labelSelected].getLayoutY() = 208.0
answerLabel[labelSelected].getLayoutX() = 386.0
view.answerLabel[labelSelected].getLayoutY() = 250.0
answerLabel[labelSelected].getLayoutX() = 386.0
view.answerLabel[labelSelected].getLayoutY() = 292.0
answerLabel[labelSelected].getLayoutX() = 386.0
view.answerLabel[labelSelected].getLayoutY() = 333.0
Upadate: The main purpose was to find/check the equivalent JavaFX methods of those used in Java Swing.
An alternative and better algorithm beeing as one can read in most popular Java books :
MouseListener ml = new MouseListener() {
public void mouseClicked(MouseEvent e) {
report("mouseClicked", e.paramString());
}
In Java Swing, one should read :
Point p = e.getComponent().getLocation();
System.out.println("px = " + p.getX() + " py = " + p.getY());
for (labelSelected = 0; labelSelected < 4; labelSelected++)
{
System.out.println("answerLabel[labelSelected].getX() = " + answerLabel[labelSelected].getX());
System.out.println("answerLabel[labelSelected].getY() = " + answerLabel[labelSelected].getY());
}
I try to write a code that find the label on which one have clicked.
You create four labels, and you create a listener for each label. Each listener is only registered with one label.
So there is no need to get your hands dirty with the coordinates of the click (the event handling mechanism has already done all of that for you, when it decided to which node to dispatch the event). Just reference the label that was clicked:
public class Beta extends Application {
final Label[] answerLabel = new Label[4];
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.setGridLinesVisible(true);
final int numCols = 7 ;
final int numRows = 12 ;
for (int i = 0; i < numCols; i++) {
ColumnConstraints colConst = new ColumnConstraints();
colConst.setPercentWidth(100.0 / numCols);
root.getColumnConstraints().add(colConst);
}
for (int i = 0; i < numRows; i++) {
RowConstraints rowConst = new RowConstraints();
rowConst.setPercentHeight(100.0 / numRows);
root.getRowConstraints().add(rowConst);
}
for(int i = 0; i<4; i++){
answerLabel[i] = new Label();
answerLabel[i].setMaxWidth(Double.MAX_VALUE);
answerLabel[i].setMaxHeight(Double.MAX_VALUE);
answerLabel[i].setStyle("-fx-background-color: blue;-fx-font-size: 7pt;-fx-padding: 0;");
answerLabel[i].setPadding(new Insets(10));
answerLabel[i].setCursor(Cursor.HAND);
root.add(answerLabel[i], 3, i +5, 1, 1);
Label currentLabel = answerLabel[i];
int currentIndex = i ;
answerLabel[i].setOnMouseClicked(event -> {
System.out.println("Clicked on label "+currentIndex);
// just for demo: in real life use external stylesheets
// and pseudoclasses, etc.
for (Label label : answerLabel) {
label.setStyle("-fx-background-color: blue;-fx-font-size: 7pt;-fx-padding: 0;");
}
currentLabel.setStyle("-fx-background-color: gold;-fx-font-size: 7pt;-fx-padding: 0;");
});
}
primaryStage.setScene(new Scene(root, 900, 500));
primaryStage.show();
}
}
It is not necessary to manually compute which node the mouse clicked on. That calculation is already done for you by the framework. That's how the framework knows which event handlers to invoke. If you simply add a unique handler to each node, then when that handler is invoked only that node could be the source. This is demonstrated in #James_D's answer.
However, if you want to manually compute which node was clicked (e.g., for fun or just for learning purposes), then here is a runnable example:
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Border;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
var labels = IntStream.range(0, 12)
.mapToObj(this::createLabel)
.toArray(Label[]::new);
var grid = new GridPane();
grid.setPadding(new Insets(20));
grid.setHgap(20);
grid.setVgap(20);
grid.setOnMouseClicked(e -> {
var clickedOnLabel = getClickedOnLabel(labels, e);
if (clickedOnLabel == null) {
System.out.println("You did not click on a label!");
} else {
System.out.printf("You clicked on a label: '%s'%n", clickedOnLabel.getText());
}
});
int i = 0;
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 3; row++) {
grid.add(labels[i++], col, row);
}
}
primaryStage.setScene(new Scene(grid));
primaryStage.show();
}
private Label getClickedOnLabel(Label[] labels, MouseEvent event) {
for (var label : labels) {
var bounds = label.localToScene(label.getBoundsInLocal());
if (bounds.contains(event.getSceneX(), event.getSceneY())) {
return label;
}
}
return null;
}
private Label createLabel(int n) {
var label = new Label(String.format("Label #%02d", n));
label.setCursor(Cursor.HAND);
label.setPadding(new Insets(5));
label.setFont(Font.font("Monospaced", 15.0));
label.setBorder(Border.stroke(Color.BLACK));
return label;
}
}
The important part, the part which computes which label was clicked on, is here:
private Label getClickedOnLabel(Label[] labels, MouseEvent event) {
for (var label : labels) {
var bounds = label.localToScene(label.getBoundsInLocal());
if (bounds.contains(event.getSceneX(), event.getSceneY())) {
return label;
}
}
return null;
}
It gets the bounds of each Label in the scene's coordinate space, and then tests if the mouse's location—also in the scene's coordinate space—is contained within those bounds. You can use whatever coordinate space you like (e.g., the screen's, the grid pane's, the label's, etc.), as long as you use the same one for both the label's bounds and the mouse's location. Note the mouse's local coordinates (i.e., getX() and getY()) are in the source node's coordinate space. The source node is the node that the currently-being-invoked handler was registered with for the specific event currently being processed (the GridPane in the above example).
But again, for any "real" code, I strongly recommend you use the solution in #James_D's answer.

Erasing Antialiased Shapes from a JavaFX Canvas

I have inherited a simulation program to extend with new features. The original was written as an Applet using the AWT library for graphics. Before adding the new features I want to adapt the program to the desktop and use JavaFX instead of AWT.
The simulation paints hundreds or thousands of objects dozens of times per second, then erases them and repaints them at new locations, effectively animating them. I am using a Canvas object for that part of the UI. Erasing is done by repainting the object with the background color. What I am seeing though is that erasing objects is incomplete. A kind of "halo" gets left behind though.
The following program illustrates the problem. Clicking the "Draw" button causes it to draw a few hundred circles on the Canvas using the foreground color. After drawing, clicking the button again will erase the circles by re-drawing them in the background color. Multiple cycles of draw/erase will build up a visible background of "ghost" images.
package com.clartaq.antialiasingghosts;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
static final int NUM_CIRCLES = 500;
static final int CIRCLE_DIAMETER = 10;
static final double PANEL_WIDTH = 75.0;
static final double PANEL_HEIGHT = 40.0;
static final Color FG_COLOR = Color.rgb(10, 0, 200);
static final Color BG_COLOR = Color.rgb(255, 255, 255);
static final double BUTTON_WIDTH = 50.0;
GraphicsContext gc;
Random rand = new Random();
double[] px = new double[NUM_CIRCLES];
double[] py = new double[NUM_CIRCLES];
void randomizeParticlePositions() {
for (int i = 0; i < NUM_CIRCLES; i++) {
px[i] = rand.nextDouble() * PANEL_WIDTH;
py[i] = rand.nextDouble() * PANEL_HEIGHT;
}
}
void drawCircles(Color color) {
gc.setFill(color);
for (int i = 0; i < NUM_CIRCLES; i++) {
var screenX = px[i] * CIRCLE_DIAMETER;
var screenY = py[i] * CIRCLE_DIAMETER;
gc.fillOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
}
#Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
stage.setTitle("AntiAliasingGhosts -- erasing objects leaves ghosts in JavaFX");
Label versionLabel = new Label("JavaFX " + javafxVersion
+ ", running on Java " + javaVersion + ".");
double canvasWidth = (PANEL_WIDTH * CIRCLE_DIAMETER);
double canvasHeight = (PANEL_HEIGHT * CIRCLE_DIAMETER);
Canvas canvasRef = new Canvas(canvasWidth, canvasHeight);
gc = canvasRef.getGraphicsContext2D();
Button deBtn = new Button("Draw");
deBtn.setPrefWidth(BUTTON_WIDTH);
deBtn.setOnAction(e -> {
String txt = deBtn.getText();
switch (txt) {
case "Draw" -> {
randomizeParticlePositions();
drawCircles(FG_COLOR);
deBtn.setText("Erase");
}
case "Erase" -> {
drawCircles(BG_COLOR);
deBtn.setText("Draw");
}
default -> Platform.exit();
}
});
Button exBtn = new Button("Exit");
exBtn.setPrefWidth(BUTTON_WIDTH);
exBtn.setOnAction(e -> Platform.exit());
TilePane tp = new TilePane();
tp.setAlignment(Pos.CENTER);
tp.setHgap(10);
tp.getChildren().addAll(deBtn, exBtn);
VBox root = new VBox();
root.setPadding(new Insets(7));
root.setSpacing(10);
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(versionLabel, canvasRef, tp);
StackPane sp = new StackPane(root);
BackgroundFill bf = new BackgroundFill(BG_COLOR, CornerRadii.EMPTY, Insets.EMPTY);
Background bg = new Background(bf);
sp.setBackground(bg);
Scene scene = new Scene(sp, 640.0, 480.0);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I can get good erasure by expanding the diameter of the circles by 2 pixels when erasing. Of course, that can affect nearby shapes too.
Also, using the fillRect method to erase the entire Canvas seems reasonable, but that means everything has to be re-drawn if anything has to be re-drawn. I suppose it is possible to optimize the re-draw by erasing and re-drawing a smaller section of the Canvas but I don't want to do that if it isn't necessary.
Magnifying sections of the program display shows that it is really an antialiasing effect. Constructing the Scene with the SceneAntialiasing.DISABLED parameter does not seem to have any effect.
Attempting to turn off image smoothing as suggested in this question does not help.
Is possible to erase a single shape drawn on a Canvas by re-drawing it in the background color?
I am using Java 17.0.1, JavaFX 17.0.1, and a 5K Mac display if that is relevant.
For expedience, note the difference between fillOval and strokeOval() in the GraphicsContext. You can conditionally erase the outline in drawCircles() as a function of a suitable boolean value:
if (stroke) {
gc.setStroke(BG_COLOR);
gc.strokeOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
Try a few representative shapes, e.g. fillRect, to verify the desired result.
A better alternative, IMO, is to pursue the erase -> render strategy. Complete examples seen here and here may help you establish whether the approach is scalable to your use-case. See also this related examination of resampling artifact.
Expedient approach, as tested:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
static final int NUM_CIRCLES = 500;
static final int CIRCLE_DIAMETER = 10;
static final double PANEL_WIDTH = 75.0;
static final double PANEL_HEIGHT = 40.0;
static final Color FG_COLOR = Color.rgb(10, 0, 200);
static final Color BG_COLOR = Color.rgb(255, 255, 255);
static final double BUTTON_WIDTH = 50.0;
GraphicsContext gc;
Random rand = new Random();
private boolean stroke;
double[] px = new double[NUM_CIRCLES];
double[] py = new double[NUM_CIRCLES];
void randomizeParticlePositions() {
for (int i = 0; i < NUM_CIRCLES; i++) {
px[i] = rand.nextDouble() * PANEL_WIDTH;
py[i] = rand.nextDouble() * PANEL_HEIGHT;
}
}
void drawCircles(Color color) {
gc.setFill(color);
for (int i = 0; i < NUM_CIRCLES; i++) {
var screenX = px[i] * CIRCLE_DIAMETER;
var screenY = py[i] * CIRCLE_DIAMETER;
gc.fillOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
if (stroke) {
gc.setStroke(BG_COLOR);
gc.strokeOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
}
}
#Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
stage.setTitle("AntiAliasingGhosts -- erasing objects leaves ghosts in JavaFX");
Label versionLabel = new Label("JavaFX " + javafxVersion
+ ", running on Java " + javaVersion + ".");
double canvasWidth = (PANEL_WIDTH * CIRCLE_DIAMETER);
double canvasHeight = (PANEL_HEIGHT * CIRCLE_DIAMETER);
Canvas canvasRef = new Canvas(canvasWidth, canvasHeight);
gc = canvasRef.getGraphicsContext2D();
Button deBtn = new Button("Draw");
deBtn.setPrefWidth(BUTTON_WIDTH);
deBtn.setOnAction(e -> {
String txt = deBtn.getText();
switch (txt) {
case "Draw" -> {
randomizeParticlePositions();
drawCircles(FG_COLOR);
deBtn.setText("Erase");
stroke = true;
}
case "Erase" -> {
drawCircles(BG_COLOR);
deBtn.setText("Draw");
stroke = false;
}
default ->
Platform.exit();
}
});
Button exBtn = new Button("Exit");
exBtn.setPrefWidth(BUTTON_WIDTH);
exBtn.setOnAction(e -> Platform.exit());
TilePane tp = new TilePane();
tp.setAlignment(Pos.CENTER);
tp.setHgap(10);
tp.getChildren().addAll(deBtn, exBtn);
VBox root = new VBox();
root.setPadding(new Insets(7));
root.setSpacing(10);
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(versionLabel, canvasRef, tp);
StackPane sp = new StackPane(root);
BackgroundFill bf = new BackgroundFill(BG_COLOR, CornerRadii.EMPTY, Insets.EMPTY);
Background bg = new Background(bf);
sp.setBackground(bg);
Scene scene = new Scene(sp, 640.0, 480.0);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}

gridpane javafx clickable cells that get toggled and untoggled

I am trying to create a gridpane that is toggled/untoggled as the user clicks on specific cells. For example, when the user clicks on a cell with the content "*", I would like the content of that specific cell to be changed into a blank cell " ". Similarly, if the user clicks on a cell with a blank content " ", I would like the content of that cell to be changed into a cell with the content "*".
Basically, I am starting out with a grid that is partly input as follows:
My gridpane is initially input as follows, I am keeping track of a matrix of booleans that represents whether there should be a "*" in one cell of the matrix. If there is a "*" in cell [i, j] of the gridpane, then the value of matrix[i,j] should be true, otherwise it should be false.
boolean matrix[][] = new boolean[StateList.size()+1][RequirementList.size()+1];
for( InstanceArtifact li: ListOfLinks) {
int y=1;
for(InstanceArtifact re: RequirementList) {
int x=1;
for(InstanceArtifact state: StateList) {
if(li.getPropertyValue("linksource").equals(re) && li.getPropertyValue("linktarget").equals(state)) {
link= new Label(" * ");
//tick in cell (1,1)
grid.add(link, y, x);
matrix[x][y]= true;
}
else {
link= new Label(" ");
//tick in cell (1,1)
grid.add(link, y, x);
}
x++;
}
y++;
}
}
}
What I would like to do is toggle/untoggle the stars, this is what I am trying to do with the code below, as we are clicking on a cell containing "*", meaning that matrix[i][j]=true, I am removing the corresponding label in the grid and I am adding a new label with an empty text. I also do the same thing in the opposite situation in which the label text is blank and I need to replace it with a label containing a star.
grid.getChildren().forEach(element -> {
element.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
int matrixX= GridPane.getRowIndex(element);
int matrixY= GridPane.getColumnIndex(element);
if(element!=null && matrixX!=0 && matrixY!=0) {
System.out.println("matrixX: "+matrixX+" matrixY: "+matrixY);
System.out.println("matrixY: "+matrixY+" "+ matrix[matrixX][matrixY]);
if(matrix[matrixX][matrixY]==true && matrixX!=0 && matrixY!=0) {
System.out.println("HEY I AM HERE FIRSTTTTTTTTT");
Node newnode= new Label(" ");
GridPane.clearConstraints(element);
// grid.getChildren().remove(element);
grid.add(newnode, matrixY, matrixX);
matrix[matrixX][matrixY]=false;
/*for(int l=0; l<RequirementList.size(); l++) {
for(int p=0; p<StateList.size(); p++) {
System.out.println(l + " "+p +" "+matrix[l][p]);
}
}*/
//grid.add(mynode, matrixY+1, matrixX+1, 1, 1);
}
else if(matrix[matrixX][matrixY]==false && matrixX!=0 && matrixY!=0){
System.out.println("HEY I AM HERE SECONDDDDDDD ");
/* for(int l=0; l<RequirementList.size(); l++) {
for(int p=0; p<StateList.size(); p++) {
System.out.println(l + " "+p +" "+matrix[l][p]);
}
}*/
Node falsenode= new Label(" * ");
GridPane.clearConstraints(element);
// grid.getChildren().remove(element);
grid.add(falsenode, matrixY, matrixX);
matrix[matrixX][matrixY]=true;
}
// System.out.println("Row: " + GridPane.getRowIndex(element));
// System.out.println("Column: " + GridPane.getColumnIndex(element));
}
}
});
});
My code is not behaving as expected, I would like the cell to be toggled/untoggled whenever the user clicks on a cell, the code is executed only the first time the user clicks on a given cell, if the user clicks on the same cell multiple times (more than once), then nothing happens.
Also, the line of code in which I am trying to remove a label is not working:
grid.getChildren().remove(element);
This could be better served with ToggleButton. Comments in code.
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
/**
*
* #author Sedrick
*/
public class JavaFXApplication60 extends Application {
#Override
public void start(Stage primaryStage) {
//Start create GUI
Label lblRequirement11 = new Label("Requirement1");
Label lblRequirement12 = new Label("Requirement2");
Label lblRequirement13 = new Label("Requirement3");
GridPane.setConstraints(lblRequirement11, 1, 0);
GridPane.setConstraints(lblRequirement12, 2, 0);
GridPane.setConstraints(lblRequirement13, 3, 0);
Label lblState1 = new Label("State1");
ToggleButton toggleButton11 = new ToggleButton();
toggleButton11.setMaxWidth(Double.MAX_VALUE);
ToggleButton toggleButton12 = new ToggleButton();
toggleButton12.setMaxWidth(Double.MAX_VALUE);
ToggleButton toggleButton13 = new ToggleButton();
toggleButton13.setMaxWidth(Double.MAX_VALUE);
GridPane.setConstraints(lblState1, 0, 1);
GridPane.setConstraints(toggleButton11, 1, 1);
GridPane.setConstraints(toggleButton12, 2, 1);
GridPane.setConstraints(toggleButton13, 3, 1);
Label lblState2 = new Label("State2");
ToggleButton toggleButton21 = new ToggleButton();
toggleButton21.setMaxWidth(Double.MAX_VALUE);
ToggleButton toggleButton22 = new ToggleButton();
toggleButton22.setMaxWidth(Double.MAX_VALUE);
ToggleButton toggleButton23 = new ToggleButton();
toggleButton23.setMaxWidth(Double.MAX_VALUE);
GridPane.setConstraints(lblState2, 0, 2);
GridPane.setConstraints(toggleButton21, 1, 2);
GridPane.setConstraints(toggleButton22, 2, 2);
GridPane.setConstraints(toggleButton23, 3, 2);
Label lblState3 = new Label("State3");
ToggleButton toggleButton31 = new ToggleButton();
toggleButton31.setMaxWidth(Double.MAX_VALUE);
ToggleButton toggleButton32 = new ToggleButton();
toggleButton32.setMaxWidth(Double.MAX_VALUE);
ToggleButton toggleButton33 = new ToggleButton();
toggleButton33.setMaxWidth(Double.MAX_VALUE);
GridPane.setConstraints(lblState3, 0, 3);
GridPane.setConstraints(toggleButton31, 1, 3);
GridPane.setConstraints(toggleButton32, 2, 3);
GridPane.setConstraints(toggleButton33, 3, 3);
GridPane root = new GridPane();
root.setVgap(5);
root.setHgap(5);
root.getChildren().addAll(lblRequirement11, lblRequirement12, lblRequirement13);
root.getChildren().addAll(lblState1, toggleButton11, toggleButton12, toggleButton13);
root.getChildren().addAll(lblState2, toggleButton21, toggleButton22, toggleButton23);
root.getChildren().addAll(lblState3, toggleButton31, toggleButton32, toggleButton33);
//End create GUI
//Start create ToggleButtons' event handlers.
List<ToggleButton> toggleButtonList = new ArrayList();
toggleButtonList.add(toggleButton11);
toggleButtonList.add(toggleButton12);
toggleButtonList.add(toggleButton13);
toggleButtonList.add(toggleButton21);
toggleButtonList.add(toggleButton22);
toggleButtonList.add(toggleButton23);
toggleButtonList.add(toggleButton31);
toggleButtonList.add(toggleButton32);
toggleButtonList.add(toggleButton33);
for(ToggleButton tempToggleButton : toggleButtonList)
{
tempToggleButton.setOnAction(actionEvent -> {
if(tempToggleButton.isSelected())
{
tempToggleButton.setText("*");
}
else
{
tempToggleButton.setText("");
}
});
}
////End create ToggleButtons' event handlers.
Scene scene = new Scene(root, 400, 300);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

JavaFX TextArea - Controlling the scroll bar sizes

Unpleasantly surprised by TextArea CSS font sizes having wacky effects on the sizes of the scroll bars, I'm trying to get control of the sizes myself. Please refer to the following SSCCE. I can easily control the vertical scroll bar, but the horizontal bar is simply ignoring the sizes I'm setting. Am I expecting something unreasonable here, or is this (yet another) bug in JavaFX? Thanks!
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
int lineCount = 100;
int wordCount = 70;
StringBuilder sb = new StringBuilder();
for (int lineNbr = 0; lineNbr < lineCount; lineNbr++) {
for (int wordNbr = 0; wordNbr < wordCount; wordNbr++) {
sb.append("Sample");
}
sb.append(System.getProperty("line.separator"));
}
textArea.setText(sb.toString());
StackPane root = new StackPane();
root.getChildren().add(textArea);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
double prefSize = 50;
ScrollBar vertScrollBar = (ScrollBar)textArea.lookup(".scroll-bar:vertical");
ScrollBar horizScrollBar = (ScrollBar)textArea.lookup(".scroll-bar:horizontal");
vertScrollBar.setPrefWidth(prefSize); // This works great!
horizScrollBar.setPrefHeight(prefSize); // This doesn't do anything!
horizScrollBar.setMinHeight(prefSize); // Nor does this
horizScrollBar.setPrefWidth(prefSize); // Nor this
horizScrollBar.setMinWidth(prefSize); // Nor this
}
}
ScrollBar vertScrollBar = (ScrollBar) textArea.lookup(".scroll-bar:vertical");
ScrollBar horizScrollBar = (ScrollBar) textArea.lookup(".scroll-bar:horizontal");
System.out.println(vertScrollBar + " " + horizScrollBar);
ScrollBar#35ef2d94[styleClass=scroll-bar]
ScrollBar#35ef2d94[styleClass=scroll-bar]
Same object (vertical ScrollBar).
ScrollPane sPane = (ScrollPane)textArea.getChildrenUnmodifiable().get(0);
ScrollBar horizScrollBar = (ScrollBar)sPane.getChildrenUnmodifiable().get(2);
horizScrollBar.setPrefHeight(prefSize); // This does something!
Update: Better way to receive the two ScrollBars.
ScrollBar[] bars = new ScrollBar[2];
textArea.lookupAll(".scroll-bar").toArray(bars);
bars[0].setPrefWidth(prefSize);
bars[1].setPrefHeight(prefSize);
Edit: Explanation for similar problem with lookupAll(...) and pseudo classes here and here.
Lookups are generally pretty fragile (and I don't think they're really intended to be robust); as noted in the links from the other answer they don't appear to support pseudoclasses.
You can of course just use CSS for this:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
int lineCount = 100;
int wordCount = 70;
StringBuilder sb = new StringBuilder();
for (int lineNbr = 0; lineNbr < lineCount; lineNbr++) {
for (int wordNbr = 0; wordNbr < wordCount; wordNbr++) {
sb.append("Sample");
}
sb.append(System.getProperty("line.separator"));
}
textArea.setText(sb.toString());
StackPane root = new StackPane();
root.getChildren().add(textArea);
Scene scene = new Scene(root, 300, 250) ;
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
}
with the following in style.css:
.scroll-pane .scroll-bar:horizontal {
-fx-pref-height: 50 ;
}
.scroll-pane .scroll-bar:vertical {
-fx-pref-width: 50 ;
}
This approach, of course, is far more convenient if you have multiple scroll panes in your application and want them all to have the same style of scroll bars.

Adding multiple nodes to vbox javafx

I am new in JavaFx and I spend too much time trying to put radio button + textfield dynamically. After typing a number, I want to display my radio buttons and my TextFields in that way (blue and red ones)
But I got this:
I tried with vbox, hbox, both of them, but it did not work!
Can anyone figure out the problem in my code please!!! Thanks for your help
RadioButton[] btn = new RadioButton[100]; //our Collection to hold newly created Buttons
TextField[] xlist = new TextField[100]; //our Collection to hold newly created Buttons
TextField[] ylist = new TextField[100];
final ToggleGroup grpBtn = new ToggleGroup();
#FXML
private Group noeuds;
#FXML
private VBox vb2;
#FXML
private HBox hb2;
#FXML
public void addBtn(int i, RadioButton[] btn) {
btn[i] = new RadioButton();
btn[i].setText(String.valueOf(i + 1));
btn[i].setToggleGroup(grpBtn);
btn[i].setSelected(true);
btn[i].setTranslateX(-5);
btn[i].setTranslateY(-340);
btn[i].setPadding(new Insets(0, 0, 20, 20));
vb2.getChildren().add(btn[i]);
}
#FXML
public void addX(int i, TextField[] xlist) {
xlist[i] = new TextField();
xlist[i].setTranslateX(-80);
xlist[i].setTranslateY(40);
xlist[i].setStyle("-fx-background-color: red;");
xlist[i].setPrefSize(30, 30);
xlist[i].setTooltip(new Tooltip("X coordinate of " + (i + 1)));
hb2.getChildren().add(xlist[i]);
}
#FXML
public void addY(int i, TextField[] ylist) {
ylist[i] = new TextField();
ylist[i].setTranslateX(-78);
ylist[i].setTranslateY(40);
ylist[i].setStyle("-fx-background-color: blue;");
ylist[i].setPrefSize(30, 30);
ylist[i].setTooltip(new Tooltip("Y coordinate of" + (i + 1)));
hb2.getChildren().add(ylist[i]);
}
public void initialize(URL url, ResourceBundle rb) {
//some code
for (int i = 0; i < Integer.parseInt(nodeID.getText()); i++) {
addBtn(i, btn);
// System.out.println("jjjj"+btn.length);
addX(i, xlist);
// System.out.println("mmmm"+xlist.length);
addY(i, ylist);
}
}
This little app might help give you a boost. Read over the code and try to get an understanding. I tried to make comments in the code.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication7 extends Application {
#Override
public void start(Stage primaryStage) {
AnchorPane root = new AnchorPane();
VBox vbox1 = new VBox();
vbox1.setSpacing(5);//Set vbox spacing
//Handles the number of row to be added to the vbox
for(int i = 0; i < 4; i++)
{
vbox1.getChildren().add(addNewRow(i));
}
root.getChildren().add(vbox1);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
//Method creates all the nodes for a new row.
HBox addNewRow(int rowNumber)
{
//Create nodes and adding correct spaceing
HBox hbox = new HBox();
hbox.setSpacing(5);
RadioButton radioButton = new RadioButton();
radioButton.setPrefHeight(25);
TextField textField = new TextField();
textField.setPrefWidth(40);
Label label = new Label(Integer.toString(rowNumber + 1));
label.setPrefHeight(25);
HBox trailingHBox = new HBox();
trailingHBox.setSpacing(5);
hbox.getChildren().addAll(radioButton, textField, label, trailingHBox);
//Event handler on textfield. Add right about of trailing textfields
textField.setOnKeyReleased((event)->{
if(textField.getText().trim().length() > 0 && Integer.parseInt(textField.getText()) > 0)//If textfield has some value greater than zero. I didn't catch for non integers
{
int tempInt = Integer.parseInt(textField.getText());
//clear trailingHBox so that new Trailing hbox can be added
if(trailingHBox.getChildren().size() > 0)
{
trailingHBox.getChildren().clear();
}
//add the correct number of textFields.
for(int i = 0; i < tempInt - 1; i++)
{
TextField tempTextField = new TextField();
tempTextField.setPrefWidth(20);
trailingHBox.getChildren().add(tempTextField);
}
//add the blue and red textfields
TextField textFieldBlue = new TextField();
textFieldBlue.setPrefWidth(40);
textFieldBlue.setStyle("-fx-background-color: BLUE");
TextField textFieldRed = new TextField();
textFieldRed.setPrefWidth(40);
textFieldRed.setStyle("-fx-background-color: RED");
trailingHBox.getChildren().addAll(textFieldBlue, textFieldRed);
}
else{
trailingHBox.getChildren().clear();//clear traingHbox if it's has no value
}
});
return hbox;
}
}

Resources