I'm using the following code to create a simple grid of buttons for a calculator:
BorderPane rootNode = new BorderPane();
GridPane gridPane = new GridPane();
rootNode.setCenter(gridPane);
int counter = 1;
for(int row = 3; row > 0; row--)
for(int col = 0; col < 3; col++) {
Button button = new Button("" + counter++);
button.setOnAction(this);
gridPane.add(button, col, row);
}
gridPane.add(new Button("R"), 0, 4);
gridPane.add(new Button("0"), 1, 4);
(Wanted to post an image of how it looks here, but I don't have enough reputation points to be allowed to do so)
The GridPane ends up being tiny and crammed up in the upper left corner, but I want it to take up all available space in the BorderPane's center region (that would be the entire window in this case). I've tried messing around with a bunch of things like various setSize methods, but havn't had much luck.
Your GridPane is already taking all the space available to it i.e. the entire Borderpane. Its the GridCells which are not using the space available to the GridPane.
You can make use of the Hgrow and Vgrow options available in the GridPane to make the cells take up the entire space available.
Here is an example which uses setHgrow to use the entire width available to the GridPane. You may add the same for the Height.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) {
BorderPane rootNode = new BorderPane();
rootNode.setMinSize(300, 300);
rootNode.setStyle("-fx-background-color: RED;");
GridPane gridPane = new GridPane();
gridPane.setStyle("-fx-background-color: BLUE;");
rootNode.setCenter(gridPane);
int counter = 1;
for (int row = 3; row > 0; row--)
for (int col = 0; col < 3; col++) {
Button button = new Button("" + counter++);
gridPane.add(button, col, row);
GridPane.setHgrow(button, Priority.ALWAYS);
}
Button r = new Button("R");
gridPane.add(r, 0, 4);
GridPane.setHgrow(r, Priority.ALWAYS);
Button r0 = new Button("0");
gridPane.add(r0, 1, 4);
GridPane.setHgrow(r0, Priority.ALWAYS);
Scene scene = new Scene(rootNode);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Sorry, I missunderstood the question.
The Buttons are just too small, so not taking all the space they have in the borderpane? The width of a button depends on the size of the text it has. You just write a number in it so they are pretty small then. Use button.setPrefWidth(Integer.MAX_VALUE) and they use all the width they can get!
Related
This question already has answers here:
How can I make buttons in a for loop with a lamda expression for each? [duplicate]
(4 answers)
Closed 2 years ago.
I have a GridPane with 20 empty text fields. I want to loop through each textfield and update the text in each with values from an ArrayList, with ~1 second pause in between each. I can't figure it out.
I create the GridPane like so:
GridPane grid = new GridPane();
Scene drawing = new Scene(new VBox(grid), 500, 200);
primaryStage.setScene(drawing);
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 10; ++j) {
TextField tf = new TextField();
tf.setPrefHeight(50);
tf.setPrefWidth(50);
tf.setAlignment(Pos.CENTER);
tf.setEditable(false);
grid.add(tf, j, i);
}
}
I now want to go through each textbox and add text with a pause in between. Using Thread.sleep() in a loop is causing the application to crash. I've tried PauseTransition like this:
ArrayList<Integer> numsDrawn= game.draw();
int count = 0;
for (Node node : grid.getChildren()) {
PauseTransition pause = new PauseTransition(Duration.seconds(1));
pause.setOnFinished(e -> ((TextField)node).setText(Integer.toString(numsDrawn.get(count))));
pause.play();
count++;
}
But I am getting the error Local variable count defined in an enclosing scope must be final or effectively final.
Count has to be able to change so I can iterate through the numsDrawn list and add different text to each TextField. I've tried creating a separate event handler instead of a lambda, but getting the same error with count.
If someone could offer advice on how to do this seemingly simple task, I'd greatly appreciate it.
I would recommend Timeline in this situation. Set the duration to one second and the cycle count to the number of TextFields in the GridPane.
import java.util.concurrent.atomic.AtomicInteger;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Control;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class App extends Application {
Timeline timeline;
AtomicInteger counter = new AtomicInteger();
#Override
public void start(Stage primaryStage) throws Exception {
GridPane gridPane = new GridPane();
gridPane.add(new TextField(), 0, 0);
gridPane.add(new TextField(), 0, 1);
gridPane.add(new TextField(), 0, 2);
gridPane.add(new TextField(), 0, 3);
gridPane.add(new TextField(), 0, 4);
gridPane.add(new TextField(), 0, 5);
gridPane.add(new TextField(), 0, 6);
gridPane.add(new TextField(), 0, 7);
gridPane.setMaxSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
timeline = new Timeline(new KeyFrame(Duration.seconds(1), (ActionEvent t) -> {
System.out.println(counter.get());
TextField tempTextField = (TextField)gridPane.getChildren().get(counter.get());
tempTextField.setText(Integer.toString(counter.getAndIncrement()));
}));
timeline.setCycleCount(gridPane.getChildren().size());
Button btnStartTimeline = new Button("Start Timeline");
btnStartTimeline.setOnAction((t) -> {
timeline.play();
});
VBox root = new VBox(gridPane, btnStartTimeline);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 700, 700);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
According to the error message you should pass a final variable to the numsDrawn.get method, so I would try this:
ArrayList<Integer> numsDrawn= game.draw();
int count = 0;
for (Node node : grid.getChildren()) {
PauseTransition pause = new PauseTransition(Duration.seconds(1));
final int countFinal = count;
pause.setOnFinished(e -> ((TextField)node).setText(Integer.toString(numsDrawn.get(countFinal))));
pause.play();
count++;
}
So I figured it out on my own. When creating the grid with textfields, I also add each textfield to an ArrayList tfs so that I can access each field individually to add the text later. I then create a new thread to add the text to each field from numsDrawn like so:
new Thread(() -> {
for (int i = 0; i < 20; ++i) {
final int j = i;
Platform.runLater(() -> tfs.get(j).setText(Integer.toString(numsDrawn.get(j))));
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}).start();
I am making a sodoku solver program in Java with the JavaFX library. The program incorporates an interactive sodoku board consisting of a series of TextFields in a GridPane. The board looks like this:
Right now, the cursor is in the top left most TextField. If the field had text in it, the user would be able to move the cursor through the text by using the arrow keys. However, I want the user to be able to use the arrow keys to navigate to a different TextField. The issue is, the field is in "typing mode" (I don't know the official terminology) so the arrow keys only move the cursor to a different point in the text, but otherwise it stays in the same field.
This is what I mean:
Pretend that line I drew is the cursor. Right now, if I click the left arrow key, the cursor will move to the left of the 1, but I want it to move the the TextField on the left, instead. If I click the down arrow key, nothing happens because there is no text below the 1 for the cursor to navigate to, but I want it to move to the TextField below, instead.
The code for the GridPane is this:
TextField[][] squares = new TextField[9][9];
GridPane grid = new GridPane();
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
squares[i][j] = new TextField();
squares[i][j].setPrefHeight(8);
squares[i][j].setPrefWidth(25);
grid.add(squares[i][j], j, i);
}
}
grid.setAlignment(Pos.CENTER);
The squares array is for me to have access to individual TextFields in the GridPane.
Any suggestions for how I can fix this?
To avoid the focused TextField from handling arrow keys at all you need to intercept the KeyEvent before it reaches said TextField. This can be accomplished by adding an event filter to the GridPane and consuming the event as appropriate. If you're not sure why this works you can check out the JavaFX: Handling Events tutorial.
Then you can use Node#requestFocus() to programmatically change the focused node.
I also recommend setting the prefColumnCount of each TextField rather than trying to set the preferred dimensions manually. That way the preferred dimensions are computed based on the font size.
Here's an example:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class App extends Application {
private TextField[][] fields;
#Override
public void start(Stage primaryStage) {
GridPane grid = new GridPane();
grid.setHgap(3);
grid.setVgap(3);
grid.setPadding(new Insets(5));
grid.setAlignment(Pos.CENTER);
grid.addEventFilter(KeyEvent.KEY_PRESSED, this::handleArrowNavigation);
fields = new TextField[9][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
fields[i][j] = createTextField();
grid.add(fields[i][j], j, i);
}
}
primaryStage.setScene(new Scene(grid));
primaryStage.show();
}
private void handleArrowNavigation(KeyEvent event) {
Node source = (Node) event.getSource(); // the GridPane
Node focused = source.getScene().getFocusOwner();
if (event.getCode().isArrowKey() && focused.getParent() == source) {
int row = GridPane.getRowIndex(focused);
int col = GridPane.getColumnIndex(focused);
// Switch expressions were standardized in Java 14
switch (event.getCode()) {
case LEFT -> fields[row][Math.max(0, col - 1)].requestFocus();
case RIGHT -> fields[row][Math.min(8, col + 1)].requestFocus();
case UP -> fields[Math.max(0, row - 1)][col].requestFocus();
case DOWN -> fields[Math.min(8, row + 1)][col].requestFocus();
}
event.consume();
}
}
private TextField createTextField() {
TextField field = new TextField();
// Rather than setting the pref sizes manually this will
// compute the pref sizes based on the font size.
field.setPrefColumnCount(1);
field.setFont(Font.font(20));
field.setTextFormatter(
new TextFormatter<>(
change -> {
// Only allow the text to be empty or a single digit between 1-9
if (change.getControlNewText().matches("[1-9]?")) {
// Without this the text goes "off screen" to the left. This also
// seems to have the added benefit of selecting the just-entered
// text, which makes replacing it a simple matter of typing another
// digit.
change.setCaretPosition(0);
return change;
}
return null;
}));
return field;
}
}
The above also adds a TextFormatter to each TextField to show a way to limit the text to digits between 1 and 9. Note the arrow navigation does not "wrap around" when it reaches the end of a row or column. You can of course modify the code to implement this, if desired.
You may want to consider creating a model for the game. That way the business logic is not tied directly to JavaFX UI objects. When you update the model it would notify the view (possibly via a "view model", depending on the architecture) and the view will update itself accordingly.
You need to set the event handler to move on arrow key press as you can see below look at the setTextHandler function there is no error handling I just wrote this up to give you an idea of what you should be doing it is called from the loop when you create the TextFields from there it checks for an arrow key press and from there it will .requestFocus() from the next TextField
public class Main extends Application {
private TextField[][] squares;
#Override
public void start(Stage primaryStage) throws Exception {
squares = new TextField[9][9];
GridPane grid = new GridPane();
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
squares[i][j] = new TextField();
squares[i][j].setPrefHeight(8);
squares[i][j].setPrefWidth(25);
setTextHandler(squares[i][j], i, j);
grid.add(squares[i][j], j, i);
}
}
grid.setAlignment(Pos.CENTER);
Scene scene = new Scene(grid);
primaryStage.setScene(scene);
primaryStage.show();
}
private void setTextHandler(TextField textField, int i, int j){
textField.setOnKeyPressed(keyEvent -> {
System.out.println(keyEvent.getCode());
if(keyEvent.getCode().isArrowKey()) {
if (keyEvent.getCode() == KeyCode.UP) {
squares[i-1][j].requestFocus();
} else if (keyEvent.getCode() == KeyCode.DOWN) {
squares[i+1][j].requestFocus();
} else if (keyEvent.getCode() == KeyCode.LEFT) {
squares[i][j-1].requestFocus();
} else if (keyEvent.getCode() == KeyCode.RIGHT) {
squares[i][j+1].requestFocus();
}
}
});
}
}
I would like to add an invisible row in a gridpane. Then I would like to show and hide it based on user selection. I've managed to get the general functionality working but the problem is when the row content are hidden there is an extra space. I am not sure if this is the actual row or the vgap? Anyway I would like to get rid of this extra space.
Here is my gridpane :
public class CustomGrid extends GridPane{
public CustomGrid() {
this.setHgap(20);
this.setVgap(20);
ColumnConstraints firstColConstraints = new ColumnConstraints();
firstColConstraints.setHalignment(HPos.RIGHT);
firstColConstraints.setPercentWidth(50.0);
firstColConstraints.setHgrow(Priority.ALWAYS);
ColumnConstraints secondColConstraints = new ColumnConstraints();
ColumnConstraints thirdColConstraints = new ColumnConstraints();
thirdColConstraints.setHgrow(Priority.ALWAYS);
thirdColConstraints.setHalignment(HPos.LEFT);
this.getColumnConstraints().addAll(firstColConstraints, secondColConstraints, thirdColConstraints);
VBox.setMargin(this, new Insets(10, 0, 50, 0));
}
I've added content then hidden it like so
Pane content = new Pane()
pane.setVisible(false);
pane.setManaged(false);
add(content, 0, 1);
I'm happy with it when the user makes it visible but when it is hidden there is an extra gap between the rows. Can anyone tell me a way to fix that.
Thank you.
As mentioned in the comment (link to comment):
Change the height to 0 when hidden.
I think the best way to handle this is to actually remove the row from the grid pane rather than trying to make the row "invisible". Inserting and removing rows at arbitrary locations in a GridPane is a little tricky as the GridPane API does not have native support of such functions. So I created a little utility based upon your custom GridPane implementation which demonstrates adding and removing rows within the GridPane. To do so, the custom implementation has to keep an internal notion of which nodes are located at which rows and to update the column constraints of each of the nodes and each of the rows whenever new rows are added or deleted. Hiding can be accomplished by removing a row from the grid pane and then, at a later stage, adding the same row back into the grid pane at the index from which it was earlier removed. I understand this isn't exactly what you were looking for, but it seemed the most reasonable solution to me.
/** Click on a Label to remove the row containing the label from the GridPane */
import javafx.application.Application;
import javafx.collections.*;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.*;
public class HiddenNodes extends Application {
private static final int N_COLS = 3;
private static final int INIT_N_ROWS = 10;
private int nextRowId = 0;
private Node[] lastRemovedRow = null;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
CustomGrid grid = new CustomGrid();
for (int i = 0; i < INIT_N_ROWS; i++) {
Label[] labels = createRow(grid);
grid.insertRow(i, labels);
}
Button prepend = new Button("Prepend");
prepend.setOnAction(event -> {
Label[] labels = createRow(grid);
grid.insertRow(0, labels);
});
Button append = new Button("Append");
append.setOnAction(event -> {
Label[] labels = createRow(grid);
grid.insertRow(grid.getRowsSize(), labels);
});
HBox controls = new HBox(10, prepend, append);
VBox layout = new VBox(10, controls, grid);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout, 200, 600));
stage.show();
}
private Label[] createRow(CustomGrid grid) {
Label[] labels = new Label[N_COLS];
for (int j = 0; j < N_COLS; j++) {
final int fj = j;
labels[j] = new Label(nextRowId + ":" + j);
labels[j].setStyle("-fx-background-color: coral;");
labels[j].setOnMouseClicked((MouseEvent e) -> {
lastRemovedRow = grid.removeRowContaining(labels[fj]);
});
}
nextRowId++;
return labels;
}
class CustomGrid extends GridPane {
private ObservableList<Node[]> rows = FXCollections.observableArrayList();
CustomGrid() {
this.setHgap(20);
this.setVgap(20);
ColumnConstraints firstColConstraints = new ColumnConstraints();
firstColConstraints.setHalignment(HPos.RIGHT);
firstColConstraints.setPercentWidth(50.0);
firstColConstraints.setHgrow(Priority.ALWAYS);
ColumnConstraints secondColConstraints = new ColumnConstraints();
ColumnConstraints thirdColConstraints = new ColumnConstraints();
thirdColConstraints.setHgrow(Priority.ALWAYS);
thirdColConstraints.setHalignment(HPos.LEFT);
this.getColumnConstraints().addAll(firstColConstraints, secondColConstraints, thirdColConstraints);
VBox.setMargin(this, new Insets(10, 0, 50, 0));
}
void insertRow(int rowIdx, Node... nodes) {
for (int i = rows.size() - 1; i >= rowIdx; i--) {
for (int j = 0; j < rows.get(i).length; j++) {
setConstraints(rows.get(i)[j], j, i + 1);
}
}
addRow(rowIdx, nodes);
rows.add(rowIdx, nodes);
}
private Node[] removeRow(int rowIdx) {
for (int i = rows.size() - 1; i > rowIdx; i--) {
for (int j = 0; j < rows.get(i).length; j++) {
setConstraints(rows.get(i)[j], j, i - 1);
}
}
for (Node node: rows.get(rowIdx)) {
getChildren().remove(node);
}
return rows.remove(rowIdx);
}
Node[] removeRowContaining(Node node) {
Iterator<Node[]> rowIterator = rows.iterator();
int rowIdx = 0;
while (rowIterator.hasNext()) {
Node[] row = rowIterator.next();
for (Node searchNode : row) {
if (searchNode == node) {
return removeRow(rowIdx);
}
}
rowIdx++;
}
return null;
}
int getRowsSize() {
return rows.size();
}
}
}
The above program is just an outline, additional logic would be required to reshow the "hidden" node (which has been removed from the grid) at the appropriate row index (which is a reasonably tricky problem).
Note: You might consider using a TableView rather than a GridPane. By manipulating the item list backing the TableView it is quite trivial to add and remove nodes to the TableView. Of course, I understand a TableView is not the appropriate solution for all problems of this kind (as the TableView comes with a bunch of other functionality, like headers and sorting, which you may not wish to have).
I have a VBox which contains Panes. Over the VBox, I want to show another element (currently I use a Pane). This element has to overlay multiple Panes in the VBox, so I put this element together with the VBox inside an AnchorPane. The complete structure looks like this:
HBox
VBox
Label <--- "Friday"
AnchorPane
VBox <--- the VBox with Panes
Pane
...
Pane <--- the Pane over the VBox (red in images)
The problem is that when I resize the window, the red Pane does not resize nor change its position.
Normal size:
Small size:
I want the red Pane to start at the same line (3rd) and be of the same relative size.
I tried binding the Pane's prefWidthProperty and prefHeightProperty to its parent (AnchorPane). This works for auto-resizing.
For auto-positioning, I have tried to bind the layoutX|Y properties, which didn't work, because these are set by the system and I get exception "Bound value cannot be set". So I tried to make the Pane unmanaged, which in turn broke the auto-resize, since in unmanaged nodes, changes in preferred properties have no effect. I thought of binding widthProperty and heightProperty to the parent's properties, but these are read-only.
Consider using a GridPane for functionality like this. You can add multiple nodes to the same cell(s) in the grid (the ones added last will appear on top in z-order). A GridPane allows maximum flexibility for layout. Here's an example: note that there's a lot of styling here that I just hard-coded for brevity, but in a real app you should move this to an external stylesheet (you can do the same with the max sizes, etc):
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.RowConstraints;
import javafx.stage.Stage;
public class CalendarExample extends Application {
#Override
public void start(Stage primaryStage) {
GridPane calendar = new GridPane();
// headers:
for (DayOfWeek dayOfWeek = DayOfWeek.MONDAY ; dayOfWeek.getValue() <= DayOfWeek.FRIDAY.getValue();
dayOfWeek = DayOfWeek.of(dayOfWeek.getValue() + 1) ) {
Label label = new Label(dayOfWeek.toString());
label.setAlignment(Pos.CENTER);
label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
label.setStyle("-fx-background-color: black, darkgray; -fx-background-insets: 0, 0 0 1 1 ;");
calendar.add(label, dayOfWeek.getValue(), 0);
}
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm");
int rowCount = 0 ;
for (LocalTime time = LocalTime.of(8, 0); time.isBefore(LocalTime.of(17, 0)); time=time.plusMinutes(30)) {
rowCount++ ;
Label label = new Label(timeFormatter.format(time));
label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
label.setAlignment(Pos.CENTER);
String color = rowCount % 2 == 0 ? "darkgray" : "lightgray" ;
label.setStyle("-fx-background-color: black, "+color+"; -fx-background-insets: 0, 0 0 1 1;");
calendar.add(label, 0, rowCount);
}
// cells:
for (int x = 1 ; x <= 5; x++) {
for (int y = 1 ; y <= rowCount; y++) {
Region region = new Region();
String color = y % 2 == 0 ? "darkgray" : "lightgray" ;
region.setStyle("-fx-background-color: black, "+color+"; -fx-background-insets: 0, 0 0 1 1 ;");
calendar.add(region, x, y);
}
}
// Column constraints:
for (int x = 0 ; x <= 5 ; x++) {
ColumnConstraints cc = new ColumnConstraints();
cc.setPercentWidth(100.0 / 6);
cc.setFillWidth(true);
calendar.getColumnConstraints().add(cc);
}
// row constraints:
for (int y = 0 ; y <= rowCount; y++) {
RowConstraints rc = new RowConstraints();
rc.setPercentHeight(100.0 / (rowCount+1));
rc.setFillHeight(true);
calendar.getRowConstraints().add(rc);
}
// Example appointment block:
DayOfWeek appointmentDay = DayOfWeek.FRIDAY ;
LocalTime startTime = LocalTime.of(10, 0);
LocalTime endTime = LocalTime.of(13, 0);
String appointmentText = "Fridays need really long coffee breaks";
Label appointment = new Label(appointmentText);
appointment.setTooltip(new Tooltip(appointmentText));
appointment.setWrapText(true);
appointment.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
appointment.setStyle("-fx-background: red; -fx-background-color: -fx-background; " );
GridPane.setMargin(appointment, new Insets(2, 5, 2, 2));
Scene scene = new Scene(calendar, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This also behaves as needed when the window is resized:
width and height properties may be readonly, but you can assign the prefered sizes and use autosize.
Example
The following code makes the overlay start at 50% height and have a height of 1/3 of the VBox height:
#Override
public void start(Stage primaryStage) {
Region region1 = new Region();
VBox.setVgrow(region1, Priority.ALWAYS);
region1.setStyle("-fx-background-color: blue;");
Region region2 = new Region();
VBox.setVgrow(region2, Priority.ALWAYS);
region2.setStyle("-fx-background-color: lime;");
Region regionOverlay = new Region();
regionOverlay.setStyle("-fx-background-color: red;");
regionOverlay.setManaged(false);
VBox root = new VBox(region1, region2, regionOverlay);
root.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
regionOverlay.setPrefSize(newValue.getWidth() - 20, newValue.getHeight() / 3);
regionOverlay.setLayoutX(10);
regionOverlay.setLayoutY(newValue.getHeight() / 2);
regionOverlay.autosize();
});
root.setPrefSize(400, 400);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
If all you need is a red rectangle, i'd advise you to use the Rectangle shape instead of panes. This allows you to bind its widths and heights explicitly.
I am creating a board game in JavaFX using GridPane.
There are 7 different animations which could be placed in each grid (cell) of the grid.
Initially the grid looks like this
I tested adding a simple circle to it before programming my animation insertions. And it looks like this
The nodes added are SubScenes which include TimeLine animation. Each cell size is 40x40 and the SubScene size is also 40x40.
The subscenes when added, get on top of the gridpane border lines and it doesn't look good.
What can I do so that the nodes are added below the grid lines? i.e. the gridlines are on top of the nodes.
If it is not possible with GridPane, is there anything else I can use?
class which i execute for the game
class Game {
static GridPane grid;
public void start(final Stage stage) throws Exception {
int rows = 5;
int columns = 5;
stage.setTitle("Enjoy your game");
grid = new GridPane();
for(int i = 0; i < columns; i++) {
ColumnConstraints column = new ColumnConstraints(40);
grid.getColumnConstraints().add(column);
}
for(int i = 0; i < rows; i++) {
RowConstraints row = new RowConstraints(40);
grid.getRowConstraints().add(row);
}
grid.setOnMouseReleased(new EventHandler<MouseEvent> () {
public void handle(MouseEvent me) {
grid.add(Anims.getAnim(1), (int)((me.getSceneX() - (me.getSceneX() % 40)) / 40), (int)((me.getSceneY() - (me.getSceneY() % 40)) / 40)); //here the getAnim argument could be between 1-7
}
});
grid.setStyle("-fx-background-color: white; -fx-grid-lines-visible: true");
Scene scene = new Scene(grid, (columns * 40) + 100, (rows * 40) + 100, Color.WHITE);
stage.setScene(scene);
stage.show();
}
public static void main(final String[] arguments) {
Application.launch(arguments);
}
}
class which contains animations, here I am just creating a circle
public class Anims {
public static SubScene getAnim(final int number) throws Exception {
Circle circle = new Circle(20, 20f, 7);
circle.setFill(Color.RED);
Group group = new Group();
group.getChildren().add(circle);
SubScene scene = new SubScene(group, 40, 40);
scene.setFill(Color.WHITE);
return scene;
}
}
Don't use setGridLinesVisible(true): the documentation explicitly states this is for debug only.
Instead, place a pane in all the grid cells (even the empty ones), and style the pane so you see the borders. (This gives you the opportunity to control the borders very carefully, so you can avoid double borders, etc.) Then add the content to each pane. You can also register the mouse listeners with the pane, which means you don't have to do the ugly math to figure out which cell was clicked.
The recommended way to apply a border to any region is to use CSS and a "nested background" approach. In this approach, you draw two (or more) background fills on the region, with different insets, giving the appearance of a border. So for example:
-fx-background-fill: black, white ;
-fx-background-insets: 0, 1 ;
will first draw a black background with no insets, and then over that will draw a white background with insets of 1 pixel on all sides, giving the appearance of a black border of width 1 pixel. While this may seem counter-intuitive, the performance of this is (allegedly) better than specifying border directly. You can also specify a sequence of four values for the insets for each fill, which are interpreted as the insets on the top, right, bottom, and left, respectively. So
-fx-background-fill: black, white ;
-fx-background-insets: 0, 0 1 1 0 ;
has the effect of a black border on the right and bottom, etc.
I'm also not sure SubScene is what you really want, unless you are intending attaching different cameras to each cell. If you really need a subscene, make the fill transparent to avoid drawing over the edges of the cell. You could just add the Group directly to each cell (you could probably just add the circle, depending on exactly what you need...).
Something like:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.RowConstraints;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Game2 extends Application{
#Override
public void start(final Stage stage) throws Exception {
int rows = 5;
int columns = 5;
stage.setTitle("Enjoy your game");
GridPane grid = new GridPane();
grid.getStyleClass().add("game-grid");
for(int i = 0; i < columns; i++) {
ColumnConstraints column = new ColumnConstraints(40);
grid.getColumnConstraints().add(column);
}
for(int i = 0; i < rows; i++) {
RowConstraints row = new RowConstraints(40);
grid.getRowConstraints().add(row);
}
for (int i = 0; i < columns; i++) {
for (int j = 0; j < rows; j++) {
Pane pane = new Pane();
pane.setOnMouseReleased(e -> {
pane.getChildren().add(Anims.getAtoms(1));
});
pane.getStyleClass().add("game-grid-cell");
if (i == 0) {
pane.getStyleClass().add("first-column");
}
if (j == 0) {
pane.getStyleClass().add("first-row");
}
grid.add(pane, i, j);
}
}
Scene scene = new Scene(grid, (columns * 40) + 100, (rows * 40) + 100, Color.WHITE);
scene.getStylesheets().add("game.css");
stage.setScene(scene);
stage.show();
}
public static class Anims {
public static Node getAtoms(final int number) {
Circle circle = new Circle(20, 20f, 7);
circle.setFill(Color.RED);
Group group = new Group();
group.getChildren().add(circle);
// SubScene scene = new SubScene(group, 40, 40);
// scene.setFill(Color.TRANSPARENT);
return group;
}
}
public static void main(final String[] arguments) {
Application.launch(arguments);
}
}
and the css:
.game-grid {
-fx-background-color: white ;
-fx-padding: 10 ;
}
.game-grid-cell {
-fx-background-color: black, white ;
-fx-background-insets: 0, 0 1 1 0 ;
}
.game-grid-cell.first-row {
-fx-background-insets: 0, 1 1 1 0 ;
}
.game-grid-cell.first-column {
-fx-background-insets: 0, 0 1 1 1 ;
}
.game-grid-cell.first-row.first-column {
-fx-background-insets: 0, 1 ;
}
Simply add an H and V gap of one pixel width and let the grid pane's background color "shine" through:
.my-grid-pane {
-fx-background-color: lightgray;
-fx-vgap: 1;
-fx-hgap: 1;
-fx-padding: 1;
}
If the grid pane's background color spreads from outside more than one pixel (will happen if its parent is larger than itself), just wrap the grid in a Group!
I apologize for the response instead of the comment, not enough reputation.
Strangely, but #James_D 's response didn't help me; when the window was resized, the cell borders randomly changed their width, overlapping each other.
This answer helped solve the problem, so by slightly changing the code given by #James_D (only the .css file), we get:
.classes-grid {
-fx-background-color: white ;
-fx-padding: 10 ;
}
.classes-grid-cell {
-fx-border-color: dimgray;
-fx-border-width: 0 1 1 0;
-fx-background-color: transparent;
}
.classes-grid-cell.first-row {
-fx-border-width: 1 1 1 0 ;
}
.classes-grid-cell.first-column {
-fx-border-width: 0 1 1 1 ;
}
.classes-grid-cell.first-row.first-column {
-fx-border-width: 1 ;
}
Same idea with Mordechai's answer. But if you want to set these things by JavaFX code, not CSS stylesheet. Then you can do sth like this:
Set up the Hgap and Vgap: gridpane.setHgap(1) and gridpane.setVgap(1)
Set up the background color: gridpane.setBackground(new Background(new BackgroundFill(Color.rgb(0,0,0), new CornerRadii(2.5), new Insets(-1.0)))) (CornerRadii and Insets value depends on your choice, background color determined by rgb value)