无法使用 FXML 按钮列表

Can't use list of FXML buttons

我有 9 个用 @FXML 注释的按钮,我想在列表中使用它们。当我直接使用它们时,按钮可以正常工作。但是当我尝试从列表中访问它们时,我得到 NullPointer,并且调试器显示我的列表只有空值

@FXML
private ToggleButton b11
//..
@FXML
private ToggleButton b33    
@FXML
private List buttons = [b11, b12, b13, b21, b22, b23, b31, b32, b33]

抛出 NullPointer 的方法示例

void reset(ActionEvent e) {
    def rand = new Random()
    def b = buttons.get(rand.nextInt(buttons.size()))      
    b.setSelected(true)
}

如果您希望 FXMLLoader 看到 List 字段并自动创建一个列表(某种类型的)并将您的按钮添加到其中,那么这是不可能的与 FXML。鉴于您似乎是如何命名按钮的,最好通过 initialize 方法在控制器中初始化它们,而不是在 FXML 文件中。

package com.example;

import java.util.ArrayList;
import java.util.List;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.GridPane;
import javafx.fxml.FXML;

public class Controller {

  // notice I declared the type argument; don't use raw types
  private List<ToggleButton> buttons;

  @FXML private GridPane grid;

  @FXML
  private void initialize() {
    buttons = new ArrayList<>();
    for (int row = 0; row < 3; row++) {
      for (int col = 0; col < 3; col++) {
        ToggleButton button = new ToggleButton();
        // configure button...
        buttons.add(button);
        grid.add(button, col, row);
      }
    }
  }
}

这可能是最简洁的方法,而且它的扩展性肯定更好——您不能在 FXML 文件中循环。

但是,您可以在 FXML 文件中声明列表,然后注入列表而不是使用上述解决方案。

<?import java.util.ArrayList?>
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.layout.GridPane?>

<GridPane fx:id="grid" xmlns:fx="http://javafx.com/fxml" 
          fx:controller="com.example.Controller">
  <children>
    <ToggleButton fx:id="x" GridPane.columnIndex="..." GridPane.rowIndex="..."/>
    <ToggleButton fx:id="y" GridPane.columnIndex="..." GridPane.rowIndex="..."/>
    <ToggleButton fx:id="z" GridPane.columnIndex="..." GridPane.rowIndex="..."/>
    <!-- ... -->
  </children>
  
  <fx:define>
    <!-- Need to annotate 'buttons' with @FXML -->
    <ArrayList fx:id="buttons">
      <fx:reference source="x"/>
      <fx:reference source="y"/>
      <fx:reference source="z"/>
      <!-- ... -->
    </ArrayList>
  </fx:define>
</GridPane>

但如您所见,这更难维护,至少在我看来是这样,而且更乏味,尤其是当您有很多按钮时。

Slaw 的回答对于涉及 FXML 时初始化列表的通用解决方案很有用。

切换的具体解决方案

对于您的特定任务,您正在使用 ToggleButtons。对于那些,已经有一个名为 ToggleGroup 的持有者和管理者 class,我建议您使用它,而不是初始化您自己的自定义按钮列表。

对于设置切换组,您可以使用 SceneBuilder 在 FXML 中完全完成,无需在控制器 initialize() 方法中维护额外的代码。

在 SceneBuilder 中,“切换组”是 ToggleButton 中的可编辑 属性(在 SceneBuilder UI 中切换按钮的 属性 列表中查找它). SceneBuilder 将生成 FXML 以将切换按钮关联到切换组。在您的控制器中放置一个 @FXML 对您的切换组的引用,您可以使用 yourToggleGroup.getToggles().

从该组中获取关联的切换列表

示例应用程序

sample/FruitSelectionController.java

package sample;

import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;

import java.util.Random;

public class FruitSelectionController {

    private static final Random random = new Random(42);

    @FXML private ToggleGroup fruitSelectionToggleGroup;
    @FXML private ToggleButton fruitSelectionButton1;
    @FXML private ToggleButton fruitSelectionButton2;
    @FXML private ToggleButton fruitSelectionButton3;

    @FXML
    void selectRandom(ActionEvent event) {
        ObservableList<Toggle> toggles = fruitSelectionToggleGroup.getToggles();

        fruitSelectionToggleGroup.selectToggle(
                toggles.get(
                        random.nextInt(toggles.size())
                )
        );
    }

}

sample/FruitSelectionApp.java

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class FruitSelectionApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {

        FXMLLoader loader = new FXMLLoader(
                getClass().getResource(
                        "fruit-selection.fxml"
                )
        );

        Parent root = loader.load();
        stage.setScene(new Scene(root));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

sample/fruit-selection.fxml

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.VBox?>


<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" spacing="10.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.FruitSelectionController">
   <children>
      <ToggleButton fx:id="fruitSelectionButton1" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Apples">
         <toggleGroup>
            <ToggleGroup fx:id="fruitSelectionToggleGroup" />
         </toggleGroup>
      </ToggleButton>
      <ToggleButton fx:id="fruitSelectionButton2" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Oranges" toggleGroup="$fruitSelectionToggleGroup" />
      <ToggleButton fx:id="fruitSelectionButton3" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Pears" toggleGroup="$fruitSelectionToggleGroup" />
      <Separator />
      <Button mnemonicParsing="false" onAction="#selectRandom" text="Select Random" />
   </children>
   <padding>
      <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
   </padding>
</VBox>

选择合适的切换类型

在组中时,ToggleButtons 和 RadioButtons 相似但行为略有不同。

来自 ToggleButton 文档:

Unlike RadioButtons, ToggleButtons in a ToggleGroup do not attempt to force at least one selected ToggleButton in the group. That is, if a ToggleButton is selected, clicking on it will cause it to become unselected. With RadioButton, clicking on the selected button in the group will have no effect.

因此请确保为您的应用程序选择合适的切换类型。