0

I'm new in Java FX and have some basic understanding problems with controllers, FXML files and their interaction.

I am using Scenebuilder and tried something simple. I have got a FXML file with just an AnchorPane and it is linked with the Controller class. It should be my main class to play around with those other FXML Controllers.

I have got a "Knopf" FXML which just includes a Button with its Controller "KnopfController" And I have got a "Feld" FXML which includes a Textfield its Controller ist "FeldController".

Both "Knopf.FXML" and "Feld.FXML" are included in the Anchor Pane.

My Code looks like this:

package sample;

import javafx.fxml.FXML;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;

public class Controller {

@FXML
private AnchorPane Main;

@FXML
private FeldController feldController;

@FXML
private KnopfController knopfController;

public void textChange(MouseEvent event){
    //Hier Comes the Code
}

Now my Question is:

How can I interact between my Button (if it's pressed) and my TextField (that it should say "Hello" when the button is pressed?

Both the Button (fx:id="Knopf" in the Knopf.FXML) and the TextField (fx:id="Feld" in the Feld.FXML) should be useable for me.

How can I archive that?

I know I could use just both in an Controller but I want to practice the Interaction between Controllers and FXML files.

  • Ok I guess I understand it now. I have to implement Methods inside the "Feld" Controller and the "Knopf" Controller class, so I can use it in my Main Controller Class. – user6567125 Sep 07 '17 at 12:46
  • Best way is to use an MVC (or similar) approach. Share a model with the different controllers and update/observe data in the model. See e.g. https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx. Another approach is just to pass a reference to the `Controller` to the `KnopfController`, so you can call `textChange()` in the button event handler, and then just define a method that updates the text field in `FeldController` and call it from the `textChange()` method. – James_D Sep 07 '17 at 12:46
  • Thank you I tried the second approach of yours. I get a NullpointerException, where I use the textChange() method in the KnopfController. I Just added @FXML Controller controller; to the KnopfController. Can u tell me what I have to write to get it into the FXML file? Including it in the SceneBuilder is not the solution I guess. – user6567125 Sep 07 '17 at 14:01
  • Yeah, that won't work (there is nothing with `fx:id="controller"` in `Knopf.fxml`). You have to create a `setController(...)` method in `KnopfController` and call `knopfController.setController(this)` from the main controller's `initialize()` method. – James_D Sep 07 '17 at 14:05

1 Answers1

0

A simple way is just to define the methods you need in each controller and call them from the event handler. In this case, your KnopfController will need to call the textChange() method in the main controller (so it needs a reference to the Controller), and then textChange() will call a method in the FeldController.

So you would do something like this:

public class FeldController {

    @FXML
    private TextField textField ;

    public void updateText(String text) {
        textField.setText(text);
    }
}

KnopfController needs a reference to the "main controller", so it will need

public class KnopfController {

    private Controller mainController ;

    public void setMainController(Controller mainController) {
        this.mainController = mainController ;
    }

    // button handler:
    @FXML
    private void handleButtonClick(ActionEvent event) {
        mainController.textChange("Hello");
    }
}

And now your main controller wires it all together:

public class Controller {

    @FXML
    private AnchorPane Main;

    @FXML
    private FeldController feldController;

    @FXML
    private KnopfController knopfController;

    public void initialize() {
        knopfController.setMainController(this);
    }

    public void textChange(String newText){
        feldController.updateText(newText);
    }
}

While this works, the design problem here is that you have tightly coupled the controllers (e.g. your KnopfController depends on the Controller class, which to some extent works against the idea of factoring it out into a separate FXML-controller pair in the first place). A better approach is to share a data model among the controllers, and update/observe the model.

E.g.:

public class Model {

    private final StringProperty text = new SimpleStringProperty() ;

    public StringProperty textProperty() {
        return text ;
    }

    public final String getText() {
        return textProperty().get();
    }

    public final void setText(String text) {
        textProperty().set(text);
    }
}

and now the basic idea is to do

public class KnopfController {

    private Model model ;

    @FXML
    private void handleButton() {
        model.setText("Hello");
    }
}

and

public class FeldController {

    @FXML
    private TextField textField ;

    private Model model ;

    // ...
        textField.textProperty().bindBidirectional(model.textProperty());
    // ...
}

Of course, both these controllers need to have a reference to the same model instance for this to work, so you need to "inject" this into the controllers. You can do this just by creating setModel() methods and calling them:

public class KnopfController {

    private Model model ;

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

    @FXML
    private void handleButton() {
        model.setText("Hello");
    }
}

and for FeldController you can do the binding when the model is set:

 public class FeldController {

    @FXML
    private TextField textField ;

    private Model model ;

    public void setModel(Model model) {
        this.model = model ;
        textField.textProperty().bindBidirectional(model.textProperty());
    }
}

Then your main controller just reduces to:

public class Controller {

    private Model model ;

    @FXML
    private FeldController feldController;

    @FXML
    private KnopfController knopfController;

    public void initialize() {
        model = new Model();
        knopfController.setModel(model);
        feldController.setModel(model);
    }
}

You can go one step further, and use a controller factory to provide the model to the controllers for you. I posted an example of doing that in a github repo. Note this is only proof-of-concept: to do DI like this you should probably use a framework for it - afterburner.fx provides a simple but flexible JavaFX DI framework (or you can use other, standard, frameworks such as Spring or Guice).

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Wow mate thank you so much for your effort! I will try to look at it. Read the fist part today and had problems with understanding MVC and how to use it.(The Model part with creating a new Model) Also it's the first time I got encountered with initialize() method and the setController Method. Hopefully I will get into it. Will read into it. Hopefully I will refere back to you once fully understood. Its a pitty It's not that simple like I thought and a simple thing is that complicated. – user6567125 Sep 09 '17 at 13:03