Javafx pass parameter and values from one controll

2019-01-11 02:36发布

问题:

I am new to JavaFx and hence I cannot find a solution to solve my problem

Suppose I have following application structure :

- views
      - first.fxml -> this has a button called btnSend and a textfield called txtEnter
      - second.fxml -> this has a textarea called txtView
- Controller 
      - FirstController  -> controller for First
      - SecondController -> controller for second
- Modal
      - AppModal -> here I have a getter and a setter method , 
                    as getText() and setText(String text)

- App
     - Main.java -> This one used FXMLLoader to load first.fxml and second.fxml together.

What is the optimal/best way to display the text in SecondController passing it from FirstController. I mean, I enter a text in txtEnter and press the button btnSend and after pressing the button I want the text to be displayed in txtView which is using another controller. I have read a lot about the observers pattern and JavaFX properties can be used to solve this, but unfortunately I am unable to implement a working solution.

I would be humbly thankful if you experts can help me in this. I know its not correct but can anyone please give me a working solution for the above project structure.

Thanks in advance.

回答1:

Use an observable StringProperty in the model:

public class AppModel {

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

Make your controllers have access to the model:

public class FirstController {

    private final AppModel model ;

    @FXML
    private TextField textEnter ;

    public FirstController(AppModel model) {
        this.model = model ;
    }

    // action event handler for button:
    @FXML
    private void sendText() { 
        model.setText(textEnter.getText());
    }
}

and

public class SecondController {

    private final AppModel model ;

    @FXML
    private TextArea txtView ;

    public SecondController(AppModel model) {
        this.model = model ;
    }

    public void initialize() {
        // update text area if text in model changes:
        model.textProperty().addListener((obs, oldText, newText) -> 
            txtView.setText(newText));
    }
}

The slightly tricky part now is that the controllers don't have a no-arg constructor, which means the default mechanism for the FXMLLoader to create them won't work. The easiest way is to set them manually. Remove both the <fx:controller> attributes from the FXML files, and then in your Main class do

AppModel model = new AppModel();

FXMLLoader firstLoader = new FXMLLoader(getClass().getResource("first.fxml"));
firstLoader.setController(new FirstController(model));
Parent firstUI = firstLoader.load();

FXMLLoader secondLoader = new FXMLLoader(getClass().getResource("second.fxml"));
secondLoader.setController(new SecondController(model));
Parent secondUI = secondLoader.load();

If you prefer to keep the <fx:controller> attributes in the FXML files, you can use a controllerFactory instead, which essentially instructs the FXMLLoader as to how to create a controller:

AppModel model = new AppModel();

Callback<Class<?>, Object> controllerFactory = type -> {
    if (type == FirstController.class) {
        return new FirstController(model);
    } else if (type == SecondController.class) {
        return new SecondController(model);
    } else {
        try {
            return type.newInstance() ; // default behavior - invoke no-arg construtor
        } catch (Exception exc) {
            System.err.println("Could not create controller for "+type.getName());
            throw new RuntimeException(exc);
        }
    }
};

FXMLLoader firstLoader = new FXMLLoader(getClass().getResource("first.fxml"));
firstLoader.setControllerFactory(controllerFactory);
Parent firstUI = firstLoader.load();

FXMLLoader secondLoader = new FXMLLoader(getClass().getResource("second.fxml"));
secondLoader.setControllerFactory(controllerFactory);
Parent secondUI = secondLoader.load();

You can make the controller factory even more flexible by using (more) reflection; basically you can implement the logic "if the controller type has a constructor taking an AppModel, call that constructor, otherwise call the no-arg constructor".

If you are creating a large application which needs to do a lot of this, then you might consider using afterburner.fx, which is a framework that essentially allows you to inject the model into the controllers using annotations.