3

I am doing my first Java project that includes a GUI. I know that the logic of the program should be seperated from the the view (the GUI) of the program. My problem is, that I do not understand how to update the GUI after something changes in the logic of the game, without coding it twice, once in the GUI and once in the logic.

Here is a very simplified example of what im trying to do:

Class for the logic:

public class Logic {

    private int importantVariable;

    public Logic(int importantVariable){
        this.importantVariable = importantVariable;
    }

    public int getImportantVariable() {
        return importantVariable;
    }

    public void increaseImportantVariable() {
        this.importantVariable++;
    }
}

Class for the GUI

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class GUI extends JFrame {

    private Logic logic;

    public GUI(Logic logic){
        this.logic = logic;

        this.setTitle("GUI");
        this.setSize(1200, 800);
        this.setLocationRelativeTo(null);//set the location to the middle of the display
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        JPanel guiPanel = new JPanel();

        //create label
        JLabel showVariableLabel = new JLabel(String.valueOf(logic.getImportantVariable()));
        guiPanel.add(showVariableLabel);

        //create Button
        JButton button = new JButton();
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                logic.increaseImportantVariable();
            }
        });
        guiPanel.add(button);

        this.add(guiPanel);
    }
}

Main class:

public class Main{

    public static void main(String[] args){
        Logic logic = new Logic(0);

        GUI gui = new GUI(logic);

        gui.setVisible(true);
    }
}

In this example, the GUI has one button and a label that shows a number. If the button is pressed, the number is increased. But how do I make the label show the increased number? I think it is not good to change the ActionListener to update the JLabel and the logic, because the program alomost already knows everything it has to know: how the logic works and how the logic is shown. That means my feeling says to implement the ActionListener as

button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                logic.increaseImportantVariable();
                showVariableLabel.setText(String.valueOf(logic.getImportantVariable()));
            }
        });

is not the best possible approach, even if it works (am I wrong here?). I think that because a real project would be much more complex and many variables might change in the logic, making it easier to update the whole GUI rather than just the components that changed.

So what im looking for is either to only change the logic and constantly update the whole GUI (which knows how to show the whole logic) or call something like gui.update() when something is changed in the logic to update the whole GUI. But for both ways I have no idea how to do that. I hope my problem is understandable and I do not have a big error in thinking on how to seperate the logic and the GUI in the best possible way.

c0der
  • 18,467
  • 6
  • 33
  • 65
adsesaft
  • 31
  • 3
  • 1
    This is a tricky question, but I think MadProgrammer's answer on [this question](https://stackoverflow.com/a/31576899/2180785) explains everything you need to know. Swing is already an MVC app, and thus trying to separate View from Logic is hard. – Frakcool Mar 27 '20 at 19:24
  • You've almost got it. The action listener updates the model and tells the view to update. The view gets the information from the model, but never changes anything in the model. Only controllers (action listeners) modify the model. – Gilbert Le Blanc Mar 27 '20 at 22:29

1 Answers1

1

To listen to model changes introduce a listener interface:

interface ModelObserver {
    void modelChanged();
}

Modify Logic so it can accept a listener and use it:

class Logic {

    ModelObserver observer;

    private int importantVariable;

    public Logic(int importantVariable, ModelObserver observer){
        this.importantVariable = importantVariable;
        this.observer = observer;
    }

    public int getImportantVariable() {
        return importantVariable;
    }

    public void increaseImportantVariable() {
        importantVariable++;
        observer.modelChanged();
    }
}

Main class acts as a controller so let it add the observer to the Model and respond to model changes:

public class Main implements ModelObserver{

    private final GUI gui;

    Main(){
        Logic logic = new Logic(0, this);
        gui = new GUI(logic);
        gui.setVisible(true);
    }

    @Override
    public void modelChanged() {
      gui.refresh();
    }

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

Putting it all together (for convince the entire code can be copy pasted into Main.java and run):

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

public class Main implements ModelObserver{

    private final GUI gui;

    Main(){
        Logic logic = new Logic(0, this);
        gui = new GUI(logic);
        gui.setVisible(true);
    }

    @Override
    public void modelChanged() {
      gui.refresh();
    }

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

interface ModelObserver {
    void modelChanged();
}

class GUI extends JFrame {

    private final Logic logic;
    private final  JLabel showVariableLabel;

    public GUI(Logic logic){

        this.logic = logic;

        this.setTitle("GUI");
        this.setLocationRelativeTo(null);//set the location to the middle of the display
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        JPanel guiPanel = new JPanel();

        //create label
        showVariableLabel = new JLabel(String.valueOf(logic.getImportantVariable()));
        guiPanel.add(showVariableLabel);

        //create Button
        JButton button = new JButton("Increment");
        button.addActionListener(e -> logic.increaseImportantVariable());
        guiPanel.add(button);

        this.add(guiPanel);
        pack();
    }

    public void refresh() {
        showVariableLabel.setText(String.valueOf(logic.getImportantVariable()));
    }
}

class Logic {

    ModelObserver observer;

    private int importantVariable;

    public Logic(int importantVariable, ModelObserver observer){
        this.importantVariable = importantVariable;
        this.observer = observer;
    }

    public int getImportantVariable() {
        return importantVariable;
    }

    public void increaseImportantVariable() {
        importantVariable++;
        observer.modelChanged();
    }
}
c0der
  • 18,467
  • 6
  • 33
  • 65