动态构建JavaFX UI

纳普斯特

我是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类型,则具有一个布尔单选按钮;如果是列表,则具有一个下拉列表;一个值的文本字段,等等。这些问题可能会因用户而异,因此用户可以通过此文件配置问题。

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

实现此功能的一个好的起点是什么?

詹姆斯·D

您可以采取几种方法。

一种是简单地解析XML文件,并在您使用Java代码时创建FX控件。这不是很糟糕的方法,但是您根本不会使用任何FXML。基本思想是创建一个DocumentBuilder,使用它将xml文件解析为Document,这是xml文档的内存模型。您可以使用它来遍历xml元素,并为每个xml元素创建适当的JavaFX UI元素,然后将它们添加到some中Pane

另一种方法是使用可扩展样式表语言转换将XML文件转换为FXML我当然不是这项技术的专家,但是这个想法很简单:

定义一个xsl文件,文件基本上FXML根据xml文件的内容定义文件的外观再说一次,我对的细节不是很熟悉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>

现在,您只需Transformer要从该xsl文件创建一个文件(我将其命名为xml2fxml.xsl)。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上述内容的细微更新

尽管这很巧妙,但它几乎太透明了,因为它使得很难访问控制器中的表单控件。您需要对FXML定义的场景图的根元素的内容进行一些难看的检查,才能找到正确的元素。

本示例使用一些反射来获取值。您也可以通过大量instanceof测试和一些转换来做到这一点它还通过“知道”它们全部在第1列中来获取控件,这确实违反了视图和控制器的分离;最好在id分配给控件的约定上有所区别(将它们与Labels区别开),然后改用它。

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();
        }
    }
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

使用片段构建动态UI

来自分类Dev

JavaFX:仅使用一个场景还是使用多个场景来构建具有静态和动态内容的UI?

来自分类Dev

基于Web技术堆栈构建JavaFX应用程序的UI

来自分类Dev

基于Web技术堆栈构建JavaFX应用程序的UI

来自分类Dev

R Shiny:如何构建动态UI(文本输入)

来自分类Dev

动态UI闪亮实现

来自分类Dev

JSON对象的动态UI

来自分类Dev

动态创建UI android

来自分类Dev

动态Kendo UI TreeList

来自分类Dev

UI5:使用不同的图标从JSON动态构建ListItems

来自分类Dev

动态构建视图模型并应用UI验证,Asp.Net MVC 5

来自分类Dev

用gulp构建kendo ui

来自分类Dev

用gulp构建kendo ui

来自分类Dev

如何构建以下 UI 项?

来自分类Dev

UI控件之间的动态填充

来自分类Dev

动态ui-sref值

来自分类Dev

UI控件之间的动态填充

来自分类Dev

Kendo Ui PanelBar动态负载

来自分类Dev

如何创建动态ui Rshiny

来自分类Dev

Angular UI Grid:如何将HTML与cellTemplate绑定,以及如何动态构建ng-click可工作的HTML

来自分类Dev

Angular UI Grid:如何将HTML与cellTemplate绑定,以及如何动态构建ng-click可工作的HTML

来自分类Dev

动态子菜单的jquery_ui菜单ui参数

来自分类Dev

角度ui路由器指令动态ui-sref

来自分类Dev

React UI组件库的构建脚本

来自分类Dev

gradle构建使用100%CPU并阻止UI

来自分类Dev

使用Litho和JSON构建UI

来自分类Dev

用于构建UI的JS / HTML / CSS框架

来自分类Dev

以编程方式为 ListView 的项目构建 UI

来自分类Dev

从JavaFX中的不同线程更新UI