0

I am writing a project using JavaFx and it's a 3D-Tic Tac Toe, i have a 3D 4*4*4 page which i designed with scene builder 2.0 and placed a lot of buttons as controls, so when you click on a button its text turns to "R" and when computer selects its move, it's going to be "B",anyway here is a sample of my Controller class:

public class Controller {
@FXML
private Button b131;

@FXML
private Button b111;

@FXML
private Button b133;

@FXML
private Button b232;

@FXML
private Button b331;

@FXML
private Button b132;

@FXML
private Button b231;

@FXML
private Button b314;

@FXML
private Button b113;

@FXML
private Button b234;

@FXML
private Button b212;

@FXML
private Button b333;

@FXML
private Button b311;
...
 @FXML
public void Initialize() {
    assert b131 != null : "fx:id=\"b131\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b111 != null : "fx:id=\"b111\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b133 != null : "fx:id=\"b133\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b232 != null : "fx:id=\"b232\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b331 != null : "fx:id=\"b331\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b132 != null : "fx:id=\"b132\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b231 != null : "fx:id=\"b231\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b314 != null : "fx:id=\"b314\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b113 != null : "fx:id=\"b113\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b234 != null : "fx:id=\"b234\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b212 != null : "fx:id=\"b212\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b333 != null : "fx:id=\"b333\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b311 != null : "fx:id=\"b311\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b112 != null : "fx:id=\"b112\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b134 != null : "fx:id=\"b134\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b233 != null : "fx:id=\"b233\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b211 != null : "fx:id=\"b211\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b332 != null : "fx:id=\"b332\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b214 != null : "fx:id=\"b214\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b313 != null : "fx:id=\"b313\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b114 != null : "fx:id=\"b114\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b213 != null : "fx:id=\"b213\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b334 != null : "fx:id=\"b334\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b312 != null : "fx:id=\"b312\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b142 != null : "fx:id=\"b142\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b241 != null : "fx:id=\"b241\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b141 != null : "fx:id=\"b141\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b122 != null : "fx:id=\"b122\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b144 != null : "fx:id=\"b144\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b243 != null : "fx:id=\"b243\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b221 != null : "fx:id=\"b221\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b342 != null : "fx:id=\"b342\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b121 != null : "fx:id=\"b121\" was not injected: check your FXML file 'Scene.fxml'.";
    assert b143 != null : "fx:id=\"b143\" was not injected: check your FXML file 'Scene.fxml'.";
...

I've set my buttons fx:id's in this logic,'b' stands for button, first integer refers to it's page and second and third integers stand for row and column of that button, Now my problem is event handling, i've written a function which does this and connected it to my button through scene builder's code section and it works :

public void ButtonHandler(){
b111.setOnAction(new EventHandler<javafx.event.ActionEvent>() {
    @Override
    public void handle(javafx.event.ActionEvent event) {
        b111.setText("R");
    }
});
...

But i want the button name to be kinda dynamic but i don't know how to do it efficiently. Any help would be appreciated.

pouyan021
  • 177
  • 1
  • 4
  • 19
  • 1
    Maybe it's better not to create these buttons in FXML. Create the pane to hold them, and other controls in FXML, and then create and add the buttons in Java code. That way you can use loops and arrays, etc. – James_D Feb 01 '16 at 15:01
  • 1
    Possible duplicate of [Grouping together JavaFX FXML Objects](http://stackoverflow.com/questions/34469447/grouping-together-javafx-fxml-objects) – fabian Feb 01 '16 at 15:39
  • @fabian's linked answer is excellent: it might just stretch your sanity to do that with 64 buttons though. – James_D Feb 01 '16 at 16:20
  • create a pane and iterate for loops for the required number of times and create button once. I will reduce LOC and its the best practice too. – Venkat Prasanna Feb 02 '16 at 12:05

1 Answers1

2

I think I would generally recommend not creating the buttons in FXML for something like this, but creating them in a loop in the controller code. So your FXML file can just define the layout:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.geometry.Insets?>

<VBox fx:controller="tictactoe3d.GameController" xmlns:fx="http://javafx.com/fxml/1">
    <GridPane fx:id="page0">
        <padding>
            <Insets top="5" left="5" right="5" bottom="5"/>
        </padding>
    </GridPane>
    <GridPane fx:id="page1">
        <padding>
            <Insets top="5" left="5" right="5" bottom="5"/>
        </padding>
    </GridPane>
    <GridPane fx:id="page2">
        <padding>
            <Insets top="5" left="5" right="5" bottom="5"/>
        </padding>
    </GridPane>
    <GridPane fx:id="page3">
        <padding>
            <Insets top="5" left="5" right="5" bottom="5"/>
        </padding>
    </GridPane>
</VBox>

and then your controller can concisely define the buttons using the obvious for loops:

package tictactoe3d;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;

public class GameController {

    @FXML
    private GridPane page0 ;
    @FXML
    private GridPane page1 ;
    @FXML
    private GridPane page2 ;
    @FXML
    private GridPane page3 ;

    public void initialize() {
        GridPane[] pages = {page0, page1, page2, page3};

        for (int page = 0; page < 4; page++) {
            for (int row = 0; row < 4; row ++) {
                for (int col = 0; col < 4; col++) {
                    Button button = new Button("O");
                    pages[page].add(button, col, row);

                    String message = "Button pressed on page "+page+" row "+row+" column "+col;
                    button.setOnAction(e -> {
                        button.setText("R");
                        // replace with real data update:
                        System.out.println(message);
                    });
                }
            }
        }
    }
}

Here's a alternative approach, in case for some reason you really want to define the buttons in FXML. You could reduce the scale of the problem by defining an FXML file for a single page and combine it with the technique in Grouping together JavaFX FXML Objects:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Button?>
<?import java.util.ArrayList?>
<?import javafx.geometry.Insets?>

<GridPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="tictactoe3d.PageController">
    <Button fx:id="button00" text="O" GridPane.rowIndex="0" GridPane.columnIndex="0"/>
    <Button fx:id="button01" text="O" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
    <Button fx:id="button02" text="O" GridPane.rowIndex="0" GridPane.columnIndex="2"/>
    <Button fx:id="button03" text="O" GridPane.rowIndex="0" GridPane.columnIndex="3"/>
    <Button fx:id="button10" text="O" GridPane.rowIndex="1" GridPane.columnIndex="0"/>
    <Button fx:id="button11" text="O" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
    <Button fx:id="button12" text="O" GridPane.rowIndex="1" GridPane.columnIndex="2"/>
    <Button fx:id="button13" text="O" GridPane.rowIndex="1" GridPane.columnIndex="3"/>
    <Button fx:id="button20" text="O" GridPane.rowIndex="2" GridPane.columnIndex="0"/>
    <Button fx:id="button21" text="O" GridPane.rowIndex="2" GridPane.columnIndex="1"/>
    <Button fx:id="button22" text="O" GridPane.rowIndex="2" GridPane.columnIndex="2"/>
    <Button fx:id="button23" text="O" GridPane.rowIndex="2" GridPane.columnIndex="3"/>
    <Button fx:id="button30" text="O" GridPane.rowIndex="3" GridPane.columnIndex="0"/>
    <Button fx:id="button31" text="O" GridPane.rowIndex="3" GridPane.columnIndex="1"/>
    <Button fx:id="button32" text="O" GridPane.rowIndex="3" GridPane.columnIndex="2"/>
    <Button fx:id="button33" text="O" GridPane.rowIndex="3" GridPane.columnIndex="3"/>

    <padding>
        <Insets top="5" left="5" bottom="5" right="5"/>
    </padding>

    <fx:define>
        <ArrayList fx:id="buttons">
            <fx:reference source="button00"/>
            <fx:reference source="button01"/>
            <fx:reference source="button02"/>
            <fx:reference source="button03"/>
            <fx:reference source="button10"/>
            <fx:reference source="button11"/>
            <fx:reference source="button12"/>
            <fx:reference source="button13"/>
            <fx:reference source="button20"/>
            <fx:reference source="button21"/>
            <fx:reference source="button22"/>
            <fx:reference source="button23"/>
            <fx:reference source="button30"/>
            <fx:reference source="button31"/>
            <fx:reference source="button32"/>
            <fx:reference source="button33"/>
        </ArrayList>
    </fx:define>
</GridPane>

Have the controller add the handlers in a loop, and also define a field for the "page":

package tictactoe3d;

import java.util.List;

import javafx.fxml.FXML;
import javafx.scene.control.Button;

public class PageController {

    @FXML
    private List<Button> buttons ;

    private int page ;

    public void setPage(int page) {
        this.page = page ;
    }

    public void initialize() {
        for (int i = 0; i < buttons.size(); i++) {
            Button button = buttons.get(i);
            int row = i % 4 ;
            int column = i / 4 ;
            button.setOnAction(e -> {
                button.setText("R");
                // replace with real data update:
                System.out.println("Button pressed on page "+page+" row "+row+" column "+column);
            });
        }
    }

}

and now assemble the pages in another FXML file, using <fx:include>:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.VBox?>

<VBox fx:controller="tictactoe3d.GameController" xmlns:fx="http://javafx.com/fxml/1">
    <fx:include fx:id="page0" source="page.fxml"/>
    <fx:include fx:id="page1" source="page.fxml"/>
    <fx:include fx:id="page2" source="page.fxml"/>
    <fx:include fx:id="page3" source="page.fxml"/>
</VBox>

Finally your controller for the overall layout can set the page fields of the individual controllers as follows:

package tictactoe3d;

import javafx.fxml.FXML;

public class GameController {
    @FXML
    private PageController page0Controller ;
    @FXML
    private PageController page1Controller ;
    @FXML
    private PageController page2Controller ;
    @FXML
    private PageController page3Controller ;

    public void initialize() {
        page0Controller.setPage(0);
        page1Controller.setPage(1);
        page2Controller.setPage(2);
        page3Controller.setPage(3);
    }
}

You could even take this idea one step further, defining a "row" FXML with four buttons, and making the page include four copies of it (instead of 16 buttons). That would prevent you using a GridPane though; you would have to use a VBox containing HBoxes, which might make it difficult to keep everything in a grid.

Community
  • 1
  • 1
James_D
  • 201,275
  • 16
  • 291
  • 322
  • for these buttons declared inside the `for` loops how could i have access to them and handle another event that i've mentioned which is placing "B" by the opponent without losing my current buttons? i tried declaring the button outside the for loops and just `new` them inside to make it accessible through the whole method but then i lost the coordinate of that button and the function is unable to set the text of button to my desired string,do you got any idea? – pouyan021 Feb 03 '16 at 21:27
  • I don't really follow what you're asking; you could presumably just register the other handler you need when you create them in the loop. But if you really need access to all the buttons later, just create an array, i.e. `Button[][][] allButtons = new Button[4][4][4];` and put the buttons you create in the array: `allButtons[page][row][col]=button;`. – James_D Feb 03 '16 at 21:33
  • So could i handle any of my buttons outside the inner most loop or send a clicked button to another method online? – pouyan021 Feb 03 '16 at 21:50
  • To reference them outside the loop (except perhaps for the one created on the last iteration of the loop) you need to store them in an array. I don't understand the last part... you mean `button.setOnAction(e -> someMethod(button));` perhaps, which is (obviously) possible. – James_D Feb 03 '16 at 21:55
  • Yes i like to create buttons in `initialize` and do `button.setOnAction(e -> someMethod(button));`in another method with knowing the exact coordination of the button which has been selected by the computer – pouyan021 Feb 03 '16 at 21:59
  • 1
    If you want the location in the grid for the button in the method, then you should pass that too, i.e. `button.setOnAction(e -> someMethod(button, page, row, col));`. – James_D Feb 03 '16 at 22:05