3
\$\begingroup\$

i am trying to learn mvc (model view controller). there are many texts out there explaining what mvc is. but not many explaining how to actually code mvc. i found a bunch of questions here asking about mvc. some seem to contradict each other (view observe model or not, controller listens to view or not).

i made a simple calculator in java swing to try to implement mvc so i can get a review from you whether you think i have done it right or not. the calculator is super simple so the code can concentrate on the mvc aspects.

the main class

public class Main {
    public static void main(String[] args) {
        Model model = new Model();
        View view = new View();
        Controller controller = new Controller(model, view);
        view.setController(controller);
        controller.start();
    }
}

the model

public class Model {
    public int calculate(int i1, String op, int i2) {
        int res;
        switch (op) {
            case "+": res = i1 + i2; break;
            case "-": res = i1 - i2; break;
            default: throw new RuntimeException("impossible operator");
        }
        return res;
    }
}

the view

public class View {

    Controller controller;
    JLabel result;

    public void setController(Controller controller) {
        this.controller = controller;
    }

    public void show() {
        JFrame frame = new JFrame("calculator mvc");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel();
        JTextField input1 = new JTextField(10);
        JComboBox<String> operand = new JComboBox<String>(
                new String[] { "+", "-" });
        JTextField input2 = new JTextField(10);
        JButton calcbutton = new JButton("calculate");
        result = new JLabel("    ");
        result.setBorder(BorderFactory.createLineBorder(Color.BLACK));
        calcbutton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String s1 = input1.getText();
                String op = operand.getItemAt(operand.getSelectedIndex());
                String s2 = input2.getText();
                controller.handleUserInput(s1, op, s2);
            }
        });
        panel.add(input1);
        panel.add(operand);
        panel.add(input2);
        panel.add(calcbutton);
        panel.add(result);
        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
    }

    public void setResult(String res) {
        this.result.setText(res);
    }

}

the controller

public class Controller {

    Model model;
    View view;

    public Controller(Model model, View view) {
        this.model = model;
        this.view = view;
    }

    public void handleUserInput(String s1, String op, String s2) {
        int i1 = Integer.parseInt(s1);
        int i2 = Integer.parseInt(s2);
        int res = this.model.calculate(i1, op, i2);
        this.view.setResult(String.valueOf(res));
    }

    public void start() {
        this.view.show();
    }

}

some comments:

i took the mvc approach where view does not observe model. instead controller talks to view. in this case model does not have any state to observe anyway.

the operators in model could have been done in command pattern or strategy pattern. but that would have added at least three more classes (or interfaces). i decided to keep it simple.

model gets ints and returns ints. view gives strings and gets strings. controller is the one translating between the two.

is this good mvc?

just for comparison here is the code without mvc

public class MainNotMvc {
    public static void main(String[] args) {
        JFrame frame = new JFrame("calculator not mvc");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel();
        JTextField input1 = new JTextField(10);
        JComboBox<String> operand = new JComboBox<String>(
                new String[] { "+", "-" });
        JTextField input2 = new JTextField(10);
        JButton calcbutton = new JButton("calculate");
        JLabel result = new JLabel("    ");
        result.setBorder(BorderFactory.createLineBorder(Color.BLACK));
        calcbutton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                int i1 = Integer.parseInt(input1.getText());
                String op = operand.getItemAt(operand.getSelectedIndex());
                int i2 = Integer.parseInt(input2.getText());
                int res;
                switch (op) {
                    case "+": res = i1 + i2; break;
                    case "-": res = i1 - i2; break;
                    default: throw new RuntimeException("impossible operator");
                }
                result.setText(String.valueOf(res));
            }
        });
        panel.add(input1);
        panel.add(operand);
        panel.add(input2);
        panel.add(calcbutton);
        panel.add(result);
        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
    }
}

code in git: https://gitlab.com/lesmana/java-swing-simple-calculator-mvc

\$\endgroup\$
1
\$\begingroup\$

Your controller class looked fine. The calculate method in the model class should have been located in this class. In this example application, the model class should be empty. There is no data to retain.

Your Main class needs to put the creation and execution of the Swing components on the Event Dispatch Thread. Here's how I changed your Main class.

import javax.swing.SwingUtilities;

public class Main implements Runnable {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Main());
    }

    @Override
    public void run() {
        Model model = new Model();
        View view = new View();
        Controller controller = new Controller(model, view);
        view.setController(controller);
        controller.start();
    }

}

In the View class, when creating Swing components, it's a good idea to group all of the method calls by each Swing component. The Swing components should be defined in row, column order.

I also added layout managers for the JPanels. You used a default FlowLayout for your one JPanel.

I made a few other tweaks to make your GUI look more like a calculator.

Calculator MVC

Here's what I did to your View class.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class View {

    Controller controller;
    JTextField result;

    public void setController(Controller controller) {
        this.controller = controller;
    }

    public void show() {
        JFrame frame = new JFrame("Calculator MVC");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        JPanel calculatorPanel = new JPanel();
        calculatorPanel.setLayout(new FlowLayout());

        JTextField input1 = new JTextField(10);
        input1.setHorizontalAlignment(JTextField.RIGHT);
        calculatorPanel.add(input1);

        JComboBox<String> operand = new JComboBox<String>(
                new String[] { "+", "-" });
        calculatorPanel.add(operand);

        JTextField input2 = new JTextField(10);
        input2.setHorizontalAlignment(JTextField.RIGHT);
        calculatorPanel.add(input2);

        JLabel label = new JLabel(" = ");
        calculatorPanel.add(label);

        result = new JTextField(10);
        result.setBorder(BorderFactory.createLineBorder(
                Color.BLACK));
        result.setEditable(false);
        result.setHorizontalAlignment(JTextField.RIGHT);
        calculatorPanel.add(result);

        panel.add(calculatorPanel, 
                BorderLayout.BEFORE_FIRST_LINE);

        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new FlowLayout());

        JButton calcbutton = new JButton("Calculate"); 
        calcbutton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String s1 = input1.getText();
                String op = operand.getItemAt(
                        operand.getSelectedIndex());
                String s2 = input2.getText();
                controller.handleUserInput(s1, op, s2);
            }
        });
        buttonPanel.add(calcbutton);

        panel.add(buttonPanel, BorderLayout.AFTER_LAST_LINE);

        frame.add(panel);
        frame.getRootPane().setDefaultButton(calcbutton);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public void setResult(String res) {
        this.result.setText(res);
    }

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.