【问题标题】:JavaFx:What is a good practice to handle lots of buttonsJavaFx:处理大量按钮的好习惯是什么
【发布时间】:2017-03-23 18:59:47
【问题描述】:

我正在使用 JavaFx 编写一个项目,它是一个 3D-Tic Tac Toe,我有一个 3D 4*4*4 页面,我使用场景构建器 2.0 设计并放置了很多按钮作为控件,所以当你点击一个按钮,其文本变为“R”,当计算机选择其移动时,它将变为“B”,无论如何这里是我的控制器类的示例:

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'.";
...

我在这个逻辑中设置了我的按钮 fx:id,'b' 代表按钮,第一个整数是指它的页面,第二个和第三个整数代表该按钮的行和列,现在我的问题是事件处理,我已经编写了一个函数来执行此操作并通过场景构建器的代码部分将其连接到我的按钮并且它可以工作:

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

但我希望按钮名称有点动态,但我不知道如何有效地做到这一点。 任何帮助将不胜感激。

【问题讨论】:

  • 也许最好不要在 FXML 中创建这些按钮。创建窗格以容纳它们以及 FXML 中的其他控件,然后在 Java 代码中创建和添加按钮。这样你就可以使用循环和数组等。
  • @fabian 的链接答案非常好:尽管使用 64 个按钮,它可能会让你的理智变得更加清醒。
  • 创建一个窗格并迭代 for 循环所需的次数并创建一次按钮。我也会减少 LOC 及其最佳实践。

标签: java javafx


【解决方案1】:

我认为我通常建议不要在 FXML 中为此类创建按钮,而是在控制器代码中循环创建它们。所以你的 FXML 文件可以定义布局:

<?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>

然后您的控制器可以使用明显的for 循环简洁地定义按钮:

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

这是另一种方法,以防出于某种原因您真的想在 FXML 中定义按钮。您可以通过为单个页面定义 FXML 文件并将其与 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>

让控制器循环添加处理程序,并为“页面”定义一个字段:

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

}

现在将页面组装到另一个 FXML 文件中,使用 &lt;fx:include&gt;:

<?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>

最后你的整体布局控制器可以设置各个控制器的页面字段如下:

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

您甚至可以将这个想法更进一步,定义一个带有四个按钮的“行”FXML,并让页面包含它的四个副本(而不是 16 个按钮)。不过,这会阻止您使用GridPane;您将不得不使用包含HBoxes 的VBox,这可能难以将所有内容保存在网格中。

【讨论】:

  • 对于在for 循环中声明的这些按钮,我如何才能访问它们并处理我提到的另一个事件,即对手放置“B”而不丢失我当前的按钮?我尝试在 for 循环之外声明按钮,并在内部声明 new 以使其可以通过整个方法访问,但随后我丢失了该按钮的坐标,并且该函数无法将按钮的文本设置为我想要的字符串,做你有什么想法吗?
  • 我并没有真正按照您的要求进行操作;您大概可以在循环中创建它们时注册您需要的其他处理程序。但是,如果您以后确实需要访问所有按钮,只需创建一个数组,即 Button[][][] allButtons = new Button[4][4][4]; 并将您创建的按钮放入数组中:allButtons[page][row][col]=button;
  • 那么我可以在最内层循环之外处理我的任何按钮,或者将点击的按钮发送到另一个在线方法吗?
  • 要在循环之外引用它们(可能除了在循环的最后一次迭代中创建的那个),您需要将它们存储在一个数组中。我不明白最后一部分......你的意思是button.setOnAction(e -&gt; someMethod(button));也许,这(显然)是可能的。
  • 如果你想要方法中按钮在网格中的位置,那么你也应该传递它,即button.setOnAction(e -&gt; someMethod(button, page, row, col));
猜你喜欢
  • 1970-01-01
  • 2015-03-28
  • 2013-01-04
  • 2018-04-07
  • 2017-11-23
  • 1970-01-01
  • 1970-01-01
  • 2017-04-03
  • 1970-01-01
相关资源
最近更新 更多