0

I've a JavaFX application with multiple controllers. The controllers must comunicate each other to manipulate view objects. There is a root controller for the root view and many sub-controllers, each for it's own sub-view. My question it's about the best design for this structure. For now I've organized things like this:

  • RootController has references to each subcontroller;
  • Each subController has references to it's own subView objects and handles events from this objects;
  • When an action on view object A (may be a Button) in subView A must effects a B in subView B, subController A mantain reference to a ChangeListener A1 that is defined and injected, during initialization, from rootController; the ChangeListener calls the subController B's method to act on viewObject B; eventually rootController can act on its own viewObjects in the ChangeListener.

I'll show an example:

public SubControllerA {
    private Model model;
    //button defined in subView A
    @FXML
    private Button buttonA;
    //Can be an user-defined interface, actually I used ChangeListener from JavaFX
    private ChangeListener listenerA;

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

    private void setListenerA(ChangeListener listener) {
        this.listenerA=listener;
    }

    //method to handle buttonA click
    @FXML
    private void handleButtonAClick(){
        doSomethingOnSubViewA();
        listener.changed();
    }
}


public SubControllerB {
    private Model model;
    //label defined in subView B
    @FXML
    private Label labelB;

    private void setModel(Model model){
        this.model=model;
    }    
    private void doSomethingOnLabelB(){
        label.setText("button A clicked!");
    }
}

public RootController {
    private Model model;
    @FXML
    private BorderPane mainPanel;
    @FXML
    private SubControllerA subControllerA;
    @FXML
    private SubControllerB subControllerB;
    //initialize listeners
    @FXML
    public void initialize() { 
        this.model = new Model();
        subControllerA.setModel(model);
        subControllerB.setModel(model);
        subControllerA.setListenerA(new ChangeListener(){
             public void changed() {
                  doSomethingOnMainPanel();
                  subControllerB.doSomethingOnLabelB();
             }
        }
    } 
}

Do you think this is a correct design? Do you have any suggestion to improve it?

Thanks

user1781028
  • 1,478
  • 4
  • 22
  • 45
  • Model is shared among all the controllers. Added in question' code snippet – user1781028 Mar 11 '16 at 13:16
  • 1
    So if you have a shared model, why not just have your controllers update the model and observe properties in it? Then there is no need to wire all the controllers together (which introduces a whole lot of coupling between them). See http://stackoverflow.com/questions/32342864/applying-mvc-with-javafx – James_D Mar 11 '16 at 13:24
  • So I have to add a property to the model for each event I want my controllers react? In my example, what should be the property for the button pressed event? A BooleanProperty "buttonPressed"? – user1781028 Mar 11 '16 at 13:57
  • 1
    If the user is pressing a button, you are responding to it by changing the state of the application somehow. You just need to think of the application in terms of the data. What do you want to happen to the data when the button is pressed? You implement that directly in the controller that has access to the button (because it also has access to the model); anything else that needs to respond will be able to do so by observing the correct properties in the model. – James_D Mar 11 '16 at 14:59
  • 1
    Really the only difference from your design is that I'm saying that the `ChangeListener` that you share between the controllers is really state, so it should be in the model. (So you end up with one fewer thing to share between the controllers.) – James_D Mar 11 '16 at 15:10

2 Answers2

0

I'm posting the solution proposed by James_D applied to my example. Here is the model:

public class Model {

    private Observable<Boolean> buttonPressed;

    public Boolean isButtonPressed(){
        return buttonPressed.get();
    }

    public void setButtonPressed(Boolean pressed){
        this.buttonPressed.set(pressed);
    }

    public Observable<Boolean> buttonPressedProperty(){
        return this.buttonPressed;
    }
}

And then SubControllerA:

public SubControllerA {
    private Model model;
    //button defined in subView A
    @FXML
    private Button buttonA;

    public void setModel(Model model){
        this.model=model;
        model.buttonPressedProperty.addListener((observable, oldValue, newValue)->{
            doSomethingOnSubViewA();
        });
    }

    private void doSomethingOnSubViewA() { ... }

    //method to handle buttonA click
    @FXML
    private void handleButtonAClick(){
        model.setButtonPressed(true);
    }
}

SubControllerB:

public SubControllerB {
    private Model model;
    //label defined in subView B
    @FXML
    private Label labelB;

    public void setModel(Model model){
        this.model=model;
        model.buttonPressedProperty.addListener((observable, oldValue, newValue)->{
            doSomethingOnLabelB();
        });
    }

    private void doSomethingOnLabelB() { ... }
}

In a similar way RootController can listen for model changes. This pattern removes a lot of dependencies between controllers. Thanks to James_D.

user1781028
  • 1,478
  • 4
  • 22
  • 45
0

What I did was make MainController extend TimerTask. I encapsulated the sub-controllers in the MainController.

  1. Each sub-controller has a state or multiple state variables, like enums or even booleans. Each sub-controller, when a method is invoked by MainController, can build a view and handle events from that view.

  2. When the user chooses to add data or close a view, a state variable in the sub-controller changes and the MainController responds to the state of that variable when the MainController.run() method detects a state change. It can tell the MainController to extract values from the sub-controller properites, or it can simply tell the MainController that the sub-controller is no longer needed to maintain and respond to its view.

  3. Then the MainController lets another subcontroller build the view and/or update the model data model via invocation from the MainController.