Sudoku GUI Grid Lines - css

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);
}
}

Related

JavaFX - Check the position of a label based on a mouse click

I try to write a code that find the label on which one have clicked.
Using an event listener, I got the positions of the event using getX() and getY().
However, I cannot find the adequate methods for the label positions in order to compare them.
Below is my code, and its ouput.
public class Beta extends Application {
final Label[] answerLabel = new Label[4];
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.setGridLinesVisible(true);
final int numCols = 7 ;
final int numRows = 12 ;
//final Label[] answerLabel = new Label[4];
for (int i = 0; i < numCols; i++) {
ColumnConstraints colConst = new ColumnConstraints();
colConst.setPercentWidth(100.0 / numCols);
root.getColumnConstraints().add(colConst);
}
for (int i = 0; i < numRows; i++) {
RowConstraints rowConst = new RowConstraints();
rowConst.setPercentHeight(100.0 / numRows);
root.getRowConstraints().add(rowConst);
}
for(int i = 0; i<4; i++){
answerLabel[i] = new Label();
answerLabel[i].setMaxWidth(Double.MAX_VALUE);
answerLabel[i].setMaxHeight(Double.MAX_VALUE);
answerLabel[i].setStyle("-fx-background-color: blue;-fx-font-size: 7pt;-fx-padding: 0;");
answerLabel[i].setPadding(new Insets(10));
answerLabel[i].setCursor(Cursor.HAND);
root.add(answerLabel[i], 3, i +5, 1, 1);
answerLabel[i].setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
answerLabelPressed(e);
}
});
}
primaryStage.setScene(new Scene(root, 900, 500));
primaryStage.show();
}
private void answerLabelPressed(MouseEvent e)
{
int labelSelected;
double px = e.getX();
double py = e.getY();
System.out.println("px = " + px + " py = " + py);
for (labelSelected = 0; labelSelected < 4; labelSelected++)
{
System.out.println("answerLabel[labelSelected].getLayoutX() = " + answerLabel[labelSelected].getLayoutX());
System.out.println("answerLabel[labelSelected].getLayoutY() = " + answerLabel[labelSelected].getLayoutY());
}
}
public static void main(String[] args) {
launch(args);
}
}
px = 42.0 py = 7.0
answerLabel[labelSelected].getLayoutX() = 386.0
view.answerLabel[labelSelected].getLayoutY() = 208.0
answerLabel[labelSelected].getLayoutX() = 386.0
view.answerLabel[labelSelected].getLayoutY() = 250.0
answerLabel[labelSelected].getLayoutX() = 386.0
view.answerLabel[labelSelected].getLayoutY() = 292.0
answerLabel[labelSelected].getLayoutX() = 386.0
view.answerLabel[labelSelected].getLayoutY() = 333.0
Upadate: The main purpose was to find/check the equivalent JavaFX methods of those used in Java Swing.
An alternative and better algorithm beeing as one can read in most popular Java books :
MouseListener ml = new MouseListener() {
public void mouseClicked(MouseEvent e) {
report("mouseClicked", e.paramString());
}
In Java Swing, one should read :
Point p = e.getComponent().getLocation();
System.out.println("px = " + p.getX() + " py = " + p.getY());
for (labelSelected = 0; labelSelected < 4; labelSelected++)
{
System.out.println("answerLabel[labelSelected].getX() = " + answerLabel[labelSelected].getX());
System.out.println("answerLabel[labelSelected].getY() = " + answerLabel[labelSelected].getY());
}
I try to write a code that find the label on which one have clicked.
You create four labels, and you create a listener for each label. Each listener is only registered with one label.
So there is no need to get your hands dirty with the coordinates of the click (the event handling mechanism has already done all of that for you, when it decided to which node to dispatch the event). Just reference the label that was clicked:
public class Beta extends Application {
final Label[] answerLabel = new Label[4];
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.setGridLinesVisible(true);
final int numCols = 7 ;
final int numRows = 12 ;
for (int i = 0; i < numCols; i++) {
ColumnConstraints colConst = new ColumnConstraints();
colConst.setPercentWidth(100.0 / numCols);
root.getColumnConstraints().add(colConst);
}
for (int i = 0; i < numRows; i++) {
RowConstraints rowConst = new RowConstraints();
rowConst.setPercentHeight(100.0 / numRows);
root.getRowConstraints().add(rowConst);
}
for(int i = 0; i<4; i++){
answerLabel[i] = new Label();
answerLabel[i].setMaxWidth(Double.MAX_VALUE);
answerLabel[i].setMaxHeight(Double.MAX_VALUE);
answerLabel[i].setStyle("-fx-background-color: blue;-fx-font-size: 7pt;-fx-padding: 0;");
answerLabel[i].setPadding(new Insets(10));
answerLabel[i].setCursor(Cursor.HAND);
root.add(answerLabel[i], 3, i +5, 1, 1);
Label currentLabel = answerLabel[i];
int currentIndex = i ;
answerLabel[i].setOnMouseClicked(event -> {
System.out.println("Clicked on label "+currentIndex);
// just for demo: in real life use external stylesheets
// and pseudoclasses, etc.
for (Label label : answerLabel) {
label.setStyle("-fx-background-color: blue;-fx-font-size: 7pt;-fx-padding: 0;");
}
currentLabel.setStyle("-fx-background-color: gold;-fx-font-size: 7pt;-fx-padding: 0;");
});
}
primaryStage.setScene(new Scene(root, 900, 500));
primaryStage.show();
}
}
It is not necessary to manually compute which node the mouse clicked on. That calculation is already done for you by the framework. That's how the framework knows which event handlers to invoke. If you simply add a unique handler to each node, then when that handler is invoked only that node could be the source. This is demonstrated in #James_D's answer.
However, if you want to manually compute which node was clicked (e.g., for fun or just for learning purposes), then here is a runnable example:
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Border;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
var labels = IntStream.range(0, 12)
.mapToObj(this::createLabel)
.toArray(Label[]::new);
var grid = new GridPane();
grid.setPadding(new Insets(20));
grid.setHgap(20);
grid.setVgap(20);
grid.setOnMouseClicked(e -> {
var clickedOnLabel = getClickedOnLabel(labels, e);
if (clickedOnLabel == null) {
System.out.println("You did not click on a label!");
} else {
System.out.printf("You clicked on a label: '%s'%n", clickedOnLabel.getText());
}
});
int i = 0;
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 3; row++) {
grid.add(labels[i++], col, row);
}
}
primaryStage.setScene(new Scene(grid));
primaryStage.show();
}
private Label getClickedOnLabel(Label[] labels, MouseEvent event) {
for (var label : labels) {
var bounds = label.localToScene(label.getBoundsInLocal());
if (bounds.contains(event.getSceneX(), event.getSceneY())) {
return label;
}
}
return null;
}
private Label createLabel(int n) {
var label = new Label(String.format("Label #%02d", n));
label.setCursor(Cursor.HAND);
label.setPadding(new Insets(5));
label.setFont(Font.font("Monospaced", 15.0));
label.setBorder(Border.stroke(Color.BLACK));
return label;
}
}
The important part, the part which computes which label was clicked on, is here:
private Label getClickedOnLabel(Label[] labels, MouseEvent event) {
for (var label : labels) {
var bounds = label.localToScene(label.getBoundsInLocal());
if (bounds.contains(event.getSceneX(), event.getSceneY())) {
return label;
}
}
return null;
}
It gets the bounds of each Label in the scene's coordinate space, and then tests if the mouse's location—also in the scene's coordinate space—is contained within those bounds. You can use whatever coordinate space you like (e.g., the screen's, the grid pane's, the label's, etc.), as long as you use the same one for both the label's bounds and the mouse's location. Note the mouse's local coordinates (i.e., getX() and getY()) are in the source node's coordinate space. The source node is the node that the currently-being-invoked handler was registered with for the specific event currently being processed (the GridPane in the above example).
But again, for any "real" code, I strongly recommend you use the solution in #James_D's answer.

Center children in GridPane using CSS

I have a GridPane populated with ToggleButtons. First row and column in that GridPane holds Text object labels.
I am unable to center the Text objects with the ToggleButtons so the text appears in the middle, using css.
(This answer shows how to achieve it by using GridPane.setHalignment(node, HPos.CENTER);).
MCVE if needed :
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class GridTest extends Application {
private static final int COLS = 5, ROWS = 3;
#Override public void start(Stage stage) {
Scene scene = new Scene(makeGrid());
scene.getStylesheets().add(getClass().
getResource("GridTest.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
private Pane makeGrid() {
GridPane grid = new GridPane();
for(int rowIndex = 0; rowIndex < ROWS ; rowIndex++) {
Node[] nodes = new Node[COLS];
Node node;
for(int colIndex = 0; colIndex < COLS ; colIndex++) {
if (rowIndex == 0){ //col header;
String txt = (colIndex == 0) ?
" " : String.valueOf(colIndex);
node = new Text(txt);
}else if (colIndex == 0){//row header
node = new Text(String.valueOf(rowIndex));
}else {
node= new ToggleButton();
}
nodes[colIndex]= node;
}
grid.addRow(rowIndex, nodes);
}
return grid;
}
public static void main(String[] args) {
Application.launch(args);
}
}
CSS:
Text{
-fx-text-alignment: center;
}
GridPane {
-fx-hpos:center ;
-fx-hgap: 5;
-fx-vgap: 5;
-fx-padding:10;
}
ToggleButton {
-fx-pref-width:30;
}
Based on the comments posted by # James_D and #fabian and previous answers there are two options to get the text labels centered.
One option, as posted in the question, does not use css. It requires slight modification of the makeGrid:
//see: https://stackoverflow.com/a/35438985/3992939
GridPane.setHalignment(node, HPos.CENTER); //added line
nodes[colIndex]= node;
This solutions does not change the (non resizable) Text. It simple centers it within its GridPane parent column.
The other option involves changing the Text lables to Labels :
private Pane makeGrid() {
GridPane grid = new GridPane();
for(int rowIndex = 0; rowIndex < ROWS ; rowIndex++) {
Node[] nodes = new Node[COLS];
Node node;
for(int colIndex = 0; colIndex < COLS ; colIndex++) {
if (rowIndex == 0){ //col header;
String txt = (colIndex == 0) ? " " : String.valueOf(colIndex);
node = new Label(txt); //** changed
}else if (colIndex == 0){//row header
node = new Label(String.valueOf(rowIndex)); //** changed
}else {
node= new ToggleButton();
}
nodes[colIndex]= node;
}
grid.addRow(rowIndex, nodes);
}
return grid;
}
And using css (or additional code) to set the label's width to maximum and center the text :
GridPane .label{
-fx-alignment: center;
-fx-max-width: Infinity;
}
GridPane {
-fx-hgap: 5;
-fx-vgap: 5;
-fx-padding:10;
}
ToggleButton {
-fx-pref-width:30;
}

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

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)

Assigning a MouseListener to each Region in a 2d array of Regions

I posted a question here a couple weeks ago for some help on making a chess game and I got a great response and some code that demonstrates a solution to my problem. I have been trying to dissect the code and study it so I can better understand how it works. While doing this I ran into a question I can not seem to find an answer for. The chess board is made up of a 2d array of Regions and I am trying to add a MouseListener to each Region in the 2d array so that when a Region is pressed it will print out the row and column of the Region that was pressed. Right now nothing is happening when I press on a square in my screen and I can't not figure out why my MouseListener is not working.
public class Main extends Application {
GridPane root = new GridPane();
final int size = 8;
int col = 0;
int row = 0;
#Override
public void start(Stage primaryStage) {
try {
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.LIGHTBLUE, CornerRadii.EMPTY, Insets.EMPTY)));
}
board.addRow(i, flds);
}
for (int i = 0; i < fields.length; i++) {
col = i;
for (int j = 0; j < fields[i].length; j++) {
row = j;
fields[i][j].setOnMouseClicked(e->{
System.out.println("Col:" + col + ", Row" + row);
});
}
}
// 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());
NumberBinding boardSize = Bindings.min(root.widthProperty(), root.heightProperty());
// 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);
// same size for piecePane
piecePane.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);
piecePane.maxWidthProperty().bind(boardSize);
piecePane.maxHeightProperty().bind(boardSize);
Scene scene = new Scene(root,400,400);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Change:
StackPane root = new StackPane(board,piecePane);
to:
StackPane root = new StackPane(piecePane,board);
🐞?
Because the board was behind the piecePane it couldn't receive events.
Before you were using global variables col and row so these variables had always the same value of 7 and 7. Running the loop those variables where changing their value,but at the end they had the values 7 7 , here we need to use local variables col and row , so you need to add this modification:
for (int i = 0; i < fields.length; i++) {
int col = i;
for (int j = 0; j < fields[i].length; j++) {
int row = j;
fields[i][j].setOnMouseClicked(e -> System.out.println("Col:" + col + ", Row" + row));
}
}
I haven't worked much in JavaFX myself, though it is very similar to java swing regarding basic "collision".
There is a rectangle class with the methods contains and intersects. If you use MouseEvent to know the mouse coordinates and when the mouse is clicked. You can do something like this:
if(mouse.isClicked() && rect.contains(mouse.x, mouse.y) { // do stuff }
You need to create rectangle objects for all the squares though. Hope it works out.
EDIT: Noticed Regions also contained the methods contains and intersects - try these beforehand, use rectangles only if you must to skip too much object creation.

How do I sort through an array of buttons to see if only one is left enabled?

How do I sort through an array of buttons to see if only one is left enabled? I am creating a Sudoku solver and need to check if there is only one button left unselected in each row, column, or box and if it is to highlight the last remaining option. I can't find a command that will allow me to check each individual cell in the row to see if ONLY one is left. This is what I have so far
public class SudoHelper extends Application
{
public boolean [][][] DisabledCell = new boolean[3][9][9]; // Creates our array
Scene scene;
Pane pane;
Pane main;
BookMark bane;
#Override
public void start (Stage primaryStage)
{
for(int a = 0;a<3;a++)
{
for(int b=0;b<9;b++)
{
for(int c=0; c<9;c++)
{
DisabledCell[a][b][c]=false;
}
}
}
mouseClicks = new MouseEvent[0];
SmartCell[] currentGame = new SmartCell[81];
pane = new Pane();
pane.setPrefSize(684, 702);
int x,y;
x=y=0;
main = new Pane();
for(int i=0; i<81; i++)
{
currentGame[i]= new SmartCell(i);
currentGame[i].setLayoutX(x);
currentGame[i].setLayoutY(y);
pane.getChildren().add(currentGame[i]);
x+=76; // Sets the layout for out array of SmartCells
if(x==684) // and puts our additional buttons on the screen
{ // With our scene and stage
x=0;
y+=78;
}
}
main.setPrefSize(1100, 702);
main.getChildren().add(pane);
bane= new BookMark();
scene = new Scene(main);
main.getChildren().add(bane);
bane.setPrefSize(416, 702);
bane.setLayoutX(685);
bane.setLayoutY(0);
primaryStage.setScene(scene);
primaryStage.setTitle("Sudoku Helper");
primaryStage.show();
}
public static void main(String[] args)
{
Application.launch(args); // Starts the game
}
class BookMark extends Pane
{
class List
{
String[] MyList=new String[0];
public void Add(String Add)
{
if(this.MyList.length>0)
{
String[] Resize = new String[this.MyList.length+1];
for(int i=0;i<this.MyList.length;i++)
{
Resize[i]=this.MyList[i];
}
Resize[this.MyList.length+1]=Add;
}
else
{
this.MyList = new String[1];
this.MyList[0]=Add;
}
}
public void Clear()
{
this.MyList = new String[0];
}
}
Button lkm = new Button("Load Bookmark"); // Creates our load bookmark button
Button bkm = new Button("Save Bookmark"); // Creates out save bookmark button'
final ToggleGroup group1 = new ToggleGroup();
final ToggleGroup group2 = new ToggleGroup();
RadioButton rl1a = new RadioButton("Rule One All"); // Creates rule one radiobutton
RadioButton rl1s = new RadioButton("Rule One Click"); // Creates Rule one click radiobutton
RadioButton rl2s = new RadioButton("Rule Two Click"); // Creates rule two click radiobutton
RadioButton rl2a = new RadioButton("Rule Two All"); // Create rule two radiobutton
void ruleTwo()
{
}
void ruleOne()
{
int _x,_y;
int B=10;
String[]Getloc = name.split("~");
int loc = Integer.parseInt(Getloc[1]);
_y = loc%9;
if(loc<8) // checking which row we are looking at
{
_x=0;
for(_x=0; loc<8;)
{
if()
{
}
}
}
else if(loc<18)
{
_x=1;
}
else if(loc<27)
{
_x=2;
}
else if(loc<36)
{
_x=3;
}
else if(loc<45)
{
_x=4;
}
else if(loc<54)
{
_x=5;
}
else if(loc<63)
{
_x=6;
}
else if(loc<72)
{
_x=7;
}
else
{
_x=8; // checks blocks were looking at
}
if(_y>=0&&_y<=2) // checks which block we are in
{
if(_x>=0&&_x<=2)
{
B=0;
}
else if(_x>=3&&_x<=5)
{
B=1;
}
else if(_x>=6&&_x<=8)
{
B=2;
}
}
else if(_y>=3&&_y<=5)
{
if(_x>=0&&_x<=2)
{
B=3;
}
else if(_x>=3&&_x<=5)
{
B=4;
}
else if(_x>=6&&_x<=8)
{
B=5;
}
}
else if(_y>=6&&_y<=8)
{
if(_x>=0&&_x<=2)
{
B=6;
}
else if(_x>=3&&_x<=5)
{
B=7;
}
else if(_x>=6&&_x<=8)
{
B=8;
}
}
}
BookMark() // Our buttons specifications (Location font ect)
{
this.bkm = new Button("Save Bookmark");
this.lkm = new Button("Load Bookmark");
this.bkm.setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
this.bkm.setLayoutX(10);
this.bkm.setLayoutY(10);
this.lkm.setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
this.lkm.setLayoutX(150);
this.lkm.setLayoutY(10);
this.rl1a.setLayoutX(10);
this.rl1a.setLayoutY(250);
this.rl2a.setLayoutX(10);
this.rl2a.setLayoutY(500);
this.rl2s.setLayoutX(250);
this.rl1s.setLayoutY(250);
this.rl1s.setLayoutX(250);
this.rl2s.setLayoutY(500);
this.rl1a.setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
this.rl1s.setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
this.rl2a.setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
this.rl2s.setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
this.group1.getToggles().add(this.rl1a);
this.group1.getToggles().add(this.rl1s);
this.group2.getToggles().add(this.rl2a);
this.group2.getToggles().add(this.rl2s);
BookMark.this.getChildren().add(this.lkm);
BookMark.this.getChildren().add(this.bkm);
BookMark.this.getChildren().add(this.rl1a);
BookMark.this.getChildren().add(this.rl1s);
BookMark.this.getChildren().add(this.rl2a);
BookMark.this.getChildren().add(this.rl2s);
// bkm.setOnMouseClicked(e -> Save(e));
}
}
private MouseEvent[] mouseClicks;
class SmartCell extends StackPane
{
GridPane buttonPane;
Pane valPane;
Text textVal;
Button [] btn;
String name;
SmartCell(int nameint)
{
buttonPane = new GridPane();
btn = new Button[10];
for(int i = 1; i <= 9; i++)
{
btn[i] = new Button(i+""); // Turns i into a String
btn[i].setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
btn[i].setOnMouseClicked(e -> mouseHandler(e));
buttonPane.add(btn[i], (i-1)%3, (i-1)/3);
}
// When the user clicks one of the 9 buttons, we want to take the number of the button
// they clicked, and set the text on the text pane to that number, hide the 9 buttons,
// and show the text pane.
name = "SmartCell~"+String.valueOf(nameint);
textVal = new Text(25, 55, "");
textVal.setFont(Font.font("Arial", 48));
textVal.setTextAlignment(TextAlignment.CENTER);
valPane = new Pane();
valPane.setStyle("-fx-border-color:black; -fx-border-stroke-width:1");
valPane.getChildren().add(textVal);
getChildren().add(buttonPane); // Add the pane with the 9 buttons to the cell
getChildren().add(valPane); // Add the pane with the one piece of text to the cell
buttonPane.setVisible(true); // We start out showing the 9 buttons
valPane.setVisible(false); // ...NOT showing the pane with the single text
} // end constructor
void disqual(MouseEvent e)
{
MouseEvent[] ResizeMouse = new MouseEvent[SudoHelper.this.mouseClicks.length+1];
ResizeMouse[ResizeMouse.length-1]=e;
SudoHelper.this.mouseClicks = ResizeMouse;
int _x,_y;
int B=10;
String[]Getloc = name.split("~");
int loc = Integer.parseInt(Getloc[1]);
_y = loc%9;
if(loc<8) // checking which row we are looking at
{
_x=0;
}
else if(loc<18)
{
_x=1;
}
else if(loc<27)
{
_x=2;
}
else if(loc<36)
{
_x=3;
}
else if(loc<45)
{
_x=4;
}
else if(loc<54)
{
_x=5;
}
else if(loc<63)
{
_x=6;
}
else if(loc<72)
{
_x=7;
}
else
{
_x=8; // checks blocks were looking at
}
if(_y>=0&&_y<=2) // checks which block we are in
{
if(_x>=0&&_x<=2)
{
B=0;
}
else if(_x>=3&&_x<=5)
{
B=1;
}
else if(_x>=6&&_x<=8)
{
B=2;
}
}
else if(_y>=3&&_y<=5)
{
if(_x>=0&&_x<=2)
{
B=3;
}
else if(_x>=3&&_x<=5)
{
B=4;
}
else if(_x>=6&&_x<=8)
{
B=5;
}
}
else if(_y>=6&&_y<=8)
{
if(_x>=0&&_x<=2)
{
B=6;
}
else if(_x>=3&&_x<=5)
{
B=7;
}
else if(_x>=6&&_x<=8)
{
B=8;
}
};
int CellNum =Integer.parseInt(((Button)e.getSource()).getText());
if(DisabledCell[0][_x][CellNum-1]==true||DisabledCell[1][_y][CellNum-1]==true||DisabledCell[2][B][CellNum-1]==true)
{
disqualify(Integer.parseInt(((Button)e.getSource()).getText()));
return;
}
DisabledCell[0][_x][CellNum-1]=true;
DisabledCell[1][_y][CellNum-1]=true;
DisabledCell[2][B][CellNum-1]=true;
textVal.setText(((Button)e.getSource()).getText());
buttonPane.setVisible(false);
valPane.setVisible(true);
for(int i = 1; i <= 9; i++)disqualify(i); // Since we have locked in, all others are out of play
// in this cell
int c = 0;
int z = 0;
switch(B)
{ // Figures out which block our selected number is in.
case 0: z=0;
break; // ^
case 1: z=27;
break; // ^
case 2: z=54;
break; // ^
case 3: z=3;
break; // ^
case 4: z=30;
break; // ^
case 5: z=57;
break; // ^
case 6: z=6;
break; // ^
case 7: z=33;
break; // ^
case 8: z=60;
break; // ^
}
for(int w = 0; w<9; w++)
{
Object xob =SudoHelper.this.pane.getChildren().get((_x*9)+w);
SmartCell d =SmartCell.class.cast(xob); // Disqualifies x cells
d.disqualify(CellNum);
xob=d;
Object yob =SudoHelper.this.pane.getChildren().get(_y+(9*w));
SmartCell b =SmartCell.class.cast(yob); // Disqualifies Y Cells
b.disqualify(CellNum);
yob=b;
Object zob =SudoHelper.this.pane.getChildren().get(z+c);
SmartCell a =SmartCell.class.cast(zob);
a.disqualify(CellNum); // Disqualifies boxes
zob=a;
c++;
if(c==3)
{
z+=9;
c=0;
}
}
}
void mouseHandler(MouseEvent e)
{
// When any button gets clicked, we take the text from the button, put it on the Text
// shape, hide the pane with the 9 buttons, and show the text pane, making it look like
// the 9 buttons have "gone", and the new value that we have "locked in" has taken their place.
//
if(e.getSource() instanceof Button)
{
// If this was a right click, then just disable this button; If it was a left click then lock
// in the value this button represents.
if(e.getButton() == MouseButton.SECONDARY)
{
disqualify(Integer.parseInt(((Button)e.getSource()).getText()));
// disables button after clicked
return;
}
// System.out.print("A button was clicked"); // for debugging
disqual(e);
} // end if source was a button
} // end mouseHandler
void disqualify(int buttonNo)
{
// When we are called, we disable button #buttonNo in this cell
btn[buttonNo].setDisable(true);
btn[buttonNo].setStyle("-fx-base:black; -fx-text-fill:black; -fx-opacity:1.0"); // Sets color of cells and numbers after disabled.
}
public String toString()
{
// The toString representation of a cell is a string containing a list of
// the values "still in play" --- the remaining candidate values --- for the cell
//
// Start with an empty string. Visit all 9 buttons, and if a given button is
// not disabled (i.e still in play), then add its number (from the text on the
// button) to our string
//
String result = "";
for(int i = 1; i <= 9; i++)
if(!btn[i].isDisabled())
result += i;
return result;
}
}
private boolean[] InitalizeTile() // Initalizes our board of 81 cells
{
boolean[] newbool = new boolean[81];
for(int i=0; i<81; i++)
{
newbool[i] = false;
}
Random R = new Random();
for(int j=0; j<0; j++)
{
while(true)
{
int W = R.nextInt(81);
if(newbool[W]==false)
{
newbool[W]=true;
break;
}
}
}
return newbool;
}
}
All I really need to know is how to look at an array of buttons and see if only one is left enabled.
Arrays.stream(buttons).filter(button -> !button.isDisabled()).count() == 1
Sample application:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.Button;
import javafx.stage.Stage;
import java.util.Arrays;
public class DisabledButtonCount extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Button[] buttons = {
new Button("1"),
new Button("2"),
new Button("3")
};
buttons[1].setDisable(true);
buttons[2].setDisable(true);
System.out.println(
"Only one button enabled? " +
(Arrays.stream(buttons).filter(button -> !button.isDisabled()).count() == 1)
);
Platform.exit();
}
}

Resources