Xml 动态构建JavaFXUI

Xml 动态构建JavaFXUI,xml,javafx,javafx-2,javafx-8,fxml,Xml,Javafx,Javafx 2,Javafx 8,Fxml,我是JavaFX的新手。仍然在摆弄一些示例,试图确定这是否适用于我们正在尝试构建的应用程序。 我们应用程序的第一阶段是一种数据输入阶段,在这个阶段,用户将准备好大量的问题,并记录他的回答。这里需要注意的是,另一个团队正在构建问题集,这些问题是XML格式的,如下所示 <?xml version="1.0" encoding="UTF-8"?> <userData> <question id="Q1" type ="desc"> <text&

我是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>

输入组件的名称
选择机构类型
类型1
类型2
类型3
类型4
零件是否经过监理人的验证?
该组件是否在住所内可用
输入数量
输入单价
它对应于各种字段,例如,如果是yesNo类型,则有一个布尔单选按钮;如果是list,则有一个下拉列表;如果是值,则有一个文本字段,等等。这些问题可能会因用户而异,因此用户可以通过此文件配置问题

其思想是在应用程序启动期间加载此xml,解析它们并动态构建适当的UI组件。这可以通过JavaFX实现吗?我使用通过SceneBuilder构建的FXML文件制作了这个应用程序的一个小原型。但诀窍是在解析启动期间加载的问题XML文件后,生成构建此UI组件以进行编程查询所需的FXML文件


实现此功能的良好起点是什么

有几种方法可以解决这个问题

一种是简单地解析XML文件,并在运行时用Java代码创建FX控件。这不是一种很糟糕的方法,但是您将根本没有任何FXML。基本思想是创建一个,使用它将xml文件解析为,这是xml文档的内存模型。您可以使用它来迭代xml元素,并为每个xml元素创建适当的JavaFXUI元素,将它们添加到一些
窗格中

另一种方法是使用可扩展样式表语言转换将
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
有轻微更新)

虽然这是非常巧妙的,但它几乎太透明了,因为它使访问控制器中的表单控件变得非常困难。为了找到正确的元素,您需要对FXML定义的场景图的根元素的内容进行一些粗略的检查

本例使用一些反射来获取值;您还可以使用大量的
实例进行测试和一些强制转换。它还通过“知道”控件都在第1列中来获取控件,这实际上违反了视图和控制器的分离;最好对分配给控件的
id
有一些约定(将它们与
标签
s区分开来),并使用该约定

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();
        }
    }
}
包xml2fx;
导入java.lang.reflect.Method;
导入java.util.ArrayList;
导入java.util.Collections;
导入java.util.HashMap;
导入java.util.List;
导入java.util.Map;
导入javafx.fxml.fxml;
导入javafx.scene.Node;
导入javafx.scene.control.control;
导入javafx.scene.control.SelectionModel;
导入javafx.scene.control.TextInputControl;
导入javafx.scene.layout.GridPane;
公共类窗体控制器{
选择私有静态最终字符串\u VALUE=“是”;
未选择私有静态最终字符串\u VALUE=“否”;
@FXML
专用网格表格;
私有最终映射控件=new HashMap();
私有最终列表ID=new ArrayList();
公共无效初始化(){
for(节点:form.getChildren()){
如果(GridPane.getColumnIndex(node)==1){//所有表单控件都在列1中
if(控件的节点实例){
String id=node.getId();
控件。放置(id,(控件)节点);
添加(id);
}
}
}
}
公共列表getIds(){
返回集合。不可修改列表(ID);
}
公共字符串getUserValue(字符串id)引发ReflectiveOperationException{
Control=controls.get(id);
if(control==null)抛出新的IllegalArgumentException(“没有id为“+id的控件”);
返回getValueForControl(控制);
}
私有字符串getValueForControl(控件控件控件)引发ReflectiveOperationException{
if(isTextControl(控制)){
返回getTextControlValue(控件);
}否则,如果(可选择(控制)){
返回getSelectableValue(控件);
}else if(hasSelectionModel(控制)){
返回getSelectedValue(控件);
}
抛出新的IllegalArgumentException(“不支持的控件类:+control.getClass().getName());
}
专用布尔isTextControl(控制){
//文本区域、文本字段等:
返回TextInputControl的控制实例;
}
私有字符串getTextControlValue(控件){
return((TextInputControl)控件).getText();
}
私有布尔值可选择(控制){
//切换按钮、复选框。。。
对于(方法:control.getClass().getMethods()){
if(method.getName().equals(“isSelected”)
&&方法.getReturnType()=布尔值.class){
返回true;
}
}
返回false;
}
私有字符串getSelectableV
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();
        }
    }
}