Search code examples
javamodel-view-controllerobserver-pattern

MVC Java: How does a Controller set listeners to the children classes of a View


I have a Controller, and a View with many children views with children with children. Example: JPanels within JPanels that have buttons and fields for a controller to pass to the model.

The current way I'm doing it is instantiating 'Controllers' in the view that have action listeners and access my models which are singletons. This works- But it's definitely not MVC.

So the question is- how do I do it?

Is the only way to daisy chain from the controller: mainview.getSubView().getSubView().getSubView().setActionListener(new AL()); and: mainview.getSubView().getSubView().getSubView().getSomeTextFieldText();

This seems extremely impractical [and I can't see the benefits of this method]

Any tips or guidance would be helpful. No tutorial I see explains MVC with children views.

On a side note- I know I should have the views and models use observable/observer - but even with those I have a problem with setting action listeners.


Solution

  • I think you are making a mistake of conceptually tying a "view" to individual GUI components. In the MVC model there really is not such concept as a "subview".

    If you have a frame, for example, with many panels and subpanels and subcomponents such as buttons, etc., that entire thing is the view/controller interface in MVC. Your top-level frame (or some encapsulating class, perhaps your GUI has many frames, it doesn't matter) would provide the interface to the controller (via, say, events) and the view (via, say, listeners). Exactly how your UI is arranged is abstracted behind that. Think of your entire UI as a black box. How events are dispatched / provided from the internal components is part of the UI implementation, and this may very well involve event chains and delegates and etc. -- but that is not something the view/controller is concerned with.

    So you end up with something like (conceptual example):

    Model m = new Model();
    View v = new View(m);
    Controller c = new Controller(m);
    MyFrame gui = new MyFrame(v, c);
    

    Then:

    public MyFrame (View v, Controller c) {
    
       // register listeners to view, e.g.
       v.addChangeListener(this /* or some other ui component */);
    
       // send events to controller, e.g.
       addActionListener(c /* or some interface that c provides */).
    
       // or even:
       deleteButton.addActionListener(new ActionListener(){
           @Override public void actionPerformed (ActionEvent e) {
               c.doDelete();
           }
       });   
    
    }
    

    In an ideal situation, you can completely rework the hierarchy of your GUI, having a totally different component tree and organization, and, providing the information being conveyed through the GUI remains unchanged, the view and controller remain unchanged as well.

    Once you get used to the MVC pattern, you may start to take shortcuts here and there (for example, in many cases the view and/or controller are just middle-men for events, and the GUI itself sometimes ends up encapsulating the entire controller and view concepts, leaving you with a GUI, a model, and a bunch of event listeners -- the Swing event architecture is fairly MVC in itself), and boundaries may get fuzzier. That's OK. But there's no reason that either the view or the controller have to know about the structure and object tree in the GUI -- even in cases where controller/view are just abstract concepts instead of concrete classes.

    Sometimes MVC can get especially fuzzy when working with Swing because you kind of end up doing everything in an MVC way naturally, so when you try to explicitly impose an MVC pattern on your architecture, you're left wondering why that doesn't seem to change much at all (and it becomes difficult to see the benefits, because you forget that you're already doing it). Sometimes it's more of a way of thinking than a concrete way of doing.

    Essentially, any time your model is completely independent of your GUI, you are using some incarnation of MVC.

    Edit: I noticed you also tagged this with "observer pattern" and mentioned it briefly. It's worth noting that many times, explicitly implementing an observer pattern for Swing-based GUIs, at least in relatively simple applications, adds nothing but a redundant abstraction layer, as the entire Swing API is already fundamentally based on this design pattern (it's the whole event -> listener subsystem).

    Frequently I have found that something like the following works well, using the so-called observer pattern all around, where the controller was essentially just a collection of event listeners, like:

     public class Controller {
         Model m;
         public ActionListener getDeleteListener () {
             return new ActionListener() {
                 @Override public void actionPerformed (ActionEvent e) {
                     m.deleteSomething();
                 }
             };
         }
     }
    

    And then the GUI does something like:

     public class GUI extends JFrame {
         JButton deleteButton; 
         public GUI (View v, Controller c) {
             deleteButton.addActionListener(c.getDeleteListener()); 
         }
     }
    

    But, there are many ways to skin that cat. In that example, even, once you are familiar with the concept, and if it is appropriate, you could take a shortcut and just have the GUI constructor register listeners that modify the model. Then, as mentioned above, the controller becomes just an abstract concept.