JavaFX:创建TextField网格时更新ProgressBar导致网格不显示
问题描述
我在相关平台上查阅了许多使用线程更新ProgressBar的代码,但这些代码均与计算任务或控件属性更新相关。我希望在创建225(15×15)至2500(50×50)个TextField的过程中,通过ProgressBar展示进度。但添加线程相关代码后,网格不再显示。
应用界面

GridCenterApplication.java
import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.stage.Stage; import java.io.IOException; public class GridCenterApplication extends Application { @Override public void start(Stage stage) throws IOException { FXMLLoader fxmlLoader = new FXMLLoader(GridCenterApplication.class.getResource("main-view.fxml")); Scene scene = new Scene(fxmlLoader.load()); stage.setTitle("Grid"); stage.setScene(scene); stage.setMaximized(true); stage.show(); } public static void main(String[] args) { launch(); } }
GridCenterController.java
import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.geometry.Pos; import javafx.scene.control.*; import javafx.scene.layout.GridPane; import java.net.URL; import java.util.ResourceBundle; public class GridCenterController implements Initializable { @FXML private ScrollPane scpGrid; @FXML private Spinner<Integer> spnCols; @FXML private Spinner<Integer> spnRows; @FXML private ProgressBar pgbProgress; GridPane gridPane; private final int CELL_HORIZONTAL_GAP = 1; private final int CELL_VERTICAL_GAP = 1; private final int CELL_HORIZONTAL_SIZE = 40; private final int CELL_VERTICAL_SIZE = 40; private int totalCols = 0; private int totalRows = 0; @FXML void onMnuItemNewGridAction(ActionEvent event) { if(!(scpGrid.getContent() == null)){ scpGrid.setContent(null); } totalCols = spnCols.getValue(); totalRows = spnRows.getValue(); var newGrid = new Grid(totalCols, totalRows, CELL_HORIZONTAL_GAP, CELL_VERTICAL_GAP, CELL_HORIZONTAL_SIZE, CELL_VERTICAL_SIZE, pgbProgress); gridPane = newGrid.getGrid(); scpGrid.setContent(gridPane); } @Override public void initialize(URL url, ResourceBundle resourceBundle) { SpinnerValueFactory<Integer> numberOfCols = new SpinnerValueFactory.IntegerSpinnerValueFactory(15, 50); SpinnerValueFactory<Integer> numberOfRows = new SpinnerValueFactory.IntegerSpinnerValueFactory(15, 50); spnCols.setValueFactory(numberOfCols); spnRows.setValueFactory(numberOfRows); scpGrid.contentProperty().addListener((observableValue, oldValue, newValue) -> { if (newValue != null && newValue.isVisible()) { pgbProgress.setProgress(0); } }); } }
Grid.java
import javafx.concurrent.Task; import javafx.scene.control.ProgressBar; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; public class Grid { private final GridPane grid; public Grid(int totalCols, int totalRows, int CELL_HORIZONTAL_GAP, int CELL_VERTICAL_GAP, int CELL_HORIZONTAL_SIZE, int CELL_VERTICAL_SIZE, ProgressBar pgbProgress) { grid = new GridPane(); grid.setHgap(CELL_HORIZONTAL_GAP); grid.setVgap(CELL_VERTICAL_GAP); TextField[][] arrayLetterField = new TextField[totalCols][totalRows]; Task<Void> task = new Task<Void>() { @Override protected Void call() throws Exception { pgbProgress.setProgress(0); double total = totalCols * totalRows; double i = 1.0; for (int row = 0; row < totalRows; row++) { for (int col = 0; col < totalCols; col++) { arrayLetterField[col][row] = new TextField(); arrayLetterField[col][row].setMinSize(CELL_HORIZONTAL_SIZE, CELL_VERTICAL_SIZE); arrayLetterField[col][row].setMaxSize(CELL_HORIZONTAL_SIZE, CELL_VERTICAL_SIZE ); grid.add(arrayLetterField[col][row], col, row); try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } updateProgress(i, total); i++; } } return null; } }; pgbProgress.progressProperty().bind(task.progressProperty()); new Thread(task).start(); } public GridPane getGrid() { return grid; } }
main-view.fxml
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.Menu?> <?import javafx.scene.control.MenuBar?> <?import javafx.scene.control.MenuItem?> <?import javafx.scene.control.ProgressBar?> <?import javafx.scene.control.ScrollPane?> <?import javafx.scene.control.Spinner?> <?import javafx.scene.layout.BorderPane?> <?import javafx.scene.layout.Pane?> <?import javafx.scene.layout.VBox?> <BorderPane prefHeight="767.0" prefWidth="1053.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.gridpanetest.GridCenterController"> <top> <VBox prefWidth="100.0" BorderPane.alignment="CENTER"> <children> <MenuBar fx:id="mnuBar" prefHeight="25.0" prefWidth="360.0"> <menus> <Menu mnemonicParsing="false" text="Grid"> <items> <MenuItem mnemonicParsing="false" onAction="#onMnuItemNewGridAction" text="New grid" /> </items> </Menu> </menus> </MenuBar> <Pane prefHeight="80.0" prefWidth="1053.0"> <children> <Label layoutX="26.0" layoutY="15.0" text="Columns" /> <Label layoutX="26.0" layoutY="46.0" text="Rows" /> <Spinner fx:id="spnCols" layoutX="79.0" layoutY="11.0" prefHeight="25.0" prefWidth="57.0" /> <Spinner fx:id="spnRows" layoutX="79.0" layoutY="42.0" prefHeight="25.0" prefWidth="57.0" /> <ProgressBar fx:id="pgbProgress" focusTraversable="false" layoutX="185.0" layoutY="31.0" prefWidth="200.0" progress="0.0" /> </children> </Pane> </children> </VBox> </top> <center> <ScrollPane fx:id="scpGrid" style="-fx-background-color: #dbbb92; -fx-background: #dbbb92;" BorderPane.alignment="CENTER" /> </center> </BorderPane>
解决方案
问题核心是JavaFX的UI组件只能在JavaFX应用线程(UI线程)中创建和修改,你在后台线程的call()方法里直接操作GridPane添加TextField,违反了这个规则,导致网格无法正常渲染。
修复后的Grid.java代码如下:
import javafx.concurrent.Task; import javafx.scene.control.ProgressBar; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; import javafx.application.Platform; public class Grid { private final GridPane grid; public Grid(int totalCols, int totalRows, int CELL_HORIZONTAL_GAP, int CELL_VERTICAL_GAP, int CELL_HORIZONTAL_SIZE, int CELL_VERTICAL_SIZE, ProgressBar pgbProgress) { grid = new GridPane(); grid.setHgap(CELL_HORIZONTAL_GAP); grid.setVgap(CELL_VERTICAL_GAP); TextField[][] arrayLetterField = new TextField[totalCols][totalRows]; Task<Void> task = new Task<Void>() { @Override protected Void call() throws Exception { double total = totalCols * totalRows; double i = 1.0; for (int row = 0; row < totalRows; row++) { for (int col = 0; col < totalCols; col++) { // 用Platform.runLater把UI操作丢到UI线程执行 int finalCol = col; int finalRow = row; Platform.runLater(() -> { TextField textField = new TextField(); textField.setMinSize(CELL_HORIZONTAL_SIZE, CELL_VERTICAL_SIZE); textField.setMaxSize(CELL_HORIZONTAL_SIZE, CELL_VERTICAL_SIZE); grid.add(textField, finalCol, finalRow); arrayLetterField[finalCol][finalRow] = textField; }); Thread.sleep(100); updateProgress(i, total); i++; } } return null; } }; pgbProgress.progressProperty().bind(task.progressProperty()); new Thread(task).start(); } public GridPane getGrid() { return grid; } }
说明
Platform.runLater()会将传入的UI操作任务放入JavaFX应用线程的队列中执行,确保所有UI组件的创建和修改都在正确的线程进行,这样网格就能正常渲染。- 后台线程只负责控制进度更新和延迟逻辑,不直接操作UI,避免线程安全问题。
- 可以移除
pgbProgress.setProgress(0);的重复调用,因为Task启动时进度默认是0,且你已经在scpGrid的内容监听器里重置了进度。
内容的提问来源于stack exchange,提问作者Adalberto J. Brasaca
相关产品推荐
相关产品推荐

