无法使用 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 时初始化列表的通用解决方案很有用。
切换的具体解决方案
对于您的特定任务,您正在使用 ToggleButton
s。对于那些,已经有一个名为 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.
因此请确保为您的应用程序选择合适的切换类型。
我有 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 时初始化列表的通用解决方案很有用。
切换的具体解决方案
对于您的特定任务,您正在使用 ToggleButton
s。对于那些,已经有一个名为 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.
因此请确保为您的应用程序选择合适的切换类型。