I made a 2D grid of ASCII characters before in which i was able to move an ascii character around. Now i want to take it to the next level by making a "visualised" version of it in a javafx window. I have tried making 2 image objects, one with a black square inside of it, and one with a white one and then putting those 2 objects multiple times inside a 2D grid like this:
Image[][] Grid = {
{B,W,B,B,B,B,B,B,B,B,B},
{B,B,B,B,W,W,W,B,B,B,B},
{B,B,B,B,B,B,B,B,B,B,B}
};
The problem is that the only way i know how to display them, is by making an imageview object for each index and if i were to say, want a 25X25 grid, that would mean i would have to make 625 imageview objects which would obviously be ridiculous.
I also tried simply putting the grid indexes one by one into the pane like this:
HBox gameLayout = new HBox(Grid[1][1], Grid[1][2], Grid[1][3]);
but that gives me a "invocationTargetException".
My goal is to be able to make snake by specifically targeting and manipulating grid elements. I want the square-color/imageView/rectangle/whatever to change when i change the value of a "B"array element to "W" (white) but the things i'v tried are either very inefficient or just don't work.
It's not at all clear what your objection is to creating multiple ImageViews. Since they can refer to the same Image instance, this should be fairly efficient (the image data doesn't need to be replicated).
This seems to work just fine:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class TiledBoard extends Application {
private final int tileSize = 30 ;
#Override
public void start(Stage primaryStage) {
Image b = createImage(Color.BLACK);
Image w = createImage(Color.WHITE);
Image[][] grid = {
{b,w,b,b,b,b,b,b,b,b,b},
{b,b,b,b,w,w,w,b,b,b,b},
{b,b,b,b,b,b,b,b,b,b,b}
};
GridPane gridPane = new GridPane();
// for visualizing the different squares:
gridPane.setHgap(2);
gridPane.setVgap(2);
gridPane.setStyle("-fx-background-color: grey;");
for (int y = 0 ; y < grid.length ; y++) {
for (int x = 0 ; x < grid[y].length ; x++) {
ImageView imageView = new ImageView(grid[y][x]);
imageView.setFitWidth(tileSize);
imageView.setFitHeight(tileSize);
gridPane.add(imageView, x, y);
}
}
Scene scene = new Scene(gridPane);
primaryStage.setScene(scene);
primaryStage.show();
}
private Image createImage(Color color) {
WritableImage image = new WritableImage(1, 1);
image.getPixelWriter().setColor(0, 0, color);
return image ;
}
public static void main(String[] args) {
launch(args);
}
}
You could do this with some kind of Shape (e.g. Rectangle), if you prefer:
#Override
public void start(Stage primaryStage) {
Color b = Color.BLACK;
Color w = Color.WHITE;
Color[][] grid = {
{b,w,b,b,b,b,b,b,b,b,b},
{b,b,b,b,w,w,w,b,b,b,b},
{b,b,b,b,b,b,b,b,b,b,b}
};
GridPane gridPane = new GridPane();
// for visualizing the different squares:
gridPane.setHgap(2);
gridPane.setVgap(2);
gridPane.setStyle("-fx-background-color: grey;");
for (int y = 0 ; y < grid.length ; y++) {
for (int x = 0 ; x < grid[y].length ; x++) {
Rectangle rect = new Rectangle(tileSize, tileSize, grid[y][x]);
gridPane.add(rect, x, y);
}
}
Scene scene = new Scene(gridPane);
primaryStage.setScene(scene);
primaryStage.show();
}
or with a Region:
#Override
public void start(Stage primaryStage) {
Color b = Color.BLACK;
Color w = Color.WHITE;
Color[][] grid = {
{b,w,b,b,b,b,b,b,b,b,b},
{b,b,b,b,w,w,w,b,b,b,b},
{b,b,b,b,b,b,b,b,b,b,b}
};
GridPane gridPane = new GridPane();
// for visualizing the different squares:
gridPane.setHgap(2);
gridPane.setVgap(2);
gridPane.setStyle("-fx-background-color: grey;");
for (int y = 0 ; y < grid.length ; y++) {
for (int x = 0 ; x < grid[y].length ; x++) {
Region rect = new Region();
rect.setMinSize(tileSize, tileSize);
rect.setPrefSize(tileSize, tileSize);
rect.setMaxSize(tileSize, tileSize);
rect.setBackground(new Background(new BackgroundFill(grid[y][x], CornerRadii.EMPTY, Insets.EMPTY)));
gridPane.add(rect, x, y);
}
}
Scene scene = new Scene(gridPane);
primaryStage.setScene(scene);
primaryStage.show();
}
Which of these is better really just depends on what else you want to do with them.
Related
I have this piece of code here that has a borderpane as a parent and 2 other panes, one is a minesweeper game and the other is an empty pane with a black background, the minesweeper game is set to be in the center of the borderpane and the empty pane is set to be in the bottom. Right now when I run the code, it will only show the minesweeper game and not the empty pane (can be seen in the image). How do I make it show the empty pane at the bottom of the borderpane?
(Layout as of right now)
package project;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class MinesweeperApp extends Application {
private static final int TILE_SIZE = 40;
private static final int W = 800;
private static final int H = 800;
private static final int X_TILES = W / TILE_SIZE;
private static final int Y_TILES = H / TILE_SIZE;
private Tile[][] grid = new Tile[X_TILES][Y_TILES];
// private ArrayList[][] grid = new ArrayList[X_TILES][Y_TILES];
private Scene scene;
private Parent createContent() {
BorderPane root = new BorderPane();
Pane middlepane = new Pane();
Pane lowerPane = new Pane();
lowerPane.setStyle("-fx-background-color: black;");
root.setTop(lowerPane);
root.setBottom(middlepane);
middlepane.setPrefSize(W, H);
for (int y = 0; y < Y_TILES; y++) {
for (int x = 0; x < X_TILES; x++) {
Tile tile = new Tile(x, y, Math.random() < 0.2);
grid[x][y] = tile;
middlepane.getChildren().add(tile);
}
}
for (int y = 0; y < Y_TILES; y++) {
for (int x = 0; x < X_TILES; x++) {
Tile tile = grid[x][y];
if (tile.hasBomb)
continue;
long bombs = getNeighbors(tile).stream().filter(t -> t.hasBomb).count();
if (bombs > 0)
tile.text.setText(String.valueOf(bombs));
}
}
return root;
}
private List<Tile> getNeighbors(Tile tile) {
List<Tile> neighbors = new ArrayList<>();
// ttt
// tXt
// ttt
int[] points = new int[] {
-1, -1,
-1, 0,
-1, 1,
0, -1,
0, 1,
1, -1,
1, 0,
1, 1
};
for (int i = 0; i < points.length; i++) {
int dx = points[i];
int dy = points[++i];
int newX = tile.x + dx;
int newY = tile.y + dy;
if (newX >= 0 && newX < X_TILES
&& newY >= 0 && newY < Y_TILES) {
neighbors.add(grid[newX][newY]);
}
}
return neighbors;
}
private class Tile extends StackPane {
private int x, y;
private boolean hasBomb;
private boolean isOpen = false;
private Rectangle border = new Rectangle(TILE_SIZE - 2, TILE_SIZE - 2);
private Text text = new Text();
public Tile(int x, int y, boolean hasBomb) {
this.x = x;
this.y = y;
this.hasBomb = hasBomb;
border.setStroke(null);
border.setFill(Color.NAVY);
text.setFont(Font.font(18));
text.setText(hasBomb ? "X" : "");
text.setVisible(false);
getChildren().addAll(border, text);
setTranslateX(x * TILE_SIZE);
setTranslateY(y * TILE_SIZE);
setOnMouseClicked(e -> open());
}
public void open() {
System.out.println("clicked");
System.out.println("x: " + this.x + " " + "y: " + this.y);
if (isOpen){
return;
}
if (hasBomb) {
System.out.println("Game Over");
scene.setRoot(createContent());
return;
}
isOpen = true;
text.setVisible(true);
border.setFill(Color.RED);
if (text.getText().isEmpty()) {
getNeighbors(this).forEach(Tile::open);
}
}
}
#Override
public void start(Stage stage) throws Exception {
scene = new Scene(createContent());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Absent children, an empty Pane has zero width and height. Try adding non-empty content to the top and bottom to see the effect:
root.setTop(new Label("top"));
root.setCenter(middlepane);
root.setBottom(new Label("bottom"));
As an aside, your original root.setBottom(middlepane) may be misleading.
Setting Constraints
Set size constraints on a pane to “see” a Pane which has no content.
This will make the pane take up space in the layout.
To make the app behave as you desire, configure the pane's:
min width or height, OR
pref width or height, OR
any combination of those.
For example, to set a min height for the top pane in a border pane:
BorderPane borderPane = new BorderPane();
Pane top = new Pane();
top.setMinHeight(15);
borderPane.setTop(top);
Hiding Content
Or you could put some content in the pane and make it invisible until it is needed, while still reserving the space required to display it.
For example:
Label header = new Label("header");
header.setVisible(false);
Pane top = new Pane(header);
borderPane.setTop(top);
This works because things in the scene graph which are not visible still take up layout space, even though they are not displayed, unless you also call setManaged(false).
For the specific case of a label, you wouldn’t need the visibility setting, you could just set the label to an empty string and it would still take up room, even though nothing would be shown.
For myself, I try to avoid sizing hints where I can, so I would use an empty label. Or, set the visibility, for a more complex pane. That way I let the layout engine calculate the appropriate amount of space to reserve.
Spacing Content
This doesn't apply in your case with border pane, but for other layouts like HBox and VBox you might want to push a node all the way right or to the bottom of the layout. You can do that by adding an empty pane before the last node and setting a layout constraint on the pane like this:
HBox.setHgrow(spacer, Priority.ALWAYS);
This technique is explained more in the answer to:
How to align a button right in JavaFX?
I'm trying to recreate the "snake" game using GridPane in JavaFX. My code seems to run properly except for this specific error, where anytime I use my keys to traverse the green node (titled head) toward the yellow node (titled food), the rows or columns of the grid seem to shorten by one unit, causing the grid to collapse in some manner. Is there a way to stop the GridPane from resizing? Below is my code:
package snake;
import java.util.ArrayList;
import java.util.Random;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.TilePane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class snakemain extends Application {
Random random = new Random();
int posX = random.nextInt(21), posY = random.nextInt(21), foodposX = random.nextInt(24), foodposY = random.nextInt(24);
public static void main(String... args) {
launch(args);
}
public void start(Stage stage) {
//Creates Scene and stage setting + gridpane
stage.setTitle("Snake by Yeldor");
GridPane gridpane = new GridPane();
Random random = new Random();
Scene scene = new Scene(gridpane, 500, 500);
scene.setFill(Color.WHITE);
gridpane.setGridLinesVisible(true);
gridpane.setHgap(20);
gridpane.setVgap(20);
//creates head of snake, Arraylist of snakes body parts, and food consumable for snake
ArrayList<Rectangle> snakeBody = new ArrayList<Rectangle>();
Rectangle head = new Rectangle(20,20,Color.GREEN.brighter().brighter());
//invisible block to manage grid
Rectangle food = new Rectangle(20,20, Color.YELLOW.brighter().brighter());
//adds rectangles to grippane
gridpane.add(head, posX, posY);
gridpane.add(food, foodposX, foodposY);
//makes food non mangaged by the gridpane
scene.setOnKeyPressed(e ->
{
String s = e.getCode().toString();
try {
switch(s) {
case "W": gridpane.getChildren().remove(head);
gridpane.add(head, posX, --posY);
break;
case "A": gridpane.getChildren().remove(head);
gridpane.add(head, --posX, posY);
break;
case "S": gridpane.getChildren().remove(head);
gridpane.add(head, posX, ++posY);
break;
case "D": gridpane.getChildren().remove(head);
gridpane.add(head, ++posX, posY);
break;
}
if (posX == 24 || posY == 24 || posX == -1 || posY == -1) {
missionFailed();
stage.close();
}
}
catch(IllegalArgumentException error) {
missionFailed();
stage.close();
}
});
stage.setScene(scene);
stage.show();
}
void missionFailed() {
Stage failedPopup = new Stage();
failedPopup.setTitle("You Died!");
failedPopup.initModality(Modality.APPLICATION_MODAL);
Button OK = new Button("OK");
Group group = new Group();
Scene miniScene = new Scene(group, 150, 100);
group.getChildren().add(OK);
failedPopup.setScene(miniScene);
failedPopup.show();
OK.setOnMouseClicked(q -> {
failedPopup.close();
});
}
}
Using hgap and vgap does not modify the column size, it modifies the space between columns. If you e.g. place the Rectangles next to each other horizontally (same row index, column index differing by 1), the distance between both nodes is vgap; there are exactly 2 columns that are filled and have a width != 0. Now move the Rectangles to the same cell and only a single column with a width != 0 leading to unexpected results.
Instead you should restrict the size of the rows/columns:
// gridpane.setHgap(20);
// gridpane.setVgap(20);
int rows = 500 / 20;
int columns = 500 / 20;
RowConstraints rConstraints = new RowConstraints(20);
ColumnConstraints cConstraints = new ColumnConstraints(20);
for (int i = 0; i < columns; i++) {
gridpane.getColumnConstraints().add(cConstraints);
}
for (int i = 0; i < rows; i++) {
gridpane.getRowConstraints().add(rConstraints);
}
Another Solution that worked for me was to fill up the whole Area with transparent Rectangulars and just set the color of the Rectangulars used at the moment.
After a player moves a stone (Candycrush-like game), in the logic i gather information about if the player move result in a Structure, which then needs to be exploded. Ofcourse once a Structure gets exploded by removing the structure elements and dropping the stones above, new Structures can appear that also needs to be exploded sequentially.
For that i have an AnimationData class that has a List of ExplosionData, which has the size of the found structures, caused by the initial player move.
My code works fine for just ONE explosion but messes up if there are multiple explosions. The problem is that the loop doesn't wait until the explosion animation is done before it continues the iteration.
Clarification : method updateGui, loop inside switchAnimation.setOnFinished
Visually :
Clips of a single Explosion and multiple Explosion that i recorded
public void updateGui(AnimationData aData) {
final int rowHeight = (int) (boardGPane.getHeight() / boardGPane.getRowConstraints().size());
Coords switchSourceCoords = aData.getSwitchSourceCoords();
Coords switchTargetCoords = aData.getSwitchTargetCoords();
// Apply player move
ParallelTransition switchAnimation = switchStones(switchSourceCoords, switchTargetCoords);
switchAnimation.play();
// Revert switch, if the move was invalid
if (aData.geteData().isEmpty()) {
switchAnimation.setOnFinished(event -> {
ParallelTransition switchBackAnimation = switchStones(switchSourceCoords, switchTargetCoords);
switchBackAnimation.play();
});
} else {
switchAnimation.setOnFinished(event -> {
// Animate explosions for every found Structure
for (ExplosionData eData : aData.geteData()) {
SequentialTransition explosionAnimation = new SequentialTransition();
// Coordinates of where the bonusStone appears
Coords bonusSource = eData.getBonusSourceCoords();
// Coordinates of where the bonusStone need to be repositioned
Coords bonusTarget = eData.getBonusTargetCoords();
// Remove all Structure elements and make Stones above drop to their target
// positions. Also translate them back to the same position for the animation
removeStructureAndReplaceIvs(eData, bonusTarget, bonusSource, rowHeight);
// This shall only proceed if the animation involves handeling a bonusStone
if (bonusSource != null && bonusTarget != null) {
int rowsToMove = bonusTarget.getRow() - bonusSource.getRow();
ImageView bonusIv = (ImageView) JavaFXGUI.getNodeFromGridPane(boardGPane, bonusTarget.getCol(), bonusTarget.getRow());
// BonusStone shall fade in at the source Position
explosionAnimation = bonusStoneFadeIn(explosionAnimation, rowsToMove, bonusIv, rowHeight);
// Translate to targetPosition, if sourcePosition is not equal to targetPosition
explosionAnimation = bonusStoneMoveToTargetCoords(explosionAnimation, rowsToMove, bonusIv, rowHeight);
}
// Make the Stone ImageViews translate from their origin position to their new target positions
explosionAnimation = dropAndFillUpEmptySpace(explosionAnimation, eData, bonusTarget, bonusSource, rowHeight);
explosionAnimation.play();
}
});
}
}
private void removeStructureAndReplaceIvs(ExplosionData eData,
Coords bonusTargetCoords,
Coords bonusSourceCoords,
final int rowHeight) {
// Removing the Structure and all stones above by deleting the ImageViews col by col
for (DropInfo info : eData.getExplosionInfo()) {
// Coordinates of the Structure element that is going to be removed in this col
int col = info.getCoords().getCol();
int row = info.getCoords().getRow();
// If a bonusStone will apear, the heightOffset gets reduced by one
int offset = getAppropiateOffset(bonusTargetCoords, info, col);
// Remove the Structure and all ImageViews above
removeImageViewsFromCells(col, row, row + 1);
List<String> stoneToken = info.getFallingStoneToken();
for (int r = row, i = 0; r >= 0; --r, ++i) {
// Fill up removed Cells with new ImageViews values
ImageView newIv = new ImageView(new Image(preImagePath + stoneToken.get(i) + ".png"));
// Place each iv to their target Coords
addImageViewToPane(newIv, col, r);
// Translate all non-bonusStones to the position they were placed before
if (ignoreBonusTargetCoordinates(bonusTargetCoords, bonusSourceCoords, r, col)) {
newIv.setTranslateY(-rowHeight * offset);
}
}
}
}
// If the removed Structure results to generate a bonusStone, make it fade in at source position
private SequentialTransition bonusStoneFadeIn(SequentialTransition explosionAnimation,
int sourceToTargetDiff,
ImageView bonusIv,
final int rowHeight) {
FadeTransition bonusFadeIn = new FadeTransition(Duration.seconds(1), bonusIv);
bonusFadeIn.setFromValue(0f);
bonusFadeIn.setToValue(1f);
// If the target Position is not the same, place it to target and translate to source position
if (sourceToTargetDiff > 0) {
bonusIv.setTranslateY(-rowHeight * sourceToTargetDiff);
}
explosionAnimation.getChildren().add(bonusFadeIn);
return explosionAnimation;
}
// If the bonusStone must be moved from source Coordinates to target Coordinates
private SequentialTransition bonusStoneMoveToTargetCoords(SequentialTransition explosionAnimation,
int sourceToTargetDiff,
ImageView bonusIv,
final int rowHeight) {
// Difference in row from bonusSourceCoordinates to bonusTargetCoordinates
if (sourceToTargetDiff > 0) {
TranslateTransition moveToTargetCoords = new TranslateTransition(Duration.seconds(1), bonusIv);
moveToTargetCoords.fromYProperty().set(-rowHeight * sourceToTargetDiff);
moveToTargetCoords.toYProperty().set(0);
explosionAnimation.getChildren().add(moveToTargetCoords);
}
return explosionAnimation;
}
private SequentialTransition dropAndFillUpEmptySpace(SequentialTransition explosionAnimation,
ExplosionData eData,
Coords bonusTargetCoords,
Coords bonusSourceCoords,
final int rowHeight) {
ParallelTransition animateDrop = new ParallelTransition();
for (int i = 0; i < eData.getExplosionInfo().size(); i++) {
// List of all stoneToken to create respective ImageViews for each col
List<DropInfo> allDropInfo = eData.getExplosionInfo();
int col = allDropInfo.get(i).getCoords().getCol();
int row = allDropInfo.get(i).getCoords().getRow();
// If a bonusStone will apear, the heightOffset gets reduced by one
int offset = getAppropiateOffset(bonusTargetCoords, allDropInfo.get(i), col);
for (int r = row; r >= 0; --r) {
// Drop all Stones above the removed Structure to fill up the empty space
// Ignore possible bonusStones since they are being animated seperately
if (ignoreBonusTargetCoordinates(bonusTargetCoords, bonusSourceCoords, r, col)) {
ImageView iv = (ImageView) JavaFXGUI.getNodeFromGridPane(boardGPane, col, r);
TranslateTransition tt = new TranslateTransition(Duration.millis(1500), iv);
tt.fromYProperty().set(-rowHeight * offset);
tt.toYProperty().set(0);
animateDrop.getChildren().add(tt);
}
}
}
explosionAnimation.getChildren().add(animateDrop);
return explosionAnimation;
}
private int getAppropiateOffset(Coords bonusTargetCoords, DropInfo dropInfo, int col) {
int bonusOffset = (bonusTargetCoords != null && col == bonusTargetCoords.getCol()) ? 1 : 0;
return dropInfo.getHeightOffset() - bonusOffset;
}
private boolean ignoreBonusTargetCoordinates(Coords bonusTargetCoords,
Coords bonusSourceCoords,
int row,
int col) {
return bonusSourceCoords == null
|| bonusTargetCoords != null && col != bonusTargetCoords.getCol()
|| bonusTargetCoords != null && row != bonusTargetCoords.getRow();
}
A SequentialTransition can be made up of other SequentialTransitions. For your code you could create a "master" SequentialTransition and build it up with each SequentialTransition you create per iteration of the for loop. Then you would play the master transition.
switchAnimation.setOnFinished(event -> {
SequentialTransition masterAnimation = new SequentialTransition();
for (ExplosionData eData : aData.geteData()) {
SequentialTransition explosionAnimation = new SequentialTransition();
// ... configure the explosionAnimation ...
masterAnimation.getChildren().add(explosionAnimation); // add to masterAnimation
}
masterAnimation.play(); // play all the explosionAnimations in squential order
});
The reason your code doesn't wait for the animation to finish before moving on to the next iteration of the loop is because Animation.play() is an "asynchronous call".
When you call play() the animation gets scheduled in the background with some internal clock/timer and the method returns immediately.
Plays Animation from current position in the direction indicated by
rate. If the Animation is running, it has no effect.
When rate > 0 (forward play), if an Animation is already positioned at
the end, the first cycle will not be played, it is considered to have
already finished. This also applies to a backward (rate < 0) cycle if
an Animation is positioned at the beginning. However, if the Animation
has cycleCount > 1, following cycle(s) will be played as usual.
When the Animation reaches the end, the Animation is stopped and the
play head remains at the end.
To play an Animation backwards from the end:
animation.setRate(negative rate);
animation.jumpTo(overall duration of animation);
animation.play();
Note:
play() is an asynchronous call, the Animation may not start immediately.
(empasis mine)
Here's a workable example. It takes a Rectangle and translates it to each corner of the scene. Each "translate-to-corner" is a separate animation which also involves rotating the Rectangle and changes its color. Then all "translate-to-corner" animations are put into one SequentialTransition. The Button at the top is disabled when clicked and re-enabled when the master SequentialTransition finishes.
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.ParallelTransition;
import javafx.animation.RotateTransition;
import javafx.animation.SequentialTransition;
import javafx.animation.Timeline;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Separator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private Button playBtn;
private StackPane groupParent;
private Rectangle rectangle;
#Override
public void start(Stage primaryStage) {
playBtn = new Button("Play Animation");
playBtn.setOnAction(ae -> {
ae.consume();
playBtn.setDisable(true);
playAnimation();
});
HBox btnBox = new HBox(playBtn);
btnBox.setAlignment(Pos.CENTER);
btnBox.setPadding(new Insets(8));
rectangle = new Rectangle(150, 100, Color.BLUE);
groupParent = new StackPane(new Group(rectangle));
groupParent.getChildren().get(0).setManaged(false);
VBox root = new VBox(btnBox, new Separator(), groupParent);
root.setMaxSize(600, 400);
root.setAlignment(Pos.CENTER);
VBox.setVgrow(groupParent, Priority.ALWAYS);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.setTitle("Animation");
primaryStage.setResizable(false);
primaryStage.show();
}
private void playAnimation() {
double maxX = groupParent.getWidth() - rectangle.getWidth();
double maxY = groupParent.getHeight() - rectangle.getHeight();
ParallelTransition pt1 = createAnimation(-25, maxY - 25, 90, Color.FIREBRICK);
ParallelTransition pt2 = createAnimation(maxX, maxY, 180, Color.BLUE);
ParallelTransition pt3 = createAnimation(maxX + 25, 25, 270, Color.FIREBRICK);
ParallelTransition pt4 = createAnimation(0, 0, 360, Color.BLUE);
SequentialTransition st = new SequentialTransition(rectangle, pt1, pt2, pt3, pt4);
st.setOnFinished(ae -> {
ae.consume();
rectangle.setTranslateX(0);
rectangle.setTranslateY(0);
rectangle.setRotate(0);
playBtn.setDisable(false);
});
st.play();
}
private ParallelTransition createAnimation(double x, double y, double r, Color c) {
TranslateTransition tt = new TranslateTransition(Duration.seconds(1.0));
tt.setToX(x);
tt.setToY(y);
RotateTransition rt = new RotateTransition(Duration.seconds(1));
rt.setToAngle(r);
Timeline tl = new Timeline(new KeyFrame(Duration.seconds(1), new KeyValue(rectangle.fillProperty(), c)));
return new ParallelTransition(tt, rt, tl);
}
}
Is it possible to make all GridPane's grid lines permanently visible without using setGridLinesVisible()?
I know that setGridLinesVisible() is for debugging purposes only, and I want to show grid lines for the end-user's convenience.
Also, I need to work on a Pane container and not a Canvas.
My program is able to add, delete, modify, move, etc. shape objects and groups on a Pane type or a Pane subtype parent container.
Thanks
Doing this depends a bit on how you have things set up. From the description of your application, when I've experimented with similar things I always found it convenient to fill the grid with empty Panes of some kind to act as the cells, and then to manipulate their content based on the data in the model. If you use this approach, you can use some CSS to put borders (e.g. using nested backgrounds) on the "cells", which will give the effect of grid lines.
Here's a simple example of this approach:
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class GridPaneWithLines extends Application {
private StackPane createCell(BooleanProperty cellSwitch) {
StackPane cell = new StackPane();
cell.setOnMouseClicked(e -> cellSwitch.set(! cellSwitch.get() ));
Circle circle = new Circle(10, Color.CORNFLOWERBLUE);
circle.visibleProperty().bind(cellSwitch);
cell.getChildren().add(circle);
cell.getStyleClass().add("cell");
return cell;
}
private GridPane createGrid(BooleanProperty[][] switches) {
int numCols = switches.length ;
int numRows = switches[0].length ;
GridPane grid = new GridPane();
for (int x = 0 ; x < numCols ; x++) {
ColumnConstraints cc = new ColumnConstraints();
cc.setFillWidth(true);
cc.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().add(cc);
}
for (int y = 0 ; y < numRows ; y++) {
RowConstraints rc = new RowConstraints();
rc.setFillHeight(true);
rc.setVgrow(Priority.ALWAYS);
grid.getRowConstraints().add(rc);
}
for (int x = 0 ; x < numCols ; x++) {
for (int y = 0 ; y < numRows ; y++) {
grid.add(createCell(switches[x][y]), x, y);
}
}
grid.getStyleClass().add("grid");
return grid;
}
#Override
public void start(Stage primaryStage) {
int numCols = 5 ;
int numRows = 5 ;
BooleanProperty[][] switches = new BooleanProperty[numCols][numRows];
for (int x = 0 ; x < numCols ; x++) {
for (int y = 0 ; y < numRows ; y++) {
switches[x][y] = new SimpleBooleanProperty();
}
}
GridPane grid = createGrid(switches);
StackPane root = new StackPane(grid);
Scene scene = new Scene(root, 600, 600);
scene.getStylesheets().add("grid-with-borders.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
and the CSS (grid-with-borders.css):
.root {
-fx-padding: 20 ;
cell-color: white ;
cell-border-color: black ;
}
.grid {
/* 1 pixel border around the top and right of the grid: */
-fx-background-color: cell-border-color, cell-color ;
-fx-background-insets: 0, 1 1 0 0 ;
-fx-padding: 1 ;
}
.cell {
/* 1 pixel border around the left and bottom of each cell: */
-fx-background-color: cell-border-color, cell-color ;
-fx-background-insets: 0, 0 0 1 1 ;
}
If you are trying to use fxml you can try this:
<GridPane GridPane.columnIndex="1" GridPane.rowIndex="0" gridLinesVisible="true">
For every children use:
-fx-border-color: black;
-fx-border-width: 0 0 1 1;
This can be achieved with the yourChildNode.setStyle(css) method for example.
Should one cell contain multiple layouts wrap them in one AnchorPane and apply the CSS on the AnchorPane.
I am learning Javafx and am having trouble getting my for loop to create a new rectangle on each iteration. When I run the program it creates one rectangle at the top left position and that is it. My goal is to create a grid of rectangles based on the amount of columns, rows, pixels wide, and pixels tall specified. Everything is tested to work besides the creation of rectangles.
for(int i = 0; i < columns; ++i)
{//Iterate through columns
for(int j = 0; j < rows; ++j)
{//Iterate through rows
Color choice = chooseColor(rectColors);
//Method that chooses a color
rect = new Rectangle(horizontal*j, vertical*i, horizontal, vertical);
//Create a new rectangle(PosY,PosX,width,height)
rect.setStroke(choice);
//Give rectangles an outline so I can see rectangles
root.getChildren().add(rect);
//Add Rectangle to board
}
}
I am trying to figure out why the rectangles aren't being created. Any help would be greatly appreciated.
I used the same program which you had.
Try with this and check where you made the mistake. Also check the values you initialized.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class RectangleDemo extends Application{
#Override
public void start(Stage stage) {
AnchorPane root = new AnchorPane();
Scene scene = new Scene(root);
stage.setScene(scene);
int columns = 20, rows = 10 , horizontal = 20, vertical = 20;
Rectangle rect = null;
for(int i = 0; i < columns; ++i)
{//Iterate through columns
for(int j = 0; j < rows; ++j)
{//Iterate through rows
// Color choice = chooseColor(rectColors);
//Method that chooses a color
rect = new Rectangle(horizontal*j, vertical*i, horizontal, vertical);
//Create a new rectangle(PosY,PosX,width,height)
rect.setStroke(Color.RED);
//Give rectangles an outline so I can see rectangles
root.getChildren().add(rect);
//Add Rectangle to board
}
}
scene.setRoot(root);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I hope it will help you...