How to add border to part of a GridPane? [duplicate] - css

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)

Related

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

I decided to create a JavaFX calculator that includes Buttons and Labels and a Pane as my root Parent.
I'm still stuck with the layout, here is a rough sketch of the expected result:
Bear with my poor graphics.
The problem is that I see a blank screen when I run the following code:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.scene.text.*;
import javafx.scene.paint.Color;
import javafx.geometry.Pos;
/**
* This class is meant to serve as a simple calculator with basic funtions
* to try myself out with javafx
*/
public class CalculatorFX extends Application{
Label question; // This is the label that shows the question
Label answer; // This is the label that shows the answer
Button[] buttons = new Button[19]; // This is the buttons of the calculator
/**
* This is the root parent or container. It is defined out here because
* I hope to know its length and width are crucial for the layout.
*/
Pane root = new Pane();
/**
* This is the length of the Pane
*/
double rootHeight = root.getHeight();
/**
* This is the width of the Pane
*/
double rootWidth = root.getWidth();
public static void main(String[] args){
launch(args);
}
/**
* This start routine sets up the GUI
*/
public void start(Stage stage){
question = new Label("Type your question");
answer = new Label("Answer:");
question.setTextFill(Color.BLACK);
question.setFont( Font.font(null, FontWeight.BOLD, 18) );
question.setStyle("-fx-font: 15pt sans-serif; -fx-padding: 7px; -fx-border-color: darkred; -fx-border-width: 2px; -fx-text-fill: darkred; -fx-background-color: pink; ");
/* Initializing the numerical buttons first using a loop */
// for(int i = 0; i < 10; i++){
// buttons[i] = new Button(i + "");
// }
/* They are configured like this in the way, they would appear on
* the parent node */
buttons[0] = new Button("CLEAR");
buttons[1] = new Button("DEL");
buttons[2] = new Button("CONT");
buttons[3] = new Button("7");
buttons[4] = new Button("8");
buttons[5] = new Button("9");
buttons[6] = new Button("*");
buttons[7] = new Button("4");
buttons[8] = new Button("5");
buttons[9] = new Button("6");
buttons[10] = new Button("/");
buttons[11] = new Button("1");
buttons[12] = new Button("2");
buttons[13] = new Button("3");
buttons[14] = new Button("-");
buttons[15] = new Button("0");
buttons[16] = new Button(".");
buttons[17] = new Button("=");
buttons[18] = new Button("+");
/* Here we set the position of the children */
double unitX = rootWidth/4;
double unitY = rootHeight/9;
double nextX = rootWidth;
double nextY = rootHeight;
for(int lineNum = 6; lineNum >= 0; lineNum--){
nextY = nextY - unitY;
nextX = rootWidth - unitX;
for(int element = 4; element >=0 ; element--){
if(lineNum == 1){// Then we need to fill the lines
if( element == 1)
continue;
//buttons[lineNum + element + 8].relocate(nextX, nextY);
}
if(lineNum == 0){
answer.relocate(0, nextY - unitY);
System.out.println("Relocated answer label");
question.relocate(0,0);
System.out.println("Relocated answer label");
break; // This breaks out of two for loops because
// this is the last lineNum.
}
//buttons[lineNum + element + 8].relocate(nextX, nextY);
nextX = nextX - unitX;
}
}
/* There is also a need to resize the children */
question.setManaged(false);
question.resize(rootWidth, 2*unitY);
System.out.println("Resized label question");
answer.setManaged(false);
answer.resize(rootWidth, 2*unitY);
System.out.println("Resized label answer");
// for(int i = 0; i < buttons.length; i++){
// buttons[i].setManaged(false);
// buttons[i].resize(unitX, unitY);
// }
/* Time to configure them on our root */
root.setPrefWidth(400);
root.setPrefHeight(800);
root.getChildren().addAll(question, answer);
// for(int i = 0; i < buttons.length; i++){
// root.getChildren().add(buttons[i]);
// }
Scene scene = new Scene(root);
scene.getStylesheets().add("myStyle.css");
stage.setScene(scene);
stage.setTitle("CalculatorFX");
stage.show();
} // End of start
} // End of class CalculatorFX
Now, myStyle.css contains:
Button {
-fx-font: bold 16pt "Times New Roman";
-fx-text-fill: darkblue;
}
Label {
-fx-font: 15pt sans-serif;
-fx-padding: 7px;
-fx-border-color: darkred;
-fx-border-width: 2px;
-fx-text-fill: darkred;
-fx-background-color: pink;
}
Help me. Why do I see a blank screen?
To answer your main question:
Yes there is!
In JavaFX, Nodes have visible property:
Specifies whether this Node and any subnodes should be rendered as part of the scene graph.
You control it using the setVisible method.
To help you get started:
As Zephyr and Jamed_D pointed out, you are doing too much work.
I believe there are several correct ways to go about what you are trying to achieve.
A visual editor like SceneBuilder comes to mind. It could help you get a deeper understanding of JavaFX's components. Especially, if building the GUI programmatically is not a must for you.
Here is a simple example to help you get started with one of the ways:
public class App extends Application {
public static void main(String[] args) {
launch();
}
#Override
public void start(Stage stage) {
var label1 = new Label("Type your question: ");
var label2 = new Label("Answer: ");
label1.setPrefHeight(40);
label2.setPrefHeight(40);
label1.setPadding(new Insets(0, 10, 0, 10));
label2.setPadding(new Insets(0, 10, 0, 10));
HBox label1HB = new HBox(label1);
HBox label2HB = new HBox(label2);
VBox vBox = new VBox();
vBox.setFillWidth(true);
vBox.setPrefWidth(Double.MAX_VALUE);
vBox.setPrefHeight(Double.MAX_VALUE);
vBox.getChildren().addAll(label1HB, label2HB);
List<HBox> rows = new ArrayList<>();
for (int row = 0; row < 6; row++) {
HBox hbox = new HBox();
for (int col = 0; col < 4; col++) {
Button button = new Button(row + " " + col);
button.setMaxWidth(Double.MAX_VALUE);
button.setMaxHeight(Double.MAX_VALUE);
HBox.setHgrow(button, Priority.ALWAYS);
hbox.getChildren().add(button);
}
VBox.setVgrow(hbox, Priority.ALWAYS);
rows.add(hbox);
}
vBox.getChildren().addAll(rows);
// Uncomment the following line to see the visible property in action.
// rows.get(2).setVisible(false);
var scene = new Scene(vBox, 400, 800);
stage.setScene(scene);
stage.show();
}
}
I personally like wrapping my nodes within a VBox or an Hbox because it allows me to change the scene's size without worrying about resizing everything again.
You can find many tutorials online explaining how to exploit different JavaFX layouts' properties to achieve better as well as cleaner GUI and code.
It should be mentioned that you are going to have to take care of:
A couple of issues, like what happens when the window is too small to fit your entire content?
Some specifics, like if you want some of your nodes to have a fixed width and height.
etc.

How to add an ImagePattern to a Rectangle while still maintaining the Rectangles background color

I have a chess board and I am trying to add pieces to the board. Every spot on the board is a Rectangle so I thought the best way to add pieces would be to add an ImagePattern to each Rectangle that gets a piece. The problem I encountered was when I added an ImagePattern to a Rectangle it would make the background of that Rectangle white despite what the color was before the ImagePattern was added. So my question is, is there a way for me to preserve the background color of a Rectangle after an ImagePattern is added?
For demo purposes my code only adds one piece to the board.
public class ChessBoard 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);
if(row == 4 && col == 3){
Image p = new Image("Peices/Black/0.png");
ImagePattern pat = new ImagePattern(p);
square.setFill(pat);
}
square.widthProperty().bind(root.widthProperty().divide(size));
square.heightProperty().bind(root.heightProperty().divide(size));
square.setOnMouseClicked(e->{
square.setFill(Color.BLUE);
});
}
}
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I think you are searching for blending which is done with the BlendMode. For example:
Image p = new Image("Peices/Black/0.png");
ImagePattern pat = new ImagePattern(p);
Rectangle r1 = new Rectangle();
r1.setX(50);
r1.setY(50);
r1.setWidth(50);
r1.setHeight(50);
r1.setFill(pat);
Rectangle r = new Rectangle();
r.setX(50);
r.setY(50);
r.setWidth(50);
r.setHeight(50);
r.setFill(Color.BLUE);
r.setBlendMode(BlendMode.ADD);
As far as I know there is no direct way to accomplish this.
Source: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/effect/BlendMode.html
No, you cannot use more than a single fill with a Rectangle. Theoretically you could use a Region with multiple backgrounds, but this is probably a bad idea. Most likely you'll want at least some of the following functionality for the pieces, which will not work, if you do not make pieces their own nodes:
Dragging a piece from one field to another
animating moves
I recommend using a StackPane and put the Board in the background and put a Pane on top of it to place the pieces. Simply use ImageViews for the pieces.
Example:
#Override
public void start(Stage primaryStage) {
GridPane board = new GridPane();
Region[][] fields = new Region[8][8];
for (int i = 0; i < fields.length; i++) {
Region[] flds = fields[i];
for (int j = 0; j < flds.length; j++) {
Region field = new Region();
flds[j] = field;
field.setBackground(new Background(new BackgroundFill((i + j) % 2 == 0 ? Color.WHITE : Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
}
board.addRow(i, flds);
}
// use 1/8 of the size of the Grid for each field
RowConstraints rowConstraints = new RowConstraints();
rowConstraints.setPercentHeight(100d / 8);
ColumnConstraints columnConstraints = new ColumnConstraints();
columnConstraints.setPercentWidth(100d / 8);
for (int i = 0; i < 8; i++) {
board.getColumnConstraints().add(columnConstraints);
board.getRowConstraints().add(rowConstraints);
}
Pane piecePane = new Pane();
StackPane root = new StackPane(board, piecePane);
NumberBinding boardSize = Bindings.min(root.widthProperty(), root.heightProperty());
ImageView queen = new ImageView("https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Chess_qdt45.svg/480px-Chess_qdt45.svg.png");
DropShadow shadow = new DropShadow(BlurType.GAUSSIAN, Color.WHITE, 2, 1, 0, 0);
// drop shadow for black piece on black field
queen.setEffect(shadow);
// trigger move to topleft field on mouse click
queen.setOnMouseClicked(evt -> {
Node source = (Node) evt.getSource();
TranslateTransition animation = new TranslateTransition(Duration.seconds(0.5), source);
Region targetRegion = fields[0][0];
final PositionChangeListener listener = (PositionChangeListener) source.getUserData();
listener.setField(null);
animation.setByX(targetRegion.getLayoutX() - source.getLayoutX());
animation.setByY(targetRegion.getLayoutY() - source.getLayoutY());
animation.setOnFinished(e -> {
source.setTranslateX(0);
source.setTranslateY(0);
listener.setField(targetRegion);
});
animation.play();
});
PositionChangeListener changeListener = new PositionChangeListener(queen);
queen.setUserData(changeListener);
changeListener.setField(fields[4][3]);
// board size should be as large as possible but at most the min of the parent sizes
board.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);
board.maxWidthProperty().bind(boardSize);
board.maxHeightProperty().bind(boardSize);
// same size for piecePane
piecePane.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);
piecePane.maxWidthProperty().bind(boardSize);
piecePane.maxHeightProperty().bind(boardSize);
// add piece to piecePane
piecePane.getChildren().add(queen);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private static class PositionChangeListener implements ChangeListener<Bounds> {
private final ImageView piece;
public PositionChangeListener(ImageView piece) {
this.piece = piece;
}
private Region currentField;
public void setField(Region newRegion) {
// register/unregister listeners to bounds changes of associated field
if (currentField != null) {
currentField.boundsInParentProperty().removeListener(this);
}
currentField = newRegion;
if (newRegion != null) {
newRegion.boundsInParentProperty().addListener(this);
changed(null, null, newRegion.getBoundsInParent());
}
}
#Override
public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
// align piece with field
piece.setLayoutX(newValue.getMinX());
piece.setLayoutY(newValue.getMinY());
piece.setFitHeight(newValue.getHeight());
piece.setFitWidth(newValue.getWidth());
}
}

JavaFx Determining whether a mouse is clicking on the background or a Circle

I am creating a game where circles fall from the top of the screen to the bottom. When the circle is clicked its suppose to re-spawn in a random position on top of the screen and with a random color. I am pretty sure my problem has to do with my line to determine if the mouse click was on one of the circles or not is working correctly. So my questions are how would I determine if a mouse click happened on one of the circles or on the background screen? and What is wrong with the following line? (Because I am almost certain that my problem is from that line)
if((shape.get(i).getLayoutX() == e.getX())&&(shape.get(i).getLayoutY() == e.getY())){
My entire code is here:
public class ShapesWindow extends Application{
final int WIDTH = 640;
final int HEIGHT = WIDTH / 12 * 9;
Random r = new Random();
Circle circle;
double yCord;
long startNanoTime;
Group root = new Group();
Scene scene = new Scene(root, WIDTH, HEIGHT);
Canvas can = new Canvas(WIDTH,HEIGHT);
GraphicsContext gc = can.getGraphicsContext2D();
ArrayList<Shape> shape = new ArrayList<>();
#Override
public void start(Stage theStage) throws Exception {
theStage.setTitle("Click the bubbles!");
theStage.setScene(scene);
root.getChildren().add(can);
gc.setFill(Color.LIGHTBLUE);
gc.fillRect(0,0,WIDTH,HEIGHT);
/* This adds 10 circles to my Group */
for(int i = 0; i < 10; i++){
gc.setFill(Color.LIGHTBLUE);
gc.fillRect(0,0,WIDTH,HEIGHT);
circle = new Circle(15,randomColor());
root.getChildren().add(circle);
circle.setLayoutX(r.nextInt(WIDTH+15));
circle.setLayoutY(0);
shape.add(circle);
}
/* This my attempt at trying to handle the Mouse Events for each thing */
for(int i = 0; i < 10; i++){
shape.get(i).setOnMouseClicked(
new EventHandler<MouseEvent>(){
public void handle(MouseEvent e){
shapeClicked(e);
}
});
}
startNanoTime = System.nanoTime();
new AnimationTimer(){
public void handle(long currentNanoTime){
double t = (currentNanoTime - startNanoTime) / 1000000000.0;
yCord = t*20;
for(int i = 0; i < 10; i++){
/* This if statment allows nodes to wrap around from bottom to top */
if(yCord >=HEIGHT){
shape.get(i).setLayoutX(r.nextInt(WIDTH+15));
shape.get(i).setLayoutY(0);
shape.get(i).setFill(randomColor());
resetNan();
}
shape.get(i).setLayoutY(yCord);
}
}
}.start();
theStage.show();
}
/*
* This Function is suppose the change the color and position of the circle that was clicked
*/
public void shapeClicked(MouseEvent e){
for(int i = 0; i < shape.size();i++){
if((shape.get(i).getLayoutX() == e.getX())&&(shape.get(i).getLayoutY() == e.getY())){
shape.get(i).setLayoutX(r.nextInt(WIDTH+15));
shape.get(i).setLayoutY(0);
shape.get(i).setFill(randomColor());
}
}
/*
* This allows the value of startNanoTime to be indrectly change it can not be changed diretly
* inside of handle() inside of the Animation class
*/
public void resetNan(){
startNanoTime = System.nanoTime();
}
public Color randomColor(){
double R = r.nextDouble();
double G = r.nextDouble();
double B = r.nextDouble();
double opacity = .6;
Color color = new Color(R, G, B, opacity);
return color.brighter();
}
public static void main(String[] args){
launch(args);
}
}
Why not just
for(int i = 0; i < 10; i++){
Shape s = shape.get(i);
s.setOnMouseClicked(
new EventHandler<MouseEvent>(){
public void handle(MouseEvent e){
s.setLayoutX(r.nextInt(WIDTH+15));
s.setLayoutY(0);
s.setFill(randomColor());
}
});
}
I know long time passed by, but if anyone else need to check if a mouse click event was on a Circle or any other shape it is better to use the built in .contains method. Thanks to Point2D from JavaFx geometry class you can check if a click (x,y coordinate) is on a shape or not, you don't have to worry about the click position: in the center or border.
for (Circle circle:listOfCircles) {
Point2D point2D = new Point2D(event.getX(),event.getY());
if (circle.contains(point2D)){
System.out.println("circle clicked");
}
}

Sudoku GUI Grid Lines

Stack Overflow first timer here!
First off, I'm creating a Sudoku Solver using JavaFX. I've got everything working, however, the only issue I'm having is creating the bolded 3x3 big blocks with 3x3 cells inside each big block. I've tried creating 2 'for' loops for the big blocks followed by 2 more 'for' loops for each small TextField cell. However, accessing those cells seem impossible since then I'd be technically creating a 4-D array and I somehow have to access that mess.
Hence, I backed off on the aesthetics and worked with 9x9 TextField cells without the appropriate Sudoku lines. It works with the solver, but now I want to add in those lines, since I might as well make it look like a legit Sudoku grid. Thought about using CSS, but nth-child does not work with JavaFX CSS. Thank you, yall!
public class SudokuGUI extends Application {
public static void main(String[] args) {
Application.launch();
}
public static LimitedNumberTextField[][] tf2D =
new LimitedNumberTextField[9][9];
public static int[][] tf2DVal = new int[9][9];
public static int[][] output = SudokuSolver.output;
public static int[][] zeroBoard = SudokuSolver.zeroSudoku;
#Override
public void start(Stage mainStage) throws Exception {
//Solve Button
Button solveButton = new Button("Solve");
solveButton.setMaxWidth(Double.MAX_VALUE);
solveButton.setStyle("-fx-background-color: "
+ "linear-gradient(#f2f2f2, #d6d6d6), "
+ "linear-gradient(#fcfcfc 0%, #d9d9d9 20%, #d6d6d6 100%),"
+ "linear-gradient(#dddddd 0%, #f6f6f6 50%);"
+ "-fx-background-radius: 8,7,6;"
+ "-fx-background-insets: 0,1,2;"
+ "-fx-text-fill: black;"
+ "-fx-effect: dropshadow( three-pass-box , "
+ "rgba(0,0,0,0.6) , 5, 0.0 , 0 , 1 );");
//Reset Button
Button resetButton = new Button("Reset");
resetButton.setMaxWidth(Double.MAX_VALUE);
resetButton.setStyle("-fx-background-color: "
+ "linear-gradient(#f2f2f2, #d6d6d6), "
+ "linear-gradient(#fcfcfc 0%, #d9d9d9 20%, #d6d6d6 100%),"
+ "linear-gradient(#dddddd 0%, #f6f6f6 50%);"
+ "-fx-background-radius: 8,7,6;"
+ "-fx-background-insets: 0,1,2;"
+ "-fx-text-fill: black;"
+ "-fx-effect: dropshadow( three-pass-box , "
+ "rgba(0,0,0,0.6) , 5, 0.0 , 0 , 1 );");
//Grid
GridPane grid = new GridPane();
grid.setPadding(new Insets(40, 40, 40, 40));
//Setting the grid to the scene.
Scene scene = new Scene(grid);
// Will hold the 2 buttons in a vBox.
VBox vb = new VBox();
vb.setPadding(new Insets(10, 0, 0, 30));
vb.setSpacing(10);
vb.getChildren().addAll(solveButton, resetButton);
GridPane.setRowIndex(vb, 0);
GridPane.setColumnIndex(vb, 4);
// Adds in the vBox consisting of the 2 buttons onto the GridPane.
grid.getChildren().add(vb);
grid.setStyle("-fx-background-color: linear-gradient(to bottom, "
+ "#cedbe9 0%,#aac5de 17%,#6199c7 50%,#3a84c3 51%,"
+ "#419ad6 59%,#4bb8f0 71%,#3a8bc2 84%,#26558b 100%);");
// Creation of Sudoku grid consisting of 81 total cells.
GridPane box = new GridPane();
box.setStyle("-fx-background-color: black, "
+ "-fx-control-inner-background; "
+ "-fx-background-insets: 0, 2; "
+ "-fx-padding: 4;"
+ "-fx-grid-lines-visible: true;");
for (int row = 0; row < 9; row++) {
for (int col = 0; col < 9; col++) {
LimitedNumberTextField limitNumberTextField =
new LimitedNumberTextField(1);
limitNumberTextField.setStyle(
"-fx-pref-width: 3em; "
+ "-fx-pref-height: 3em;");
GridPane.setConstraints(limitNumberTextField, row, col);
box.getChildren().add(limitNumberTextField);
if (limitNumberTextField.getText().equals("")) {
limitNumberTextField.setText("0");
}
tf2D[row][col]= limitNumberTextField;
tf2DVal[row][col] = Integer
.parseInt(limitNumberTextField.getText());
if (limitNumberTextField.getText().equals("0")) {
limitNumberTextField.setText("");
tf2D[row][col] = limitNumberTextField;
}
}
}
grid.getChildren().add(box);
//Action Listeners for the buttons.
try {
solveButton.setOnAction(e -> {
getBoard();
setBoard(tf2DVal);
if (isZeroBoard(tf2DVal)) {
Alert alert = new Alert(AlertType.INFORMATION);
alert.setTitle(" No values!! ");
alert.setHeaderText(null);
alert.setContentText(
" Please input some values and try again.");
alert.showAndWait();
return;
}
try {
SudokuSolver.solveIt(tf2DVal);
} catch (Exception e1) {
Alert alert = new Alert(AlertType.INFORMATION);
alert.setTitle(" Invalid Sudoku board! ");
alert.setHeaderText(null);
alert.setContentText(" Your input is invalid! \n"
+ " Please check your board and try again.");
alert.showAndWait();
}
setFinalBoard();
});
resetButton.setOnAction(e -> {
getBoard();
resetBoard();
});
//Shows hand when hovered over.
solveButton.setOnMouseEntered(e -> {
solveButton.setCursor(Cursor.HAND);
});
resetButton.setOnMouseEntered(e -> {
resetButton.setCursor(Cursor.HAND);
});
} catch (Exception e) { // In case there is no solution. Not likely.
Alert alert = new Alert(AlertType.INFORMATION);
alert.setTitle(" No Solution Found! ");
alert.setHeaderText(null);
alert.setContentText(
"Please check your board and try again. ");
alert.showAndWait();
}
mainStage.setTitle("Sudoku Solver");
mainStage.getIcons().add(new Image("file:logo_icon.png"));
mainStage.setScene(scene);
mainStage.show();
}
//Reading the values of the Sudoku.
public static void getBoard() {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
LimitedNumberTextField ltf = tf2D[i][j];
if (ltf.getText().equals("")) {
ltf.setText("0");
}
tf2DVal[i][j] = 0;
tf2DVal[i][j] = Integer.parseInt(ltf.getText());
if (ltf.getText().equals("0")) {
ltf.setText("");
tf2D[i][j] = ltf;
}
}
}
}
//Setting the values to the board.
public static void setBoard(int[][] board) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
tf2DVal[i][j] = board[i][j];
if (tf2D[i][j].getText().equals("")) {
tf2D[i][j].setText("0");
}
tf2D[i][j].setText(Integer.toString(tf2DVal[i][j]));
if (tf2D[i][j].getText().equals("0")) {
tf2D[i][j].setText("");
}
}
}
}
//Method to set the final value after it is solved.
public static void setFinalBoard() {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
tf2DVal[i][j] = output[i][j];
if (tf2D[i][j].getText().equals("")) {
tf2D[i][j].setText("0");
}
tf2D[i][j].setText(Integer.toString(tf2DVal[i][j]));
if (tf2D[i][j].getText().equals("0")) {
tf2D[i][j].setText("");
}
}
}
}
//Resets the board.
public static void resetBoard() {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
tf2D[i][j].setText("");
}
}
}
//This method compares the board to a board with all zeros.
public static boolean isZeroBoard(int[][] input) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (!(input[i][j] == zeroBoard[i][j])) {
return false;
}
}
}
return true;
}
}
The basic idea I would use is to put the text fields inside another container (e.g. a StackPane) and then add some style to the stack pane. You should use an external style sheet to define the styles.
As the style depends on the location of the cell inside the "block", you need to somehow manipulate the style class depending on that location. Essentially you need a border on the bottom for rows 2 and 5, and a border on the right for columns 2 and 5. I think the cleanest way to do that part is to set or unset CSS PseudoClasses to indicate if the cell is in the right column, or bottom row of the respective blocks.
To generate the actual borders, use a "nested background" approach. The basic idea is to paint two rectangular backgrounds. The first is for the border, and fills the entire space. The second is for portion inside the border, and is painted over the top of the first, but with a 1 pixel inset along the edges where you want the border visible.
The CSS looks like:
sudoku.css:
.root {
-fx-padding: 5px ;
}
.cell {
/* Defines a black border around the standard color -fx-base */
-fx-background-color: black, -fx-base ;
/* By default draw the base color over the whole region, so no border visible */
-fx-background-insets: 0, 0 ;
-fx-padding: 5px ;
}
.cell:right {
-fx-background-insets: 0, 0 1 0 0 ;
}
.cell:bottom {
-fx-background-insets: 0, 0 0 1 0 ;
}
.cell:right:bottom {
-fx-background-insets: 0, 0 1 1 0 ;
}
.cell .text-field {
-fx-pref-width: 3em ;
-fx-pref-height: 3em ;
}
And here's a sample that uses it:
import javafx.application.Application;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class SudokuBoard extends Application {
#Override
public void start(Stage primaryStage) {
GridPane board = new GridPane();
PseudoClass right = PseudoClass.getPseudoClass("right");
PseudoClass bottom = PseudoClass.getPseudoClass("bottom");
for (int col = 0; col < 9; col++) {
for (int row = 0; row < 9; row++) {
StackPane cell = new StackPane();
cell.getStyleClass().add("cell");
cell.pseudoClassStateChanged(right, col == 2 || col == 5);
cell.pseudoClassStateChanged(bottom, row == 2 || row == 5);
cell.getChildren().add(createTextField());
board.add(cell, col, row);
}
}
Scene scene = new Scene(board);
scene.getStylesheets().add("sudoku.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private TextField createTextField() {
TextField textField = new TextField();
// restrict input to integers:
textField.setTextFormatter(new TextFormatter<Integer>(c -> {
if (c.getControlNewText().matches("\\d?")) {
return c ;
} else {
return null ;
}
}));
return textField ;
}
public static void main(String[] args) {
launch(args);
}
}

How to create a background grid

I'd like to create a grid as a background for my JavaFX application. My current solution is to paint a rectangle on a canvas, create an image pattern from it and set it as fill.
Question: Is there a better way to approach this, preferrably via CSS?
Current version:
public class BackgroundGrid extends Application {
double gridSize = 20;
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new Group(), 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
scene.setFill(createGridPattern());
}
public ImagePattern createGridPattern() {
double w = gridSize;
double h = gridSize;
Canvas canvas = new Canvas(w, h);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setStroke(Color.BLACK);
gc.setFill(Color.LIGHTGRAY.deriveColor(1, 1, 1, 0.2));
gc.fillRect(0, 0, w, h);
gc.strokeRect(0, 0, w, h);
Image image = canvas.snapshot(new SnapshotParameters(), null);
ImagePattern pattern = new ImagePattern(image, 0, 0, w, h, false);
return pattern;
}
public static void main(String[] args) {
launch(args);
}
}
Thank you very much!
Edit: in order to get sharp grid lines, just use
gc.strokeRect(0.5, 0.5, w, h);
I think that wouldn't be doable in CSS, isn't it?
You can do it with CSS too. This is all you need:
.root {
-fx-background-color: #D3D3D333,
linear-gradient(from 0.5px 0.0px to 10.5px 0.0px, repeat, black 5%, transparent 5%),
linear-gradient(from 0.0px 0.5px to 0.0px 10.5px, repeat, black 5%, transparent 5%);
}
The 0.5px offset solves some buggy behavior when set from 0px to 10px, and some lines are rendered with two pixels instead of one:
Here is an answer reproduced from an old Oracle forum post.
GridPane based approach
A few methods (base upon a GridPane layout):
style borders of the individual cells (and ensure that they fill
their entire grid position) OR
style the background of the whole
grid leaving gaps between cells which fill their entire grid
position as is shown below OR
add new grid nodes with lines and then
style the added lines.
I chose method 2 (styling the grid background) for the code below. The sample uses inline CSS styles (cause I'm lazy), but it would work (and be better) with an external CSS stylesheet to style the grid.
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class GridPaneStyle extends Application {
#Override
public void start(final Stage stage) {
// create a grid with some sample data.
GridPane grid = new GridPane();
grid.addRow(0, new Label("1"), new Label("2"), new Label("3"));
grid.addRow(1, new Label("A"), new Label("B"), new Label("C"));
// make all of the Controls and Panes inside the grid fill their grid cell,
// align them in the center and give them a filled background.
// you could also place each of them in their own centered StackPane with
// a styled background to achieve the same effect.
for (Node n : grid.getChildren()) {
if (n instanceof Control) {
Control control = (Control) n;
control.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
control.setStyle("-fx-background-color: cornsilk; -fx-alignment: center;");
}
if (n instanceof Pane) {
Pane pane = (Pane) n;
pane.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
pane.setStyle("-fx-background-color: cornsilk; -fx-alignment: center;");
}
}
// style the grid so that it has a background and gaps around the grid and between the
// grid cells so that the background will show through as grid lines.
grid.setStyle("-fx-background-color: palegreen; -fx-padding: 2; -fx-hgap: 2; -fx-vgap: 2;");
// turn layout pixel snapping off on the grid so that grid lines will be an even width.
grid.setSnapToPixel(false);
// set some constraints so that the grid will fill the available area.
ColumnConstraints oneThird = new ColumnConstraints();
oneThird.setPercentWidth(100 / 3.0);
oneThird.setHalignment(HPos.CENTER);
grid.getColumnConstraints().addAll(oneThird, oneThird, oneThird);
RowConstraints oneHalf = new RowConstraints();
oneHalf.setPercentHeight(100 / 2.0);
oneHalf.setValignment(VPos.CENTER);
grid.getRowConstraints().addAll(oneHalf, oneHalf);
// layout the scene in a stackpane with some padding so that the grid is centered
// and it is easy to see the outer grid lines.
StackPane layout = new StackPane();
layout.setStyle("-fx-background-color: whitesmoke; -fx-padding: 10;");
layout.getChildren().addAll(grid);
stage.setScene(new Scene(layout, 600, 400));
stage.show();
// can be uncommented to show the grid lines for debugging purposes, but not particularly useful for styling purposes.
//grid.setGridLinesVisible(true);
}
public static void main(String[] args) {
launch();
}
}
Alternate Canvas Based Approach
See also the FXExperience blog post Resizable Grid using Canvas.
The advice to get really sharp grid lines is not correct for all systems. On a Retina display you would have to use a line width of 0.5 and then an offset of 0.25 from the integer line coordinates. So in practice you would have to determine on what system your application is running and then use different line widths and offsets.

Resources