3

I am trying to understand how can I implement MVC into my Java application, read couple of tutorials and MVC hello worlds, but I am still not sure about many things, so I would be glad if you could help me understand all this.

Let's say I have a simple GUI application, that is used for storing and working with various buildings. I can add building by chosing type from list and pressing button. These buldings will be stored in some arraylist, displayed in GUI and will be editable (number of rooms, number of floors ..... not important). Buildings will be displayed in JComboBox and after choosing some building, the setting panel for that building will appear.

So far I have 2 "components". Buildings (container) and building. I have created the BuildingsModel class, which holds the buildings, has some methods to work with them and notifies observers after change. Then I have a BuildingsView class, which is observing the BuildingsModel. Then I have BuldingsController class just with constructor method, that takes BuildingsModel and BuildingsView as parameters, binds the view to model as observer, create some initial buildings and add some listeners to the view.

Now I don't know how to continue. There are few things that I am not very happy about.

  1. I have binded listener to the button, that will get the current selection from JList in view, create new object (xxxBuildingModel) and add it to BuildingsModel. However, the JList contains just String representations of all building types and to avoid long if-else statemenets and finding the right class for that String, I had to use reflection. (Each building type has its own class that extends BuildingModel.) Is there any better way to do it?

  2. The second listener is bound to JComboBox that contains already created building instances. After selecting building in that combobox, I want to display settings form for that building. So I guess there should be some view class associated with BuildingModel that will display its current settings (model's state). But I am not sure, how to do it from the BuildingsController's constructor context. I have access only to building's model, so how should I "find" the right view for that instance and display it? Maybe I am doing it all wrong and the combobox shouldn't contain just models but controllers (which has access to both model and view), in that case I could just call the controller's view method, which will get the needed data from model, passes it to view and display it. I don't if I should work with concrete building instance as model, or as controller or as some custom class that encapsulates all these 3 components (controller, model, view) ...

Here are the most important parts of the code. I didn't include the BuildingModel class and its subclasses, because for now they are just blank classes with toString() method.

    public class BuildingsController {
        public BuildingsController(final BuildingsModel model, final BuildingsView view) {
            class addBuildingButtonActionListener implements ActionListener {
                @Override
                public void actionPerformed(ActionEvent e) {
                    String selectedBuildingType;
                    if ((selectedBuildingType = view.getSelectedBuildingType()) != null) {
                        try {
                            BuildingModel newBuilding = (BuildingModel) Class.forName("building.models." + selectedBuildingType + "BuildingModel").newInstance();
                            model.addBuilding(newBuilding);

                        } catch (InstantiationException ex) {
                            //addToErrorLog
                        } catch (IllegalAccessException ex) {
                            //addToErrorLog
                        } catch (ClassNotFoundException ex) {
                            //addToErrorLog
                        }
                    } else {
                        //addToLog - NO_BUILDING_SELECTED
                    }
                }
            }

            class buildingComboBoxSelectListener implements ActionListener {
                @Override
                public void actionPerformed(ActionEvent e) {
                    BuildingModel selectedBuilding;
                    if ((selectedBuilding = view.getSelectedBuilding()) != null) {
                        //display current building form???
                    }
                }
            }
            model.addObserver(view);
            model.addBuilding(new HospitalBuildingModel());
            model.addBuilding(new SchoolBuildingModel());

            view.fillBuildingTypesList(BuildingsModel.getAllBuildingsTypes());
            view.addAddBuildingButtonListener(new addBuildingButtonActionListener());
            view.addActiveBuildingsListener(new buildingComboBoxSelectListener());
        }
    }
    public class BuildingsModel extends Observable {
        private static String[] allBuildingsTypes = {"School", "Hospital", "Stadion"};
        private ArrayList<BuildingModel> buildings = new ArrayList<BuildingModel>();

        public void addBuilding(BuildingModel building){
            this.buildings.add(building);
            this.setChanged();
            this.notifyObservers();
        }
        public BuildingModel[] getAllBuildings(){
            return this.buildings.toArray(new BuildingModel[this.buildings.size()]);
        }
        public static String[] getAllBuildingsTypes(){
            return BuildingsModel.allBuildingsTypes;
        }

    }
    public class BuildingsView implements Observer {
        private static String name = "Buldings";
        private static String addBuildingButtonText = "Add building";
        private JComboBox allActiveBuildings = new JComboBox();
        private JList buildingTypesList = new JList();
        private JButton addBuildingButton = new JButton(BuildingsView.addBuildingButtonText);


        @Override
        public void update(Observable model, Object o){
            BuildingModel[] allBuildings = ((BuildingsModel) model).getAllBuildings();
            this.allActiveBuildings.removeAllItems();
            for(BuildingModel building : allBuildings){
                this.allActiveBuildings.addItem(building);
            }
        }
        public String getSelectedBuildingType(){
            return (String) this.buildingTypesList.getSelectedValue();
        }
        public BuildingModel getSelectedBuilding(){
            return (BuildingModel) this.allActiveBuildings.getSelectedItem();
        }

        public void fillBuildingTypesList(String[] buildingTypes){
            this.buildingTypesList.setListData(buildingTypes);
        }

        public void addAddBuildingButtonListener(ActionListener l){
            this.addBuildingButton.addActionListener(l);
        }
        public void addActiveBuildingsListener(ActionListener l){
            this.allActiveBuildings.addActionListener(l);
        }
        public JComponent display(){
            JPanel panel = new JPanel();
            Border border = BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(BuildingsView.name), BorderFactory.createEmptyBorder(5, 5, 5, 5));
            panel.setBorder(border);
            panel.add(this.allActiveBuildings);
            panel.add(this.buildingTypesList);
            panel.add(this.addBuildingButton);

            panel.setPreferredSize(new Dimension(200, 262));

            return panel;
        }
    }

main:

    public static void main(String[] args) {
        BuildingsView buildingsView = new BuildingsView();
        BuildingsModel buildingsModel = new BuildingsModel();
        BuildingsController buldingsController = new BuildingsController(buildingsModel, buildingsView);


        mainWindow window = new mainWindow("MVC test", buildingsView);
        window.generateDefaultLayout();
        window.showMainWindow();

    }

window:

public class mainWindow extends JFrame {
    private JPanel buildingsPanel = new JPanel();
    private BuildingsView buildingsView;

    public mainWindow(String title, BuildingsView buildingsView) {
        this.buildingsView = buildingsView;
        this.setTitle(title);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public void generateDefaultLayout(){
        this.setLayout(new FlowLayout());
        this.setPreferredSize(new Dimension(1200, 920));
        this.addBuildingsPanel();
    }
    public void addBuildingsPanel(){
        this.buildingsPanel.add(this.buildingsView.display());
        this.add(this.buildingsPanel);
    }
    public void showMainWindow(){
        this.pack();
        this.setVisible(true);
    }
}

Thanks :)

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
user10099
  • 1,345
  • 2
  • 17
  • 23
  • If you keep your questions concise, you'll have a better chance of receiving a useful answer. People generally don't bother reading this must information. – Tom May 08 '13 at 20:21
  • http://codereview.stackexchange.com/ – Dave Jarvis May 08 '13 at 20:46
  • @Tom Well I just tried to describe my concerns as well as I could. As I said, this is new to me and don't know what's important for you to help me. I just need some real example of how I can use MVC pattern in Java OOP. All I have found was some simple few lines hello world examples, but didn't find any complex examples or real applications code – user10099 May 08 '13 at 21:02

1 Answers1

4

I have binded listener to the button, that will get the current selection from JList in view, create new object (xxxBuildingModel) and add it to BuildingsModel. However, the JList contains just String representations of all building types and to avoid long if-else statemenets and finding the right class for that String, I had to use reflection. (Each building type has its own class that extends BuildingModel.) Is there any better way to do it?

Yes. Don't have your JList hold Strings but rather have it hold Building (non-GUI) objects. Use a ListCellRenderer to tell the JList how to best display each Building. Then when a user selects an item from the JList, they're selecting an actual building object and not a String representation of it. Certainly don't consider reflection for any of this.

The second listener is bound to JComboBox that contains already created building instances. After selecting building in that combobox, I want to display settings form for that building. So I guess there should be some view class associated with BuildingModel that will display its current settings (model's state). But I am not sure, how to do it from the BuildingsController's constructor context.

You could have an AbstractAction class in your Control class and use it as a listener for the JComboBox (see my answer to a previous question here for more on this).

I have access only to building's model, so how should I "find" the right view for that instance and display it?

You will get the selected Building from the listener, and then the listener will notify the GUI to change views after selection by calling public methods on the view.

Maybe I am doing it all wrong and the combobox shouldn't contain just models but controllers (which has access to both model and view),

The combo box's listeners will be part of the control, yes. And this will have access to model and view.

in that case I could just call the controller's view method, which will get the needed data from model, passes it to view and display it.

I'm not sure enough about the specifics of your program itself to be able to answer this.


Edit 1
On review of your code, I see a potential problem:

    private static String[] allBuildingsTypes = {"School", "Hospital", 
            "Stadion"};
    private ArrayList<BuildingModel> buildings = new ArrayList<BuildingModel>();

You should not be working with Strings here but rather with a well-behaved object-oriented Building class, one that has a name String, and all other properties that are important in whatever properties your Building objects will need. Then your model should deal with a Collection of Building objects rather than String representation of buildings. Your Views will display properties of these Buildings and will allow the user to be able to update the properties that you allow them to change.

This goes to the core of your program's structure, and I feel is thus very important, and doing this will likely require that you change everything, but in the long run will be well worth it.

Edit 1b: I stand corrected. It looks like you have a class for this, BuildingModel, one whose code has not been posted here. I wonder if your BuildingTypes should be an enum that is part of your BuildingModel class.


Edit 2
For more Swing-MVC related answers with code, please check out these links:

Community
  • 1
  • 1
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373