JAVAFX Show image on top of a rectangle - java

I'm developing a chess game using Java and JAVAFX.
My board is a JAVAFX group that contains an array of squares. My squares inherit from the JAVAFX rectangle class. I want to draw an image inside of these squares (image of the pieces) but I can't seem to find a way. when I use setfill to image Pattern the color of the square disappears which is not what I want I want the image to be transparent and drawn on top of each square. Any Ideas?

To place an image on top of a shape you can encapsulate both in a StackPane:
import javafx.application.Application;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Chess extends Application {
private final String[] COLORS = {"black","white"};
private static int ROWS = 4, COLS = 4;
#Override
public void start(Stage stage) {
Board board = new Board(COLS);
int tileNum = 0;
for(int row = 0; row < ROWS ; row++){
tileNum = tileNum == 0 ? 1:0;
for(int col = 0; col < COLS; col++){
Tile tile = new Tile(COLORS[tileNum]);
if(row==ROWS/2 && col == COLS/2) {//place an arbitrary piece
tile.setPiece(Pieces.KING.getImage());
}
board.addTile(tile.getTile());
tileNum = tileNum == 0 ? 1:0;
}
}
Parent root = new Group(board.getBoard());
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class Board {
private final TilePane board;
public Board(int columns) {
board = new TilePane(Orientation.HORIZONTAL);
board.setPrefColumns(columns);
board.setTileAlignment(Pos.CENTER);
board.setStyle("-fx-border-color:red;");
}
Pane getBoard(){
return board;
}
void addTile(Node node){
board.getChildren().add(node);
}
}
class Tile {
public static final int SIZE = 100;
private final StackPane tile;
Tile(String colorName) {
this(colorName, null);
}
Tile(String colorName, Image piece) {
Rectangle rect = new Rectangle(SIZE, SIZE, Color.valueOf(colorName));
tile = new StackPane(rect);
tile.setStyle("-fx-border-color:red;");
if(piece != null) {
setPiece(piece);
}
}
void setPiece(Image piece){
tile.getChildren().add(new ImageView(piece));
}
public Node getTile() {
return tile;
}
}
enum Pieces {
KING ("https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/64x64/Circle_Blue.png"),
QUEEN ("https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/64x64/Circle_Orange.png");
private String image;
private Pieces(String image) {
this.image = image;
}
public Image getImage(){
return new Image(image);
}
}
It is quiet simple to change the representation of a tile to a JavaFx control such as Label or Button. All you need to do is some minor changes to Tile:
class Tile {
public static final int SIZE = 100;
private final Label tile;//a Button if you need it clickable
Tile(String colorName) {
this(colorName, null);
}
Tile(String colorName, Image piece) {
tile = new Label();
tile.setPrefSize(SIZE, SIZE);
tile.setStyle("-fx-border-color:red; -fx-background-color:"+colorName);
tile.setAlignment(Pos.CENTER);
if(piece != null) {
setPiece(piece);
}
}
void setPiece(Image piece){
tile.setGraphic(new ImageView(piece));
}
public Node getTile() {
return tile;
}
}

Related

How to perform sequential animation

I'm creating a basic animation with javaFX, I have 5 rectangles in a row, I have two cirlces, red and blue. The red cricle is set on the first rectangle and the blue one is set on the fifth rectangle.
The idea is: If I click the first rectangle I want the red circle to travel to the fifth rectangle (by a translation) and once it gets there the blue rectangle (which is on the fifth rectangle) travels to the first one, in another word they exchange positions. I used the AnimationTimer class in my logic but the porblem is that the animation of both cirlces is synchronized when the pressedMouse event happens and that's not what I want, what I want is that the animation of the blue circle starts once the red circle's one is finished. I want to understand why is that happening? Is it kinda multiple thread? In case it is, when I run the program the red circle gets stuck in the middle however the blue one travels out of the bounds and hides, however if I comment one of the cirlce's position code for update (the update method) the app runs correctly, I hope I get an answer and I'm so thankful.
Another question is: How to make my animation looks smoother because it stops for a fraction of a second and moves again.
here is my code:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class animation extends Application {
AnchorPane root = new AnchorPane();
//create a matrix from rectangle class to stock 5 rectangle objects
rectangle rect [] = new rectangle [5];
//isMoving gets the true value when the animation starts
private boolean isMoving = false;
private int traveledDistance = 30;
#Override
public void start(Stage primaryStage) {
//add 5 rectangles on the anchorpane
//rect[0], rect[2] and rect[4] have BURLYWOOD color
//rect[1], rect[3] have DARKBLUE color
for(int i = 0; i<5; i++)
{
if(i%2 == 0)
{
rect[i] = new rectangle();
rect[i].setFill(Color.BURLYWOOD);
}
else
{
rect[i] = new rectangle();
rect[i].setFill(Color.DARKBLUE);
}
//set all 5 rectangles as empty
rect[i].setRectEmpty(true);
//set all the 5 rectangles one after the other along the x axis
rect[i].setTranslateX(i*60);
//add the 5 rectangles to the parent
root.getChildren().add(rect[i]);
}
//instantiation of two circles (c and d) from cirlce class
circle c = new circle(Color.RED);
c.setName("redCircle");
circle d = new circle(Color.BLUE);
d.setName("blueCircle");
//set the position of the red circle centered relatively to rect[0]
//rect[0] is no longer empty as it contains the red circle
c.setTranslateX(30);
c.setTranslateY(30);
rect[0].setCircle(c);
rect[0].setCircleName(c.getName());
rect[0].setRectEmpty(false);
root.getChildren().add(c);
//set the position of the blue circle centered relatively to rect[4]
d.setTranslateX(4*60 +30);
d.setTranslateY(30);
rect[4].setCircle(d);
rect[4].setCircleName(d.getName());
root.getChildren().add(d);
displayedScene(primaryStage);
//when the parent is clicked
root.setOnMousePressed(new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent event) {
//get the index of the clicked rectangle
int index = (int) event.getX()/60;
//if the clicked rectangle contains the red circle inside
if(!rect[index].isRectEmpty() && rect[index].getCircleName().equals("redCircle"))
{
Circle circle = rect[index].getCircle();
//update the postion of the red circle so that it occupies the last rectangle (rect[4])
update(index,5, circle);
//update the position of the blue circle so that it occupies the first rectangle(rect[0])
update(5,0, rect[4].getCircle());
}
}
});
}
//update method uses the AnimationTimer class
public void update(int initialPos, int lastPos, Circle circle)
{
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
updateCirclePosition(initialPos, lastPos, circle);
if(!isMoving)
{
this.stop();
}
}
};
timer.start();
}
public void updateCirclePosition(int initialPos, int lastPos, Circle circle)
{
int dx = 2;
if(initialPos>lastPos)
{
dx = -1*dx;
}
isMoving = true;
int distance = Math.abs((lastPos - initialPos)*60);
if(traveledDistance<distance-30)
{
circle.setTranslateX(circle.getTranslateX() + dx);
traveledDistance +=Math.abs(dx);
}
else{
isMoving = false;
traveledDistance = 30;
}
}
//load the Stage
public void displayedScene(Stage primaryStage)
{
Scene scene = new Scene(root, 300, 60);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
// circle class extends Circle
public class circle extends Circle
{
private String name;
public circle(Paint color) {
super(30, color);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
And here is rectangle class:
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
//rectangle class extends Rectangle
public class rectangle extends Rectangle {
private Circle circle;
private String circleName;
private boolean rectEmpty;
public rectangle() {
super(60, 60);
}
public Circle getCircle() {
return circle;
}
public void setCircle(Circle circle) {
this.circle = circle;
}
public boolean isRectEmpty() {
return rectEmpty;
}
public void setRectEmpty(boolean rectEmpty) {
this.rectEmpty = rectEmpty;
}
public String getCircleName() {
return circleName;
}
public void setCircleName(String circleName) {
this.circleName = circleName;
}
}
The following is an mre demonstrating the requested functionality.
Circles animation is done by animateCircles(). It uses TranslateTransition to translate a circle from one position to the other.
setOnFinished is used to start the next animation.
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Animation extends Application {
private static final double SQUARE_SIZE = 60, RADIUS = SQUARE_SIZE /2, ANIMATION_TIME = 1;
private final Pane root = new Pane();
private final Rectangle rect [] = new Rectangle [5];
private final Circle circles [] = new Circle[2];
private boolean isMoving = false, isSwapped = false;
#Override
public void start(Stage primaryStage) {
for(int i = 0; i<rect.length; i++) {
rect[i] = new Rectangle(SQUARE_SIZE, SQUARE_SIZE, i%2 == 0 ? Color.BURLYWOOD : Color.DARKBLUE);
//set all the 5 rectangles one after the other along the x axis
rect[i].setTranslateX(i*SQUARE_SIZE);
root.getChildren().add(rect[i]);
}
circles[0] = new Circle(RADIUS,Color.RED);
circles[1] = new Circle(RADIUS,Color.BLUE);
//set the position of the red circle centered to rect[0]
Point2D center = centerOf(rect[0]);
circles[0].setTranslateX(center.getX());
circles[0].setTranslateY(center.getY());
//set the position of the blue circle centered to rect[4]
center = centerOf(rect[4]);
circles[1].setTranslateX(center.getX());
circles[1].setTranslateY(center.getY());
root.getChildren().add(circles[0]);
root.getChildren().add( circles[1]);
Scene scene = new Scene(root, SQUARE_SIZE*rect.length, SQUARE_SIZE);
primaryStage.setScene(scene);
primaryStage.show();
root.setOnMousePressed(event -> animateCircles());
}
//return the center point
private Point2D centerOf(Rectangle rect) {
Bounds bounds = rect.getBoundsInParent();
double x = bounds.getMinX() + 0.5 * bounds.getWidth();
double y = bounds.getMinY() + 0.5 * bounds.getHeight();
return new Point2D(x, y);
}
private void animateCircles() {
if(isMoving) return;
TranslateTransition translateCircle0 = new TranslateTransition(Duration.seconds(ANIMATION_TIME), circles[0]);
translateCircle0.setToX( isSwapped ? centerOf(rect[0]).getX() : centerOf(rect[4]).getX());
TranslateTransition translateCircle1 = new TranslateTransition(Duration.seconds(ANIMATION_TIME), circles[1]);
translateCircle1.setToX( isSwapped ? centerOf(rect[4]).getX() : centerOf(rect[0]).getX());
translateCircle0.setOnFinished(e-> {
translateCircle1.play();
});
translateCircle1.setOnFinished(e-> {
isMoving = false;
isSwapped = ! isSwapped;
});
isMoving = true;
translateCircle0.play();
}
public static void main(String[] args) {
launch(args);
}
}
Alternatively you could implement animateCircles() using SequentialTransition:
private void animateCircles() {
if(isMoving) return;
TranslateTransition translateCircle0 = new TranslateTransition(Duration.seconds(ANIMATION_TIME), circles[0]);
translateCircle0.setToX( isSwapped ? centerOf(rect[0]).getX() : centerOf(rect[4]).getX());
TranslateTransition translateCircle1 = new TranslateTransition(Duration.seconds(ANIMATION_TIME), circles[1]);
translateCircle1.setToX( isSwapped ? centerOf(rect[4]).getX() : centerOf(rect[0]).getX());
SequentialTransition sequentialTransition = new SequentialTransition(translateCircle0, translateCircle1);
isMoving = true;
sequentialTransition.play();
sequentialTransition.setOnFinished(e-> {
isMoving = false;
isSwapped = ! isSwapped;
});
}

Selecting cell in GridPane JavaFx

Long story short, I have 8x8 GridPane (using it as Chess Board) and I want to be able to click on each cell and get its coordinates.
public class BoardView {
private ImageView imageView = new ImageView(new Image("board.png"));
private GridPane boardGrid = new GridPane();
public void createBoard(){
boardGrid.getChildren().add(imageView);
for(int i =0;i < 8; i++){
for(int j = 0; j < 8; j++){
Tile tile = new Tile(i, j);
GridPane.setConstraints(tile.getPane(), i, j);
boardGrid.getChildren().add(tile.getPane());
}
}
}
class Tile {
private int positionX;
private int positionY;
private Pane pane;
Tile(int x, int y) {
pane = new Pane();
positionX = x;
positionY = y;
pane.setOnMouseClicked(e -> {
System.out.println(positionX + " " + positionY);
}
);
}
}
However, everywhere I click, the result is "0 0", not the actual row/column position.
You code is incomplete some of your errors are :
You haven't give a specific size (width, height) on each Pane (Tiles)
I am guessing you set the size of the GridPane somewhere but its just a guess, now the way you add the background image on your Grid is something that I don't recommend instead use a StackPane.
Here is a small example which you can check to debug your problem.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class BoardView extends Application {
// the dimensions of our background Image
private final int BORDER_WIDTH = 695;
private final int BORDER_HEIGHT = 720;
#Override
public void start(Stage stage) throws Exception {
// Load your Image
ImageView backgroundImageView = new ImageView(
new Image("https://cdn.pixabay.com/photo/2013/07/13/10/24/board-157165_960_720.png"));
// Initialize the grid
GridPane boardGrid = initBoard();
// Set the dimensions of the grid
boardGrid.setPrefSize(BORDER_WIDTH, BORDER_HEIGHT);
// Use a StackPane to display the Image and the Grid
StackPane mainPane = new StackPane();
mainPane.getChildren().addAll(backgroundImageView, boardGrid);
stage.setScene(new Scene(mainPane));
stage.setResizable(false);
stage.show();
}
private GridPane initBoard() {
GridPane boardGrid = new GridPane();
int tileNum = 8;
double tileWidth = BORDER_WIDTH / tileNum;
double tileHeight = BORDER_HEIGHT / tileNum;
for (int i = 0; i < tileNum; i++) {
for (int j = 0; j < tileNum; j++) {
Tile tile = new Tile(i, j);
// Set each 'Tile' the width and height
tile.setPrefSize(tileWidth, tileHeight);
// Add node on j column and i row
boardGrid.add(tile, j, i);
}
}
// Return the GridPane
return boardGrid;
}
class Tile extends Pane {
private int positionX;
private int positionY;
public Tile(int x, int y) {
positionX = x;
positionY = y;
setOnMouseClicked(e -> {
System.out.println(positionX + " " + positionY);
});
}
}
public static void main(String[] args) {
launch(args);
}
}
From my point of view you its more easy to handle each Tile if you made the class to extend the Pane instead of just holding a reference to it but this is just my opinion. Well the above its just an example anyway. If you cant find the problem then post a MCVE show we can help you better.

How to paint over a JPanel?

I (A novice programmer) am trying to paint an oval over a JPanel. I am trying to use the method paint. However, it requires a Graphics argument. I get a NullPointerException when I include my Graphics as a argument because it is null, but I do not know how else to paint the oval. I tried repaint instead but nothing happened. Any help would be appreciated. Here is my main class:
public class Checkers extends JPanel{
public static final int BOARDSQUARES = 8;
public static final int BOARDSIZE = 75;
private JPanel[][] board;
private final Point point1;
private final LineBorder border1;
public JFrame frame;
checkerPiece piece = new checkerPiece(this);
Graphics g;
public Checkers(){
board = new JPanel[BOARDSQUARES][BOARDSQUARES];
point1 = new Point (BOARDSIZE,BOARDSIZE);
border1=new LineBorder(Color.BLACK, 1);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Checkers checkers = new Checkers();
checkers.frame=checkers.createJFrame();
checkers.board =checkers.createBoard(checkers.board, checkers.point1, checkers.border1);
for (int y=0;y<BOARDSQUARES;y++){
for (int x = 0;x<BOARDSQUARES;x++){
checkers.frame.getContentPane().add(checkers.board[y][x]);
}
}
checkers.paint(checkers.g);
}
private JPanel[][] createBoard (JPanel[][] board, Point point, LineBorder border1){
for (int row = 0; row<BOARDSQUARES;row++){
point.y=BOARDSIZE;
for (int col = 0; col <BOARDSQUARES;col++){
board[row][col] = new JPanel();
board[row][col].setLocation(point);
board[row][col].setVisible(true);
board[row][col].setSize(BOARDSIZE,BOARDSIZE);
board[row][col].setOpaque(true);
if ((row%2==0&&col%2==0)||(row%2==1&&col%2==1)){
board[row][col].setBackground(new Color (230,200,150));
} else if ((row%2==0&&col%2==1)||(row%2==1&&col%2==0)) {
board[row][col].setBackground(new Color (165, 42,42) );
}
board[row][col].setBorder(border1);
point.y = point.y+BOARDSIZE;
}
point.x=point.x+BOARDSIZE;
}
return board;
}
private JFrame createJFrame (){
JFrame mainFrame = new JFrame("Checkers");
mainFrame.setSize(1000,1000);
mainFrame.setVisible(true);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.add(new Checkers());
return mainFrame;
}
#Override
public void paint (Graphics g){
System.out.println("hi");
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
piece.paint(g2d,board[0][0].getLocation().x,board[0][0].getLocation().y,Color.BLACK);
}
}
A necessary snippet from my other class (cherkerPiece piece):
public void paint (Graphics2D g, int x, int y, Color color){
g.setColor(color);
g.fillOval(x, y, DIAMETER, DIAMETER);
}
Thank you for your help
To create something as complex as a checkers game, you should follow the model / view / controller pattern when creating your Java Swing GUI.
I created a model class for a checker board square and another model class for a checker piece. I created a model class for the checker board and another model class to hold all of the checker pieces.
I created a view class to draw the checker board. I created another view class to create the main window.
I did not create any controller classes. This code just shows you how to draw a checker board. I put all of the classes together to make it easier to paste the code. You should separate the classes into separate Java files.
package com.ggl.testing;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class CheckerBoard implements Runnable {
private Board board;
private CheckerBoardPanel checkerBoardPanel;
private JFrame frame;
private Pieces pieces;
public static void main(String[] args) {
SwingUtilities.invokeLater(new CheckerBoard());
}
public CheckerBoard() {
this.board = new Board();
this.pieces = new Pieces();
}
#Override
public void run() {
frame = new JFrame("Checker Board");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
checkerBoardPanel = new CheckerBoardPanel(board, pieces);
frame.add(checkerBoardPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public class CheckerBoardPanel extends JPanel {
private static final long serialVersionUID = 3770663347384271771L;
private Board board;
private Pieces pieces;
public CheckerBoardPanel(Board board, Pieces pieces) {
this.board = board;
this.pieces = pieces;
this.setPreferredSize(board.getPreferredSize());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Square[][] checkerBoard = board.getBoard();
for (int row = 0; row < checkerBoard.length; row++) {
for (int column = 0; column < checkerBoard[row].length; column++) {
Square square = checkerBoard[row][column];
Rectangle drawingRectangle = square.getDrawingRectangle();
g.setColor(square.getColor());
g.fillRect(drawingRectangle.x, drawingRectangle.y,
drawingRectangle.width, drawingRectangle.height);
}
}
List<Piece> checkerPieces = pieces.getPieces();
for (Piece checkerPiece : checkerPieces) {
Point coordinate = checkerPiece.getCoordinate();
Rectangle drawingRectangle = checkerBoard[coordinate.x][coordinate.y]
.getDrawingRectangle();
int x = drawingRectangle.x + drawingRectangle.width / 2;
int y = drawingRectangle.y + drawingRectangle.height / 2;
int radius = board.getSquareWidth() * 2 / 6;
g.setColor(checkerPiece.getColor());
g.fillOval(x - radius, y - radius, radius + radius, radius
+ radius);
}
}
}
public class Board {
private static final int BOARD_WIDTH = 8;
private static final int SQUARE_WIDTH = 64;
private Square[][] board;
public Board() {
this.board = initalizeBoard(BOARD_WIDTH, SQUARE_WIDTH);
}
private Square[][] initalizeBoard(int boardWidth, int squareWidth) {
Square[][] board = new Square[boardWidth][boardWidth];
int x = 0;
int y = 0;
for (int row = 0; row < boardWidth; row++) {
for (int column = 0; column < boardWidth; column++) {
Square square = new Square();
if (isLightSquare(row, column)) {
square.setColor(Color.WHITE);
} else {
square.setColor(Color.LIGHT_GRAY);
}
square.setCoordinate(new Point(row, column));
square.setDrawingRectangle(new Rectangle(x, y, squareWidth,
squareWidth));
board[row][column] = square;
x += squareWidth;
}
x = 0;
y += squareWidth;
}
return board;
}
public boolean isLightSquare(int row, int column) {
if (row % 2 == 0) {
if (column % 2 == 0) {
return true;
} else {
return false;
}
} else {
if (column % 2 == 0) {
return false;
} else {
return true;
}
}
}
public Dimension getPreferredSize() {
int width = SQUARE_WIDTH * 8 + 1;
return new Dimension(width, width);
}
public Square[][] getBoard() {
return board;
}
public int getSquareWidth() {
return SQUARE_WIDTH;
}
}
public class Square {
private Color color;
private Point coordinate;
private Rectangle drawingRectangle;
public Point getCoordinate() {
return coordinate;
}
public void setCoordinate(Point coordinate) {
this.coordinate = coordinate;
}
public Rectangle getDrawingRectangle() {
return drawingRectangle;
}
public void setDrawingRectangle(Rectangle drawingRectangle) {
this.drawingRectangle = drawingRectangle;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
public class Pieces {
private List<Piece> pieces;
public Pieces() {
this.pieces = addPieces();
}
private List<Piece> addPieces() {
List<Piece> pieces = new ArrayList<Piece>();
Piece piece = new Piece();
piece.setColor(Color.RED);
piece.setCoordinate(new Point(2, 1));
pieces.add(piece);
piece = new Piece();
piece.setColor(Color.BLACK);
piece.setCoordinate(new Point(5, 0));
pieces.add(piece);
// Add the rest of the red and black pieces here
return pieces;
}
public List<Piece> getPieces() {
return pieces;
}
public void addPiece(Piece piece) {
this.pieces.add(piece);
}
}
public class Piece {
private Color color;
private Point coordinate;
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public Point getCoordinate() {
return coordinate;
}
public void setCoordinate(Point coordinate) {
this.coordinate = coordinate;
}
}
}
you need to add a class that extends Canvas and do all your painting in that class. like this
public class FirstWindow extends JFrame
{
/**
*
*/
private static final long serialVersionUID = 1L;
public FirstWindow()
{
super("Kayte does not go to water parks");
setSize(500, 500);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
}
}
public class Start extends Canvas implements Runnable
public class Main
{
public static void main(String arg[])
{
FirstWindow fw = new FirstWindow();
Start game = new Start(500, 500);
fw.add(game);
fw.setVisible(true);
new Thread(game).start();
}
}

Chessboard with automatic resizing

So, I am trying to display a chessboard in javaFX. I will have to perform different operations and draw on some of the tiles so I chose to use a Canvas for each tile and a GridPane to arrange them for me in a grid fashion.
Unfortunately I am having some problems with the resizing of the grid tiles; I want my whole chessboard to automatically adapt its size to the Scene. Therefore, I have added a ChangeListener to both the height and width properties of the GridPane which takes care of resizing the tiles. This only works when the window gets bigger, when the window is reduced to a smaller size everything still gets bigger!
Here's the shortest SSCCE I came up with which reproduces my problem:
package chessboardtest;
import javafx.application.Application;
import javafx.beans.value.*;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.stage.Stage;
public class ChessboardTest extends Application {
final int size = 10;
#Override
public void start(Stage primaryStage) {
VBox root = new VBox();
final GridPane chessboard = new GridPane();
fillChessboard(chessboard, size);
ChangeListener<Number> resizeListener = new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
double newWidth = chessboard.getWidth() / size;
double newHeight = chessboard.getHeight() / size;
for(Node n: chessboard.getChildren()) {
Canvas canvas = (Canvas)n;
canvas.setWidth(newWidth);
canvas.setHeight(newHeight);
}
}
};
chessboard.widthProperty().addListener(resizeListener);
chessboard.heightProperty().addListener(resizeListener);
root.getChildren().add(chessboard);
root.setPadding(new Insets(10));
VBox.setVgrow(chessboard, Priority.ALWAYS);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("chessboard");
primaryStage.setScene(scene);
primaryStage.show();
}
void fillChessboard(GridPane pane, int size) {
class RedrawListener implements ChangeListener<Number> {
Color color;
Canvas canvas;
public RedrawListener(Canvas c, int i) {
if(i % 2 == 0) {
color = Color.BLACK;
}
else {
color = Color.WHITE;
}
canvas = c;
}
#Override
public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
canvas.getGraphicsContext2D().setFill(color);
canvas.getGraphicsContext2D().fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
}
}
for(int row = 0; row < size; row++) {
for(int col = 0, i = row; col < size; col++, i++) {
Canvas c = new Canvas();
RedrawListener rl = new RedrawListener(c, i);
c.widthProperty().addListener(rl);
c.heightProperty().addListener(rl);
pane.add(c, row, col);
}
}
}
public static void main(String[] args) {
launch(args);
}
}
If you don't need a canvas (and you probably don't), just use StackPanes for the squares and make them fill the width and the height. You can always add a canvas (or anything else) to the StackPanes to display their content.
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.Control;
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.stage.Stage;
public class Chessboard extends Application {
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
final int size = 8 ;
for (int row = 0; row < size; row++) {
for (int col = 0; col < size; col ++) {
StackPane square = new StackPane();
String color ;
if ((row + col) % 2 == 0) {
color = "white";
} else {
color = "black";
}
square.setStyle("-fx-background-color: "+color+";");
root.add(square, col, row);
}
}
for (int i = 0; i < size; i++) {
root.getColumnConstraints().add(new ColumnConstraints(5, Control.USE_COMPUTED_SIZE, Double.POSITIVE_INFINITY, Priority.ALWAYS, HPos.CENTER, true));
root.getRowConstraints().add(new RowConstraints(5, Control.USE_COMPUTED_SIZE, Double.POSITIVE_INFINITY, Priority.ALWAYS, VPos.CENTER, true));
}
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This is a nice solution, but resizing is so much easier with data binding in Java FX. You can hide all listener business this way.
Here is a solution much like James D's, but using Rectangles insread of Canvases for the squares:
public class ResizeChessboard extends Application {
GridPane root = new GridPane();
final int size = 8;
public void start(Stage primaryStage) {
for (int row = 0; row < size; row++) {
for (int col = 0; col < size; col++) {
Rectangle square = new Rectangle();
Color color;
if ((row + col) % 2 == 0) color = Color.WHITE;
else color = Color.BLACK;
square.setFill(color);
root.add(square, col, row);
square.widthProperty().bind(root.widthProperty().divide(size));
square.heightProperty().bind(root.heightProperty().divide(size));
}
}
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Dynamically add elements to a fixed-size GridPane in JavaFX

I would like to display a grid containing a various number of rectangles in JavaFX. It is important that this grid cannot be resized.
I chose the GridPane layout. I dynamically add javafx.scene.shape.Rectangle to it. Here's how my grid looks like with 2 rows and 4 columns.
Upon resizing, I would like it to keep the same overall shape, that is to say each Rectangle having the same size and keeping an horizontal and vertical gaps between my Rectangles.
However, here's what I get with a 4x4 grid:
The problems being:
The last row and last column do not have the same size as the rest of the Rectangles.
The gaps have disappeared.
Here is my code responsible for refreshing the display:
public void refreshConstraints() {
getRowConstraints().clear();
getColumnConstraints().clear();
for (int i = 0; i < nbRow; i++) {
RowConstraints rConstraint = new RowConstraints();
// ((nbRow - 1) * 10 / nbRow) = takes gap into account (10% of height)
rConstraint.setPercentHeight(100 / nbRow - ((nbRow - 1) * 10 / nbRow));
getRowConstraints().add(rConstraint);
}
for (int i = 0; i < nbColumn; i++) {
ColumnConstraints cConstraint = new ColumnConstraints();
cConstraint.setPercentWidth(100 / nbColumn - ((nbColumn - 1) * 10 / nbColumn));
getColumnConstraints().add(cConstraint);
}
}
Using the setFillWidth and setHgrow yields no result either, the gap is kept between my Rectangles, but the Rectangles aren't resized and they overlap the rest of my GUI elements.
EDIT: MCVE code:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.RowConstraints;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class DynamicGrid extends Application {
//Class containing grid (see below)
private GridDisplay gridDisplay;
#Override
public void start(Stage primaryStage) {
//Represents the grid with Rectangles
gridDisplay = new GridDisplay(400, 200);
//Fields to specify number of rows/columns
TextField rowField = new TextField();
TextField columnField = new TextField();
//Function to set an action when text field loses focus
buildTextFieldActions(rowField, columnField);
HBox fields = new HBox();
fields.getChildren().add(rowField);
fields.getChildren().add(new Label("x"));
fields.getChildren().add(columnField);
BorderPane mainPanel = new BorderPane();
mainPanel.setLeft(gridDisplay.getDisplay());
mainPanel.setBottom(fields);
Scene scene = new Scene(mainPanel);
primaryStage.setTitle("Test grid display");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
private void buildTextFieldActions(final TextField rowField, final TextField columnField) {
rowField.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
if (!t1) {
if (!rowField.getText().equals("")) {
try {
int nbRow = Integer.parseInt(rowField.getText());
gridDisplay.setRows(nbRow);
gridDisplay.updateDisplay();
} catch (NumberFormatException nfe) {
System.out.println("Please enter a valid number.");
}
}
}
}
});
columnField.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
if (!t1) {
if (!columnField.getText().equals("")) {
try {
int nbColumn = Integer.parseInt(columnField.getText());
gridDisplay.setColumns(nbColumn);
gridDisplay.updateDisplay();
} catch (NumberFormatException nfe) {
System.out.println("Please enter a valid number.");
}
}
}
}
});
}
//Class responsible for displaying the grid containing the Rectangles
public class GridDisplay {
private GridPane gridPane;
private int nbRow;
private int nbColumn;
private int width;
private int height;
private double hGap;
private double vGap;
public GridDisplay(int width, int height) {
this.gridPane = new GridPane();
this.width = width;
this.height = height;
build();
}
private void build() {
this.hGap = 0.1 * width;
this.vGap = 0.1 * height;
gridPane.setVgap(vGap);
gridPane.setHgap(hGap);
gridPane.setPrefSize(width, height);
initializeDisplay(width, height);
}
//Builds the first display (correctly) : adds a Rectangle for the number
//of rows and columns
private void initializeDisplay(int width, int height) {
nbRow = height / 100;
nbColumn = width / 100;
for (int i = 0; i < nbColumn; i++) {
for (int j = 0; j < nbRow; j++) {
Rectangle rectangle = new Rectangle(100, 100);
rectangle.setStroke(Paint.valueOf("orange"));
rectangle.setFill(Paint.valueOf("steelblue"));
gridPane.add(rectangle, i, j);
}
}
}
//Function detailed in post
//Called in updateDisplay()
public void refreshConstraints() {
gridPane.getRowConstraints().clear();
gridPane.getColumnConstraints().clear();
for (int i = 0; i < nbRow; i++) {
RowConstraints rConstraint = new RowConstraints();
rConstraint.setPercentHeight(100 / nbRow - ((nbRow - 1) * 10 / nbRow));
gridPane.getRowConstraints().add(rConstraint);
}
for (int i = 0; i < nbColumn; i++) {
ColumnConstraints cConstraint = new ColumnConstraints();
cConstraint.setPercentWidth(100 / nbColumn - ((nbColumn - 1) * 10 / nbColumn));
gridPane.getColumnConstraints().add(cConstraint);
}
}
public void setColumns(int newColumns) {
nbColumn = newColumns;
}
public void setRows(int newRows) {
nbRow = newRows;
}
public GridPane getDisplay() {
return gridPane;
}
//Function called when refreshing the display
public void updateDisplay() {
Platform.runLater(new Runnable() {
#Override
public void run() {
//The gridpane is cleared of the previous children
gridPane.getChildren().clear();
//A new rectangle is added for row*column
for (int i = 0; i < nbColumn; i++) {
for (int j = 0; j < nbRow; j++) {
Rectangle rectangle = new Rectangle(100, 100);
rectangle.setStroke(Paint.valueOf("orange"));
rectangle.setFill(Paint.valueOf("steelblue"));
gridPane.add(rectangle, i, j);
}
}
//Call to this function to update the grid's constraints
refreshConstraints();
}
});
}
}
}
Seems like a TilePane is a better fit for this use case than a GridPane.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.TilePane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
// java 8 code
public class DynamicTiles extends Application {
//Class containing grid (see below)
private GridDisplay gridDisplay;
//Class responsible for displaying the grid containing the Rectangles
public class GridDisplay {
private static final double ELEMENT_SIZE = 100;
private static final double GAP = ELEMENT_SIZE / 10;
private TilePane tilePane = new TilePane();
private Group display = new Group(tilePane);
private int nRows;
private int nCols;
public GridDisplay(int nRows, int nCols) {
tilePane.setStyle("-fx-background-color: rgba(255, 215, 0, 0.1);");
tilePane.setHgap(GAP);
tilePane.setVgap(GAP);
setColumns(nCols);
setRows(nRows);
}
public void setColumns(int newColumns) {
nCols = newColumns;
tilePane.setPrefColumns(nCols);
createElements();
}
public void setRows(int newRows) {
nRows = newRows;
tilePane.setPrefRows(nRows);
createElements();
}
public Group getDisplay() {
return display;
}
private void createElements() {
tilePane.getChildren().clear();
for (int i = 0; i < nCols; i++) {
for (int j = 0; j < nRows; j++) {
tilePane.getChildren().add(createElement());
}
}
}
private Rectangle createElement() {
Rectangle rectangle = new Rectangle(ELEMENT_SIZE, ELEMENT_SIZE);
rectangle.setStroke(Color.ORANGE);
rectangle.setFill(Color.STEELBLUE);
return rectangle;
}
}
#Override
public void start(Stage primaryStage) {
//Represents the grid with Rectangles
gridDisplay = new GridDisplay(2, 4);
//Fields to specify number of rows/columns
TextField rowField = new TextField("2");
TextField columnField = new TextField("4");
//Function to set an action when text field loses focus
buildTextFieldActions(rowField, columnField);
HBox fields = new HBox(10);
fields.getChildren().add(rowField);
fields.getChildren().add(new Label("x"));
fields.getChildren().add(columnField);
BorderPane mainPanel = new BorderPane();
mainPanel.setCenter(gridDisplay.getDisplay());
mainPanel.setTop(fields);
Scene scene = new Scene(mainPanel, 1000, 800);
primaryStage.setTitle("Test grid display");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
private void buildTextFieldActions(final TextField rowField, final TextField columnField) {
rowField.focusedProperty().addListener((ov, t, t1) -> {
if (!t1) {
if (!rowField.getText().equals("")) {
try {
int nbRow = Integer.parseInt(rowField.getText());
gridDisplay.setRows(nbRow);
} catch (NumberFormatException nfe) {
System.out.println("Please enter a valid number.");
}
}
}
});
columnField.focusedProperty().addListener((ov, t, t1) -> {
if (!t1) {
if (!columnField.getText().equals("")) {
try {
int nbColumn = Integer.parseInt(columnField.getText());
gridDisplay.setColumns(nbColumn);
} catch (NumberFormatException nfe) {
System.out.println("Please enter a valid number.");
}
}
}
});
}
}
Thanks a lot for your answer. TilePanes are indeed a lot easier to use, although what you've written does not completely answer my question.
I wanted to have a pane in which the children would resize, and not the pane itself. It seems setting the maxSize and prefSize doesn't have any effect.
EDIT: I managed to do it using two JavaFX Property in my GridDisplay class, corresponding to the fixed height and width of my grid:
public class GridDisplay {
private ReadOnlyDoubleProperty heightProperty;
private ReadOnlyDoubleProperty widthProperty;
...
}
Then I assign to these members the values corresponding to the desired fixed size in the constructor. The size of the children inside the grid correspond to a fraction of the height and width of the grid, depending on the number of rows and columns. Here's what my updateDisplay() looks like:
public void updateDisplay() {
gridPane.getChildren().clear();
for (int i = 0; i < nbColumn; i++) {
for (int j = 0; j < nbRow; j++) {
Rectangle rectangle = new Rectangle(100, 100);
//Binding the fraction of the grid size to the width
//and heightProperty of the child
rectangle.widthProperty().bind(widthProperty.divide(nbColumn));
rectangle.heightProperty().bind(heightProperty.divide(nbRow));
gridPane.add(rectangle, i, j);
}
}
}

Categories

Resources