我是 JavaFX 的新手。仍在摆弄一些示例,以尝试确定这是否适用于我们正在尝试构建的应用程序。 我们应用程序的第一阶段是一种数据输入阶段,用户将准备好提出很多问题并记录他的回答。这里的问题是,另一个团队正在构建问题集,并且这些问题在 XML 中,如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<userData>
<question id="Q1" type ="desc">
<text>Enter the name of the Component</text>
</question>
<question id ="Q2" type ="list">
<text>Select mechanism type</text>
<choices>
<choice> Type 1 </choice>
<choice> Type 2 </choice>
<choice> Type 3 </choice>
<choice> Type 4 </choice>
</choices>
</question>
<question id ="Q5" type="yesNo">
<text> Whether the parts have been verified by the supervisor? </text>
</question>
<question id ="Q6" type="yesNo">
<text> Whether the component is available within the domicile </text>
</question>
<question id ="Q7" type="value">
<text> Enter the quantity </text>
</question>
<question id ="Q8" type="value">
<text> Enter the unit price </text>
</question>
</userData>
它对应于各种字段,例如如果是 yesNo 类型则有一个 bool 单选按钮,如果是列表则有一个下拉列表,一个用于值的文本字段等等。这些问题可能会因用户而异,因此用户可以通过此文件配置问题。
想法是在应用程序启动期间加载此 xml,解析它们并动态构建适当的 UI 组件。这可以通过 JavaFX 实现吗?我使用通过 SceneBuilder 构建的 FXML 文件制作了这个应用程序的小型原型(prototype)。但诀窍是在解析启动期间加载的问题 XML 文件后,以编程方式生成构建此 UI 组件所需的 FXML 文件以进行查询。
实现此功能的良好开端是什么?
最佳答案
您可以采用多种方法。
一种是简单地解析 XML 文件,并在您进行操作时用 Java 代码创建 FX 控件。这种方法还算不错,但您根本不会有任何 FXML。基本思想是创建一个 DocumentBuilder
, 用它将你的 xml 文件解析为 Document
,这是 xml 文档的内存模型。您可以使用它来遍历 xml 元素,并为每个 xml 元素创建适当的 JavaFX UI 元素,将它们添加到某个 Pane
。
另一种方法是使用可扩展样式表语言转换将您的 XML
文件转换为 FXML
。我当然不是这项技术的专家,但这个想法非常简单:
定义一个 xsl
文件,该文件基本上根据 xml
文件的内容定义您的 FXML
文件的外观。同样,我不太熟悉 xsl
的细节,但类似这样的内容似乎适用于您的示例:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fx="http://javafx.com/fxml">
<xsl:template match="/">
<xsl:processing-instruction name="import">
java.lang.*
</xsl:processing-instruction>
<xsl:processing-instruction name="import">
javafx.scene.layout.*
</xsl:processing-instruction>
<xsl:processing-instruction name="import">
javafx.scene.control.*
</xsl:processing-instruction>
<xsl:processing-instruction name="import">
javafx.geometry.Insets
</xsl:processing-instruction>
<xsl:processing-instruction name="import">
javafx.collections.FXCollections
</xsl:processing-instruction>
<GridPane hgap="5" vgap="5" fx:id="form" fx:controller="xml2fx.FormController">
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="NEVER" />
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS" />
</columnConstraints>
<padding>
<Insets top="10" bottom="10" left="10" right="10"/>
</padding>
<xsl:apply-templates select="//text"/>
<xsl:apply-templates select="//question"/>
</GridPane>
</xsl:template>
<xsl:template match="text">
<Label text="{.}" wrapText="true" textAlignment="RIGHT"
GridPane.columnIndex="0"
GridPane.rowIndex="{count(../preceding-sibling::question)}" />
</xsl:template>
<xsl:template name="controlCoords">
<GridPane.columnIndex>1</GridPane.columnIndex>
<GridPane.rowIndex>
<xsl:value-of select="count(preceding-sibling::question)"/>
</GridPane.rowIndex>
</xsl:template>
<xsl:template match="question[@type='desc']">
<TextArea fx:id="{@id}" id="{@id}">
<xsl:call-template name="controlCoords" />
</TextArea>
</xsl:template>
<xsl:template match="question[@type='list']">
<ComboBox fx:id="{@id}" id="{@id}">
<xsl:call-template name="controlCoords" />
<items>
<FXCollections fx:factory="observableArrayList">
<xsl:for-each select="choices/choice">
<String fx:value="{.}"/>
</xsl:for-each>
</FXCollections>
</items>
</ComboBox>
</xsl:template>
<xsl:template match="question[@type='value']">
<TextField fx:id="{@id}" id="{@id}">
<xsl:call-template name="controlCoords" />
</TextField>
</xsl:template>
<xsl:template match="question[@type='yesNo']">
<CheckBox fx:id="{@id}" id="{@id}">
<xsl:call-template name="controlCoords" />
</CheckBox>
</xsl:template>
</xsl:stylesheet>
现在您只需从该 xsl
文件(我将其命名为 xml2fxml.xsl
)创建一个 Transformer
。 transform
方法将读取 xml
文件,根据 xsl
文件中的规则对其进行转换,并将输出发送到输出流。您只需要一点小技巧将其通过管道传输到输入流并指示 FXMLLoader
从中读取生成的 fxml
:
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
final PipedOutputStream transformOutput = new PipedOutputStream();
final PipedInputStream fxmlInputStream = new PipedInputStream(transformOutput);
Thread transformThread = new Thread( () -> {
try {
StreamSource xsltSource = new StreamSource(getClass().getResourceAsStream("xml2fxml.xsl"));
Transformer transformer = TransformerFactory.newInstance().newTransformer(xsltSource);
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StreamSource xmlSource = new StreamSource(getClass().getResourceAsStream("questionnaire.xml"));
StreamResult transformerResult = new StreamResult(transformOutput);
transformer.transform(xmlSource, transformerResult);
transformOutput.close();
} catch (Exception e) {
e.printStackTrace();
}
});
transformThread.start();
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(fxmlInputStream);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
更新:(另请注意对上面的 xsl
的细微更新)
虽然这很巧妙,但它几乎太透明了,因为它很难访问 Controller 中的表单控件。您需要对 FXML 定义的场景图的根元素的内容进行一些粗略的检查,以便找到正确的元素。
此示例使用一些反射来获取值;您也可以通过大量 instanceof
测试和一些转换来完成。它还通过“知道”它们都在第 1 列中来获取控件,这确实违反了 View 和 Controller 的分离;最好对分配给控件的 id
进行一些约定(将它们与 Label
区分开来)并改用它。
package xml2fx;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.SelectionModel;
import javafx.scene.control.TextInputControl;
import javafx.scene.layout.GridPane;
public class FormController {
private static final String SELECTED_VALUE = "yes" ;
private static final String UNSELECTED_VALUE = "no" ;
@FXML
private GridPane form ;
private final Map<String, Control> controls = new HashMap<>();
private final List<String> ids = new ArrayList<>();
public void initialize() {
for (Node node : form.getChildren()) {
if (GridPane.getColumnIndex(node) == 1) { // all form controls are in column 1
if (node instanceof Control) {
String id = node.getId();
controls.put(id, (Control)node);
ids.add(id);
}
}
}
}
public List<String> getIds() {
return Collections.unmodifiableList(ids);
}
public String getUserValue(String id) throws ReflectiveOperationException {
Control control = controls.get(id);
if (control == null) throw new IllegalArgumentException("No control with id "+id);
return getValueForControl(control);
}
private String getValueForControl(Control control) throws ReflectiveOperationException {
if (isTextControl(control)) {
return getTextControlValue(control);
} else if (isSelectable(control)) {
return getSelectableValue(control);
} else if (hasSelectionModel(control)) {
return getSelectedValue(control);
}
throw new IllegalArgumentException("Unsupported control class: "+control.getClass().getName());
}
private boolean isTextControl(Control control) {
// TextAreas, TextFields, etc:
return control instanceof TextInputControl ;
}
private String getTextControlValue(Control control) {
return ((TextInputControl) control).getText();
}
private boolean isSelectable(Control control) {
// ToggleButtons, CheckBoxes...
for (Method method : control.getClass().getMethods()) {
if (method.getName().equals("isSelected")
&& method.getReturnType() == boolean.class) {
return true ;
}
}
return false ;
}
private String getSelectableValue(Control control) throws ReflectiveOperationException {
Method isSelectedMethod = control.getClass().getMethod("isSelected");
boolean selected = (Boolean) isSelectedMethod.invoke(control);
if (selected) {
return SELECTED_VALUE ;
} else {
return UNSELECTED_VALUE ;
}
}
private boolean hasSelectionModel(Control control) {
// ComboBoxes, ListViews, TableViews, etc:
for (Method method : control.getClass().getMethods()) {
if (method.getName().equals("getSelectionModel")) {
return true ;
}
}
return false ;
}
private String getSelectedValue(Control control) throws ReflectiveOperationException {
Method selectionModelMethod = control.getClass().getMethod("getSelectionModel");
SelectionModel<?> selectionModel = (SelectionModel<?>) selectionModelMethod.invoke(control);
Object selectedItem = selectionModel.getSelectedItem();
if (selectedItem == null) {
return "" ;
} else {
return selectedItem.toString();
}
}
}
关于xml - 即时动态构建 JavaFX UI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24321871/