Ma Tâm
Ma Tâm

Reputation: 307

Javafx Update a tableview from another FXML

Reference article Applying MVC With JavaFx

I have interface 1 FXML the following

<SplitPane>
    <items>
        <TableView prefHeight="200.0" prefWidth="200.0">
            <columns>
                <TableColumn prefWidth="75.0" text="User" />
                <TableColumn prefWidth="75.0" text="Pass" />
            </columns>
        </TableView>
        <fx:include source="Container.fxml"/>
    </items>
</SplitPane>

Container.fxml it is only used to store xml interface example I used to embed InputData.fxml,Test.fxml on TabPanel

And interface 3 InputData.xml I contains textField the username and password and a save button.

I would like to ask every time I press the button save the,interface 1 table can get value and update from the value of the interface 3

Because the interface 3 Input data.xml not embedded directly into the interface 1 it only appears when eligible.So I can not transfer data fxml directly to the following

        DataModel model = new DataModel();
        listController.initModel(model);
        editorController.initModel(model);
        menuController.initModel(model);

It on the interface if can do please help me

Upvotes: 1

Views: 2100

Answers (1)

James_D
James_D

Reputation: 209684

Two approaches here.

Passing the model to nested controllers

You can inject the controller from the included FXML into the controller for the including FXML using the nested controllers technique. Then the "outer controller" can propagate the model to the "nested controller".

So, e.g. if you have

<SplitPane xmlns="..." fx:controller="com.mycompany.MainController">
    <items>
        <TableView prefHeight="200.0" prefWidth="200.0">
            <columns>
                <TableColumn prefWidth="75.0" text="User" />
                <TableColumn prefWidth="75.0" text="Pass" />
            </columns>
        </TableView>
        <fx:include fx:id="container" source="Container.fxml"/>
    </items>
</SplitPane>

Suppose the controller class for Container.fxml is ContainerController. Then you would do:

public class MainController {

    private Model model ;

    @FXML
    private ContainerController containerController ; // name is fx:id with "Controller" appended

    public void setModel(Model model) {
        this.model = model ;
        containerController.setModel(model);
        // ...
    }

    // ...
}

and of course

public class ContainerController {

    private Model model ;

    public void setModel(Model model) {
        this.model = model ;
        // ...
    }

    // ...
}

Using a controller factory

If you have a lot of included FXML files and controllers, etc, this can start to get unmaintainable. In that case, a better approach might be to initialize the model in the controllers' constructors, and to use a controllerFactory on the FXMLLoader to create controllers with a model passed to the constructor. So now you controllers look like:

public class MainController {

    private final Model model ;

    public MainController(Model model) {
        this.model = model ;
    }

    public void initialize() {
        // bind controls to model here...
    }
}

and similarly for ContainerController, etc. Note this is much cleaner than the previous versions, where you had to worry about the model being set at an arbitrary time. Here you're guaranteed it is set when any code in the controller is executed.

Now you need a bit of magic for the FMXLLoader to create the controllers correctly when the FXML is loaded:

Model model = new Model();
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
Callback<Class<?>, Object> controllerFactory = (Class<?> type) -> {
    try {
        for (Constructor<?> c : type.getConstructors()) {
            if (c.getParameterCount() == 1 && c.getParameterTypes()[0].equals(Model.class)) {
                return c.newInstance(model);
            }
        }
        // couldn't find constructor taking a model, just use default:
        return type.newInstance();
    } catch (Exception exc) {
        exc.printStackTrace();
        return null ;
    }
};
loader.setControllerFactory(controllerFactory);

Parent root = loader.load();
// ...

Note that FXML files loaded via an <fx:include> tag will use the same controller factory as the "including" FXML file. So this will automatically pass the same model to the nested controller.

Using DI frameworks

Finally, if you really do a lot of this, you might want to consider using a dependency inject framework to manage the dependencies on the model for you. Afterburner.fx is a dedicated JavaFX DI framework, and then everything is as simple as

public class MainController {

    @Inject 
    private Model model ;

    public void initialize() {
        // bind UI elements to model...
    }
}

You can also use Spring or Guice. E.g. with Spring, configure the controllers as prototype beans, the model as a singleton bean, and write the controllers to inject the model as with afterburner.fx. Then you can tell an FXMLLoader to use Spring to create the controllers with

// Spring application context:
ApplicationContext appContext =  ... ;
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
loader.setControllerFactory(appContext::getBean);
Parent root = loader.load();

Upvotes: 3

Related Questions