JavaFX中代码设置ComboBox在GridPane的rowSpan不生效问题
我尝试通过代码向已有的GridPane(名为daysGridPane)中添加一个ComboBox,要求该ComboBox跨两行显示。整体布局嵌套关系为:daysGridPane放在parentGridPane内的ScrollPane中,目的是让daysGridPane内容可滚动,其余UI保持固定显示。但实际添加后,ComboBox并未跨两行,仅占据上方单元格。
窗口显示时,“Monday”下方的第一个ComboBox是通过SceneBuilder的FXML定义的,我在SceneBuilder中创建了第二个跨两行的ComboBox,并将所有等效设置复制到代码中,但仍未生效。从截图可见网格线,新增的行比parentGridPane的行更小,不确定这是否为问题根源。
相关FXML文件
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.Insets?> <?import javafx.scene.control.Button?> <?import javafx.scene.control.ComboBox?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.ScrollPane?> <?import javafx.scene.control.Separator?> <?import javafx.scene.layout.ColumnConstraints?> <?import javafx.scene.layout.GridPane?> <?import javafx.scene.layout.RowConstraints?> <GridPane fx:id="parentGridPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" stylesheets="@application.css" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.levin.ctrl.TrainingPlanEditorCtrl"> <children> <ComboBox fx:id="startHourComboBox" GridPane.columnIndex="1" GridPane.rowIndex="1"> <GridPane.margin> <Insets top="6.0" /> </GridPane.margin> </ComboBox> <ComboBox fx:id="startMinutesComboBox" GridPane.columnIndex="3" GridPane.rowIndex="1"> <GridPane.margin> <Insets right="6.0" top="6.0" /> </GridPane.margin> </ComboBox> <ComboBox fx:id="endHourComboBox" GridPane.columnIndex="1" GridPane.rowIndex="2"> <GridPane.margin> <Insets bottom="12.0" top="6.0" /> </GridPane.margin> </ComboBox> <ComboBox fx:id="endMinutesComboBox" GridPane.columnIndex="3" GridPane.rowIndex="2"> <GridPane.margin> <Insets bottom="12.0" right="6.0" top="6.0" /> </GridPane.margin> </ComboBox> <Label text=":" GridPane.columnIndex="2" GridPane.rowIndex="1"> <GridPane.margin> <Insets left="3.0" right="3.0" /> </GridPane.margin> </Label> <Label text=":" GridPane.columnIndex="2" GridPane.rowIndex="2"> <GridPane.margin> <Insets left="3.0" right="3.0" /> </GridPane.margin> </Label> <Label text="Start" GridPane.rowIndex="1"> <GridPane.margin> <Insets left="12.0" right="6.0" /> </GridPane.margin> </Label> <Label text="End" GridPane.rowIndex="2"> <GridPane.margin> <Insets left="12.0" right="6.0" /> </GridPane.margin> </Label> <Button fx:id="addDayButton" mnemonicParsing="false" onAction="#addDay" text="+" GridPane.columnIndex="5"> <GridPane.margin> <Insets right="12.0" top="12.0" /> </GridPane.margin> </Button> <Button fx:id="addIntervallButton" mnemonicParsing="false" onAction="#addIntervall" text="+" GridPane.columnIndex="1" GridPane.rowIndex="3"> <GridPane.margin> <Insets bottom="12.0" top="12.0" /> </GridPane.margin> </Button> <Separator prefWidth="200.0" GridPane.columnSpan="2147483647" GridPane.rowIndex="4"> <GridPane.margin> <Insets bottom="6.0" top="6.0" /> </GridPane.margin> </Separator> <Button fx:id="okButton" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#ok" text="OK" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="5"> <GridPane.margin> <Insets bottom="24.0" top="6.0" /> </GridPane.margin> </Button> <Button fx:id="cancelButton" mnemonicParsing="false" onAction="#cancel" text="Cancel" GridPane.columnIndex="4" GridPane.rowIndex="5"> <GridPane.margin> <Insets bottom="24.0" top="6.0" /> </GridPane.margin> </Button> <ScrollPane fx:id="daysScrollPane" GridPane.columnIndex="4" GridPane.hgrow="ALWAYS" GridPane.rowSpan="3" GridPane.vgrow="ALWAYS"> <content> <GridPane fx:id="daysGridPane" maxWidth="1.7976931348623157E308"> <columnConstraints> <ColumnConstraints /> </columnConstraints> <rowConstraints> <RowConstraints minHeight="10.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS" /> </rowConstraints> <children> <ComboBox fx:id="dayComboBox"> <GridPane.margin> <Insets bottom="6.0" right="12.0" /> </GridPane.margin> </ComboBox> <ComboBox fx:id="sessionComboBox" maxHeight="1.7976931348623157E308" GridPane.rowIndex="1" GridPane.rowSpan="2"> <GridPane.margin> <Insets right="12.0" /> </GridPane.margin> </ComboBox> </children> </GridPane> </content> <GridPane.margin> <Insets right="12.0" top="12.0" /> </GridPane.margin> </ScrollPane> </children> <columnConstraints> <ColumnConstraints /> <ColumnConstraints /> <ColumnConstraints /> <ColumnConstraints /> <ColumnConstraints hgrow="ALWAYS" maxWidth="1.7976931348623157E308" minWidth="10.0" /> <ColumnConstraints /> </columnConstraints> <rowConstraints> <RowConstraints minHeight="10.0" /> <RowConstraints /> <RowConstraints minHeight="10.0" /> <RowConstraints minHeight="10.0" /> <RowConstraints minHeight="10.0" vgrow="NEVER" /> <RowConstraints minHeight="10.0" vgrow="NEVER" /> </rowConstraints> </GridPane>
原添加ComboBox的代码
@FXML protected void addIntervall(ActionEvent event) { // Find the index for the new column int newRowIndex = parentGridPane.getRowConstraints().size() -3; // add-intervall, ok, cancel button rows ... // extend session daysGridPane GridPane.setRowSpan(daysScrollPane, GridPane.getRowSpan(daysScrollPane) + 2); daysGridPane.setGridLinesVisible(true); RowConstraints rowC1 = new RowConstraints(); // we need two new rows that are spaned by the new ComboBox RowConstraints rowC2 = new RowConstraints(); rowC1.setVgrow(Priority.ALWAYS); rowC1.setMaxHeight(Double.MAX_VALUE); rowC1.setFillHeight(true); rowC2.setVgrow(Priority.ALWAYS); rowC2.setMaxHeight(Double.MAX_VALUE); rowC2.setFillHeight(true); daysGridPane.getRowConstraints().add(newRowIndex, rowC1 ); daysGridPane.getRowConstraints().add(newRowIndex, rowC2); ComboBox<Session> newSessionCB = new ComboBox<Session>(); daysGridPane.add(newSessionCB, 0, newRowIndex); GridPane.setRowSpan(newSessionCB, 2); newSessionCB.setMaxHeight(Double.MAX_VALUE); GridPane.setVgrow(newSessionCB, Priority.ALWAYS); newSessionCB.setItems(persistance.loadSessions(null)); }
问题根源与修正方案
1. 行索引计算错误
你用parentGridPane.getRowConstraints().size() -3作为新行索引,但该索引是针对parentGridPane的,而daysGridPane的行约束列表是独立的,应该基于daysGridPane的现有行数计算起始索引。
2. 新增行插入顺序错误
连续两次向同一索引插入行约束,会导致行顺序颠倒,破坏跨行列的布局计算逻辑,应将新行追加到daysGridPane行约束的末尾。
3. 不必要的ScrollPane行跨度调整
daysGridPane在ScrollPane内部,调整ScrollPane的行跨度不会影响其内部布局,反而可能破坏parentGridPane的整体布局,应删除该操作。
修正后的代码
@FXML protected void addIntervall(ActionEvent event) { // 基于daysGridPane的现有行数计算新行起始索引 int newRowIndex = daysGridPane.getRowConstraints().size(); daysGridPane.setGridLinesVisible(true); RowConstraints rowC1 = new RowConstraints(); RowConstraints rowC2 = new RowConstraints(); // 和FXML中定义的行保持一致的属性 rowC1.setVgrow(Priority.ALWAYS); rowC1.setPrefHeight(30.0); rowC1.setFillHeight(true); rowC2.setVgrow(Priority.ALWAYS); rowC2.setPrefHeight(30.0); rowC2.setFillHeight(true); // 将新行追加到daysGridPane的行约束末尾 daysGridPane.getRowConstraints().addAll(rowC1, rowC2); ComboBox<Session> newSessionCB = new ComboBox<>(); daysGridPane.add(newSessionCB, 0, newRowIndex); GridPane.setRowSpan(newSessionCB, 2); newSessionCB.setMaxHeight(Double.MAX_VALUE); GridPane.setVgrow(newSessionCB, Priority.ALWAYS); // 添加和FXML中一致的边距 GridPane.setMargin(newSessionCB, new Insets(0, 12, 0, 0)); newSessionCB.setItems(persistance.loadSessions(null)); }
另外,确保daysGridPane的maxHeight已设置为Double.MAX_VALUE(FXML中已配置),这样它能随内容扩展高度,保证ScrollPane正常滚动。
内容的提问来源于stack exchange,提问作者Alex

