Search code examples
javafxjavafx-8fxmlfxmlloaderopenjfx

FX 11: Controller loading in Baseclass


Injecting FXML into a derived FX Class(Controller) from the base class works - but why?

The code below is actually working. But i am curious why?

The FXML is loaded in the constructor of the abstract Base class (FXMLPopup) and injected to the derived class(TestfxmlController).

My problem: when the base class is constructed(and the fxml gets injected) the derived class hasn't been constructed yet. Also imho the base shouldn't know anything about the derived class, should it?

Further the field, that is to be injected is private in the derived class! So the loader has to make it accessible but there is no @FXML in the base that would give permision to do so(permision is given only in the derived class that hasn't yet been constructed - Well the Field doesn't exist at all in the base!).

Still the FXML gets correctly injected into the derived class - and the fields are actually the ones in the derived class. Why does this work?

Baseclass:

public abstract class FXMLPopup extends Popup implements Initializable {

    @SuppressWarnings("LeakingThisInConstructor")
    public FXMLPopup(String filename) {
        super();
        final FXMLLoader loader = new FXMLLoader(FXMLLoader.class.getResource(filename));
        //if a controller is set in the fxml, ignor it.
        loader.setControllerFactory(p -> this);
        try {
            this.getContent().add(loader.load());
        } catch (IOException ex) { }
     }
}

Derived class:

public class TestfxmlController extends FXMLPopup {

    @FXML
    private ChoiceBox<String> testChoiceBox;

    public TestfxmlController() {
        super("fxml/testfxml.fxml");
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        //works!!!!
        testChoiceBox.getItems().add("test");
    }
}

FXMLCode:

<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/11.0.1" fx:controller="TestfxmlController">
   <children>
      <ChoiceBox fx:id="testChoiceBox" layoutX="113.0" layoutY="160.0" prefWidth="150.0" />
   </children>
</AnchorPane>

What would I expect? I would expect Errors over errors. That the loader doesn't find the fields in the base class and that access is denied.... But it somehow magicaly works flawless. Despite I am violating like everything possible in such a small example. I'd like to understand the "magic" behind this...


Solution

  • I think I undrstand it now. The derived class is already constructed when the baseclass constructor is called - only not initialized.

    So the loader actually gets the derived class. With that given reflection is able to return the fields from the derived class.

    With that it becomes possible for the base class to initialize the fields of the derived class - even though the baseclass doesn't have any information about its future derivations. It doesn't need to. It gets that information through reflection(rsp. the loader does).

    It is not even crude as the derived class is actually known through reflection and therefore is known to be of the correct type.

    So I now think this generic FXML Popup code is actually perfectly valid.

    While this particular uscase is sound, it looks like there is a flaw in the FXML loader, when it comes to the documented usecase.

    Reason: if someone creates a control loaded from a fxml - file in the way documented(not this usecase) and distribute it as a library a user of that library may subclass it. The loader now will inject into the subclass and not in the fields it was ment to create causing a failure of the control(in this case the fields in the library class don't get initialized).

    So again: while the code in the question seems to work reliably, with this behavior the documented usecase can result in problems.