Building JavaFX UI dynamically on the fly

后端 未结 1 2013
醉梦人生
醉梦人生 2021-01-15 00:41

I am a novice to JavaFX. Still fiddling around a few samples to try and decide if that works out for the application that we are trying to build. First phase of our app is

1条回答
  •  轻奢々
    轻奢々 (楼主)
    2021-01-15 01:35

    There are a couple of approaches you can take to this.

    One is to simply parse the XML file, and create the FX controls in Java code as you go. This isn't too bad an approach, but you will not have any FXML at all. The basic idea is that you create a DocumentBuilder, use it to parse your xml file to a Document, which is an in-memory model of the xml document. You can use that to iterate through the xml elements, and create the appropriate JavaFX UI element for each xml element, adding them to some Pane.

    The other approach is to use an Extensible Stylesheet Language Transformation to transform your XML file into FXML. I am certainly no expert in this technology, but the idea is pretty straightforward:

    Define an xsl file that basically defines what your FXML file should look like based on the contents of your xml file. Again, I am not really familiar with the details of xsl, but something like this appears to work for your example:

    
    
    
    
        
    
            
                java.lang.*
            
    
            
                javafx.scene.layout.*
            
    
            
                javafx.scene.control.*
            
    
            
                javafx.geometry.Insets
            
    
            
                javafx.collections.FXCollections
            
    
                     
                
                    
                    
                
                
                    
                
    
                
                
    
            
    
        
    
        
            
    
    
        
                1
                
                    
                    
        
    
        
                 
        
    
        
            
                
                
                    
                        
                            
                        
                    
                
            
        
    
        
            
                
                
        
    
        
            
                
             
        
    
    
    

    Now you just need to create a Transformer from that xsl file (I named it xml2fxml.xsl). The transform method will read the xml file, transform it according to the rules in the xsl file, and send the output to an output stream. You just need a little trickery to pipe that to an input stream and instruct the FXMLLoader to read the generated fxml from it:

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

    Update: (Also note slight updates to the xsl above)

    While this is quite slick, it is almost too transparent in that it makes it difficult to get access to the form controls in the controller. You need to do a bit of ugly examination of the contents of the root element of the FXML-defined scene graph in order to find the correct elements.

    This example uses some reflection to get at the values; you could also do it with a lot of instanceof tests and some casting. It also gets at the controls by "knowing" that they are all in column 1, which really violates the separation of view and controller; it might be better to have some convention on the id assigned to the controls (that distinguishes them from the Labels) and use that instead.

    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 controls = new HashMap<>();
        private final List 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 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();
            }
        }
    }
    

    0 讨论(0)
提交回复
热议问题