I would like to add little space between rows every x row in the loop. I find this is better to add empty row to GridPane than setting particular constrains on rows. Problem is I don't know what node should I put into that row to fake empty element. I could do by putting let say Text node. But is this really correct? Can anyone provide more elegant solution?
gridPane.addRow(i, new Text(""));
Using a Text node with an empty string for creating the empty gridpane row is fine.
As an alternative, the sample below uses a Pane to create a "spring" node for the empty grid row which could have it's preferred height set to any required value to achieve whatever gap size you want. Additionally the spring node can also be styled via css if necessary.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
// GridPane with a blank row
// http://stackoverflow.com/questions/11934045/how-to-add-empty-row-in-gridpane-in-javafx
public class GridPaneWithEmptyRowSample extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(final Stage stage) throws Exception {
// create nodes for the grid.
final Label label1 = new Label("Label 1");
final Label label2 = new Label("Label 2");
final Label label3 = new Label("Label 3");
final Pane spring = new Pane();
spring.minHeightProperty().bind(label1.heightProperty());
// layout the scene.
final GridPane layout = new GridPane();
layout.add(label1, 0, 0);
layout.add(spring, 0, 1);
layout.add(label2, 0, 2);
layout.add(label3, 0, 3);
layout.setPrefHeight(100);
stage.setScene(new Scene(layout));
stage.show();
}
}
I think the best way to solve this is by adding RowConstraints, setting the height of each row in the Gridpane. Then you wont have to add "empty" rows, because each row will acquire the same space regardless of whether it contains anything or not.
Here's a minimal, complete and verifiable example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SSCCE extends Application {
#Override
public void start(Stage stage) {
VBox root = new VBox();
GridPane gridPane = new GridPane();
gridPane.add(new Label("First"), 0, 0);
gridPane.add(new Label("Second"), 0, 2);
gridPane.add(new Label("Third"), 0, 3);
// Add one RowConstraint for each row. The problem here is that you
// have to know how many rows you have in you GridPane to set
// RowConstraints for all of them.
for (int i = 0; i <= 3; i++) {
RowConstraints con = new RowConstraints();
// Here we set the pref height of the row, but you could also use .setPercentHeight(double) if you don't know much space you will need for each label.
con.setPrefHeight(20);
gridPane.getRowConstraints().add(con);
}
root.getChildren().add(gridPane);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
The problem with this method is that there's - to my knowledge - no easy way to to get the amount rows in a GridPane, and there's no easy way to add the same RowConstraint to every row in the GridPane. This makes the code rather messy. But you could solve this by e.g. creating your own subclass to GridPane that keeps track of the size.
In the example above we set the pref height of the row, but you could also use .setPercentHeight(double) if you don't know much space you will need for each label.
GridPane gp = new GridPane();
gp.add(" ",2, 2);
Related
i wish to add a grid layout with each row having different cells. As for now i can add or delete only a row or column, i wish to ask if i can add a cell in a particular row or column.
Eg in the below grid image, i wish to ask how i can add a cell in Column 2 . i mean i want to add more cell in column 2 than the rest of column
You can make each column a 1-column GridPane. Each column can have as many rows as you like. Put all those columns in another 1-row GridPane:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class FxTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
GridPane grid = new GridPane();
grid.setPadding(new Insets(5));
for (int i= 0; i< 5 ; i++){
GridPane column = makeColumn(3+i);
grid.add(column, i, 0);
}
Scene scene = new Scene(new Group(grid));
primaryStage.setScene(scene);
primaryStage.sizeToScene();
primaryStage.show();
}
private GridPane makeColumn(int columns) {
GridPane column = new GridPane();
column.setPadding(new Insets(2));
column.setGridLinesVisible(true);
for(int i = 0; i < columns; i++){
column.add(new Label(" "+i +" "), 0, i);
}
return column;
}
public static void main(String[] args) {
launch(null);
}
}
I'm having a problem positioning JavaFX's HBox in a similar manner to Circle.
If using a circle shape it is possible to manually position it such that it is bound to a different node. This is what I've done until now, by having a Pane as the point of reference:
Pane node; //can be dragged around/resized
//...
Circle terminal = new Circle(10);
terminal.setStroke(Color.GREEN);
terminal.setFill(Color.GREEN);
terminal.centerXProperty().bind( node.layoutXProperty() );
terminal.centerYProperty().bind( node.layoutYProperty() );
The pane (node) functions as a graph node and can be dragged around and resized. The circle functions as a port/terminal for edge connections in the graph. Seeing that the node should have more than one the idea is to put the circles into an HBox that is attached/bound to the pane like the circle has until now. This makes it so that manual layout calculations are unnecessary when adding or removing ports, resizing the node, etc. So the code then used was:
Pane node; //can be dragged around/resized
//...
HBox terminalContainer = new HBox();
terminalContainer.layoutXProperty().bind( node.layoutXProperty() );
terminalContainer.layoutYProperty().bind( node.layoutYProperty() );
//... adding circles into HBox as scenegraph children
The only difference is swapping out the HBox for the Circle and using the layoutXProperty() as there is no centerXProperty(). But of course this fails, and the ports appear glued on to the top part of the containing frame, acting strangely. Is there a fix for this? I tried changing the parenting Pane to an anchorPane, this allowed to manually anchor down the HBox in the correct place, but caused issues with the resizing/dragging code.
Minimal example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main2 extends Application {
private AnchorPane component;
#Override
public void start(Stage primaryStage) {
component = new AnchorPane();
Scene scene = new Scene(component, 1024, 768);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
//This works, but is hard to maintain
Cell c1 = new Cell();
Cell c2 = new Cell();
Port p1 = new Port(c1);
Port p2 = new Port(c2);
component.getChildren().addAll(c1, c2, p1, p2);
c1.relocate(150, 150);
c2.relocate(550, 550);
//This does not work, even if unbinding circles, but is simpler
HBox pc1 = new HBox();
HBox pc2 = new HBox();
pc1.layoutXProperty().bind( c1.layoutXProperty() );
pc1.layoutYProperty().bind( c1.layoutYProperty() );
pc2.layoutXProperty().bind( c2.layoutXProperty() );
pc2.layoutYProperty().bind( c2.layoutYProperty() );
Port p3 = new Port(c1);
Port p4 = new Port(c2);
pc1.getChildren().add(p3);
pc2.getChildren().add(p4);
component.getChildren().addAll(pc1, pc2);
}
class Cell extends Pane {
public Cell() {
Rectangle view = new Rectangle(50,50);
view.setStroke(Color.DODGERBLUE);
view.setFill(Color.DODGERBLUE);
getChildren().add(view);
}
}
class Port extends Pane {
public Port(Cell owner) {
Circle view = new Circle(10);
view.setStroke(Color.GREEN);
view.setFill(Color.GREEN);
view.centerXProperty().bind( owner.layoutXProperty() );
view.centerYProperty().bind( owner.layoutYProperty() );
getChildren().add(view);
}
}
public static void main(String[] args) {
launch(args);
}
}
Got it to work, was a typo in the code binding the layoutXProperty twice instead of the layoutYProperty facepalm
is there any way to determine the bounds (especially height and width) of a node which is already attached to a scene but set to invisible?
I want to show a label on screen only if its width exceeds 100px... but it is always 0:
#Override
public void start(Stage primaryStage) {
Group root = new Group();
Scene scene = new Scene(root, 500, 500, Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
Label n = new Label();
n.setVisible(false);
n.setStyle("-fx-background-color: red;");
root.getChildren()
.addAll(n);
n.textProperty()
.addListener((v, ov, nv) -> {
System.out.println(n.getBoundsInParent());
n.setVisible(n.getWidth() > 100);
});
n.setText("TEST11111111111111111111111");
}
The result of the sysout: (also n.getWidth() is no better)
BoundingBox [minX:0.0, minY:0.0, minZ:0.0, width:0.0, height:0.0, depth:0.0, maxX:0.0, maxY:0.0, maxZ:0.0]
Is there any trick ?
Thanks all!
Your problem is that you are listening for changes to the text property and expecting the width of the node to be updated at that time - but it's not. The width of nodes are only calculated and set during a render pass which consists of an applyCSS and layout routine (see: Get the height of a node in JavaFX (generate a layout pass)). Your code incorrectly sets the node to invisible before the updated size of the node is calculated.
Instead of using a listener on the text property to determine visibility of the node, I suggest that you use a binding expression to create a direct binding on the visibility property to the desired width property. An example of this approach is provided below. You can see that the label only displays when the text to display is longer than the required width (in this case 100 pixels).
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class BoundSample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Scene scene = new Scene(root, 200, 100);
primaryStage.setScene(scene);
primaryStage.show();
Label n = new Label();
n.setVisible(false);
n.visibleProperty().bind(n.widthProperty().greaterThan(100));
TextField textField = new TextField("TEST11111111111111111111111");
n.textProperty().bind(textField.textProperty());
textField.relocate(0, 50);
root.getChildren().addAll(n, textField);
}
}
Is it possible to shrink a GridPane row if the content of that row is both disabled and invisible?
When I set a Node to disable=true and visible=false, the cell still takes up space.
If I have 8 rows and only the first and last is visible I don't want the empty rows to take up much space. As if there was only two rows.
The only "solution" I could find was to set the size to zero. However I do not consider that a good solution. I would have to store the min/max size to set it back when/if the node becomes enabled/visible again.
Could perhaps CSS help with a better solution?
package com.company;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class App extends Application {
public static void main(String[] args) {
launch(args);
}
/* (non-Javadoc)
* #see javafx.application.Application#start(javafx.stage.Stage)
*/
#Override
public void start(Stage stage) throws Exception {
Label label1 = new Label("Text");
Label label2 = new Label("Text");
Label label3 = new Label("Text");
label2.setDisable(true);
label2.setVisible(false);
GridPane root = new GridPane();
root.setGridLinesVisible(true);
root.add(label1, 0, 0);
GridPane.setVgrow(label1, Priority.NEVER);
root.add(label2, 0, 1);
GridPane.setVgrow(label2, Priority.NEVER);
root.add(label3, 0, 2);
GridPane.setVgrow(label3, Priority.NEVER);
stage.setScene(new Scene(root));
stage.setWidth(300);
stage.setHeight(300);
stage.setTitle("JavaFX 8 app");
stage.show();
}
}
If you do not want the parent to layout a node, you can set the managed property of the node to false.
Here is an image showing the difference in layout when I used the following line in your code:
label2.setManaged(false);
I have been trying to learn more on using JavaFX and in this program I am trying to display a 3 by 3 game of tic tac toe that has already been played. I have created my ImageViews and set the images I want to use but once I started plugging them into columns and rows I noticed I cannot use the same one twice. I have an image for an empty space, an X, and an O. Once I use one more than once I get an "Exception while running application". Might be a rookie mistake, but an explanation would be greatly appreciated.
package Fresh;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class Fresh extends Application {
#Override
public void start(Stage primaryStage) {
//Create a pane and set its properties
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
pane.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
pane.setHgap(5.5);
pane.setVgap(5.5);
//imv0 = X image
final ImageView imv0 = new ImageView();
final Image image0 = new Image(Fresh.class.getResourceAsStream("images/x.gif"));
imv0.setImage(image0);
//imv1 = O image
final ImageView imv1 = new ImageView();
final Image image1 = new Image(Fresh.class.getResourceAsStream("images/o.gif"));
imv1.setImage(image1);
//imv2 = empty image
final ImageView imv2 = new ImageView();
final Image image2 = new Image(Fresh.class.getResourceAsStream("images/empty.gif"));
imv2.setImage(image2);
//Place nodes in the pane
pane.add((imv0),0,0);
pane.add((imv1), 1, 0);
//Once I try to use imv0 again "I get an exception while running".
pane.add((imv0),0,1);
//Create a scene and place it in the stage
Scene scene = new Scene(pane);
primaryStage.setTitle("ShowGridPane");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You can use the same Image as many times as you like; however, you can only place a given ImageView in one place in the scene graph. From the Javadocs:
A node may occur at most once anywhere in the scene graph.
To see why this must be true, what would you expect
GridPane.getColumnIndex(imv0)
to return, given the code you have?
So you can do:
final Image image0 = new Image(Fresh.class.getResourceAsStream("images/x.gif"));
ImageView imv00 = new ImageView(image0);
ImageView imv01 = new ImageView(image0);
pane.add(imv00, 0, 0);
pane.add(imv01, 0, 1);
// etc
The overhead here is not too bad; you use the same image data for each ImageView.