JavaFX 和 Spring - 可重用的 FXML 组件

JavaFX and Spring - Reusable FXML component

我正在使用 JavaFX 中的 Scene Builder 和 Spring Boot 构建 GUI。我的几个场景有一个共同点,就是一个简单的 KPI 指标,叫做 PerformanceBeacon。根据控制器 class 完成的配置,可以使用信标,例如对于预算、时间等项目参数。理想情况下,即使是一个场景也可能有多个这样的元素。

问题是,在一个场景中有多个 KPI 信标不知何故不起作用,因为所有嵌入式控制器都指向相同的内存,因此,我只能操纵一个 - 这是最后一个 - KPI 信标。

从 fxml 文件中截取的片段如下所示:

...
<VBox layoutX="301.0" layoutY="325.0" spacing="6.0" AnchorPane.leftAnchor="2.0" AnchorPane.topAnchor="24.0">
      <children>
               <fx:include source="PerformanceBeacon.fxml" fx:id="embeddedTimeKPI" />
               <fx:include source="PerformanceBeacon.fxml" fx:id="embeddedBudgetKPI"/>
               <fx:include source="PerformanceBeacon.fxml" fx:id="embeddedResourcesKPI"/>
               <fx:include source="PerformanceBeacon.fxml" fx:id="embeddedScopeKPI"/>
               <fx:include source="PerformanceBeacon.fxml" fx:id="embeddedEffortKPI"/>
               <fx:include source="PerformanceBeacon.fxml" fx:id="embeddedQualityKPI"/>
      </children>
</VBox>
...

嵌入式控制器在封闭 UI 的控制器 class 中定义,代码应 "only" 设置每个 PerformanceBeacon 中每个超链接的标签。

@Controller
public class ProductManagerCtrl implements Initializable {
 ...
 @FXML protected PerformanceBeaconCtrl embeddedTimeKPIController;       
 @FXML protected PerformanceBeaconCtrl embeddedBudgetKPIController; 
 @FXML protected PerformanceBeaconCtrl embeddedResourcesKPIController;  
 @FXML protected PerformanceBeaconCtrl embeddedScopeKPIController;      
 @FXML protected PerformanceBeaconCtrl embeddedEffortKPIController;     
 @FXML protected PerformanceBeaconCtrl embeddedQualityKPIController;

 ...

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {

        ...
        initializePerformanceIndicator();
        ...
     }

    protected void initializePerformanceIndicator() {
        embeddedBudgetKPIController.setHyperlink("Budget", null);
        embeddedScopeKPIController.setHyperlink("Scope", null);
        embeddedTimeKPIController.setHyperlink("Time", null);
        embeddedQualityKPIController.setHyperlink("Quality", null);
        embeddedResourcesKPIController.setHyperlink("Resources", null);
        embeddedEffortKPIController.setHyperlink("Effort estimates", null);
    }

当我调试代码时,这些嵌入式控制器中的每一个都指向相同的内存。如此有效,我有一个控制器,只能操纵最后一个元素。最后一个信标有 "Effort estimates" 标签,其他的都没有变化。

PerformanceBeacon.fxml比较简单。我复制了,如果你想自己试试

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

<?import java.lang.Double?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.shape.Polygon?>

<GridPane styleClass="pane" stylesheets="@../stylesheets/PerformanceBeacon.css" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.agiletunes.productmanager.controllers.PerformanceBeaconCtrl">
   <children>
      <Circle fx:id="beacon" fill="#1fff4d" radius="10.0" stroke="BLACK" strokeType="INSIDE" GridPane.columnIndex="1" GridPane.valignment="CENTER">
         <GridPane.margin>
            <Insets bottom="12.0" left="12.0" right="6.0" top="12.0" />
         </GridPane.margin>
      </Circle>
      <Polygon fx:id="trendArrow" fill="#11ff1c" rotate="45.0" stroke="#2e229a" strokeType="INSIDE" GridPane.columnIndex="2" GridPane.valignment="CENTER">
        <points>
          <Double fx:value="-10.0" />
          <Double fx:value="10.0" />
          <Double fx:value="-5.0" />
          <Double fx:value="10.0" />
          <Double fx:value="-5.0" />
          <Double fx:value="15.0" />
          <Double fx:value="5.0" />
          <Double fx:value="15.0" />
          <Double fx:value="5.0" />
          <Double fx:value="10.0" />
          <Double fx:value="10.0" />
          <Double fx:value="10.0" />
          <Double fx:value="0.0" />
          <Double fx:value="-10.0" />
        </points>
         <GridPane.margin>
            <Insets bottom="12.0" left="12.0" right="12.0" top="12.0" />
         </GridPane.margin>
      </Polygon>
      <Hyperlink fx:id="hyperlink" onAction="#showDetails" text="Subject">
         <GridPane.margin>
            <Insets left="24.0" />
         </GridPane.margin>
      </Hyperlink>
   </children>
   <columnConstraints>
      <ColumnConstraints hgrow="ALWAYS" maxWidth="1.7976931348623157E308" minWidth="170.0" />
      <ColumnConstraints minWidth="10.0" />
      <ColumnConstraints minWidth="10.0" />
   </columnConstraints>
   <rowConstraints>
      <RowConstraints />
   </rowConstraints>
</GridPane>

最后但同样重要的是 PerformanceBeacon 的控制器

@Controller
public class PerformanceBeaconCtrl {

    @FXML   protected Circle beacon;
    @FXML   private Polygon trendArrow;
    @FXML   private Hyperlink hyperlink;

    @FXML
    public void showDetails(ActionEvent event) {
        // TODO
    }

    public void setHyperlink(String label, URL url) {
        Platform.runLater(() -> {
            hyperlink.setText(label);
        });
    }

    public void setBeaconColor(Color color) {
        Platform.runLater(() -> {
            beacon.setFill(color);
        });
    }

    public void setTrend(Double angle) {
        Platform.runLater(() -> {
            trendArrow.rotateProperty().set(angle);
        });
    }

}

我也在代码的其他地方成功地使用了嵌入式 UI 元素/控制器。但绝不会多次使用相同的元素。

我不确定这是否是 Spring 的副作用?

我做错了什么?提前谢谢你。

所描述的观察结果表明该问题与设计中某处的单例相关。我对 Spring 影响的怀疑是正确的。 ProductManagerCtrl 控制器 class 使用

创建
fxmlLoader.setControllerFactory(c -> ProdMgrApp.springContext.getBean(c));

事实证明,这种方法默认创建单例。因此所有的嵌入式控制器也是单例的。修复它的方法是用

注释 PerformanceBeaconCtrl 控制器 class
@Scope("prototype")