简体   繁体   中英

JavaFx Generate multiple scene from single FXML

I started a javafx project that interact with a database (add new, edit, delete, find,...).
My database contains a lot of tables, and each table requires its own scene (which are almost the same!!!).
so My problem is: instead of implementing ~20 fxml file, is it possible to make a single fxml file that will change its content based on the Class name (for example) passed to its controller?
if yes, any tips to achieve it?
Here is what I have tried:
added a HashMap for each TableClass containing all the attributes needed to be in the scene and and iterate through it in a ControllerClass to add the control to the scene!!! but FAILED!
TableSampleClass:

public class TableSampleClass{
public static final HashMap<String, String> attr;
static {
    attr = new HashMap<String, String>();
    attr.put("ref", "text");
    attr.put("name", "text");
    attr.put("adress", "text");
    attr.put("Mobile", "tel");
    attr.put("mail", "mail");
    attr.put("isalive", "checkbox");//just to illustrate what i want !
    }
     ........
   }

ControllerClass:

@FXML
private AnchorPane pane;
@Override
public void initialize(URL location, ResourceBundle resources)
{     Iterator it = TableSampleClass.attr.entrySet().iterator();
    while(it.hasNext())
    {
        Map.Entry pair = (Map.Entry)it.next();
        switch (pair.getValue().toString()){
        case "text":
            TextField txt =new TextField(pair.getValue().toString());
            txt.setPromptText(pair.getKey().toString());
            Label lbl =new Label(pair.getKey().toString());
            pane.getChildren().add(txt);
            break;
        }
    }
}

I hope I did explain myself clearly !!

Use the Custom component FXML pattern . The way this works is that you write a controller class which loads the corresponding FXML file in the constructor. Since you use this by calling the constructor, you can allow the constructor to take parameters and use those parameters to configure the component after loading the FXML.

The code you showed isn't really very clear to me, but this structure would look something like:

public class MyCustomTableDisplay extends AnchorPane {

    @FXML
    private AnchorPane pane ;

    public MyCustomTableDisplay(Map<String, String> config) {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
            loader.setRoot(this); 
            loader.setController(this);
            loader.load();
        } catch (IOException exc) {
            throw new RuntimeException(exc);
        }

        // now configure pane based on config passed in...
    }
}

The FXML file has to use a "dynamic root", which means the root element is determined by calling setRoot(...) on the FXMLLoader (as seen in the code above). So it looks like

<!-- imports omitted -->
<fx:root type="AnchorPane" xmlns:fx="..." ... >
    <!-- usual fxml stuff -->
    <AnchorPane fx:id="pane" ...>

    <AnchorPane>
</fx:root>

Now you just do

Map<String, String> config = new HashMap<>();
// populate config...
MyCustomTableDisplay display = new MyCustomTableDisplay(config);
Scene scene = new Scene(display);
// etc...

The type of the fx:root can be basically any node type, but must match the type of the class. Then you can include any fxml content you want. The class acts as the controller, so you can inject values from the FXML in the usual way: they will be initialized (as usual) when the load() method is invoked on the loader.

Potential Approaches

There are some third party libraries which generate forms and interface UIs for JavaFX dynamically, such as FXForm2 and ControlsFX PropertySheets .

It is also possible to generate a table from data dynamically , which seems like it might be what you are actually trying to do.

On FXML

For most of those systems, you don't define the internal GUI of the widget using FXML. Instead the low-level fields are dynamically generated, either by introspection on data or Java classes. All you would do is add the appropriate library to SceneBuilder and import the high level control (eg the ControlsFX PropertySheet), just declaring only that in FXML and not defining the detailed fields for the control within FXML.

If needed, and the systems in the linked libraries aren't what you need, you can also create custom controls and import those for use in SceneBuilder , though those will still likely work in a similarly manner of dynamically generating the detail fields from data or Java class introspection via reflection .

On Dynamic Field Generation via Introspection

Creating a custom control based upon reflection is an advanced topic recommended only for experienced Java and JavaFX developers. You can view the PropertySheet implementation for example code for such an approach, that uses java beans and an Introspector via a BeanPropertyUtils utility class. Java beans are a pretty large topic, I can't run through in detail here. Only some features of the java beans specification are required to achieve what you need, as other parts of the specification are obsoleted by newer JavaFX features such as properties and binding.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM