Search code examples
javaswingjpanelpaintcomponent

passing data between JPanels


I'm making Tetris in Java and would like to have the game play on the left and scoring, buttons, and nextPiece on the right, like so:

enter image description here

You'll notice that the score on the Game Panel is updating, but the score on the Score Panel (on the right) is not.

On the Game Panel, I have global variables for score and level: private int level, totalScore; which are initialized to 0.

and this in my paint component():

        g.setColor(Color.RED);
        g.drawString("Level: " + level, this.getWidth()/2+110, this.getHeight()/2-200);
        g.drawString("Score: " + totalScore, this.getWidth()/2+110, this.getHeight()/2-170);

Then I have this code within the Game Panel which calculates level and scoring:

public void changeLevel () {
    int max = (level+1)*100;
    if (totalScore >= max) {
        System.out.println(max + "reached... next level");
        level++;
        totalScore = 0;
        timer();
    }
}

public int tallyScore(int totalLines) {
    int score = 0;
    switch (totalLines) {
        case 1: score = 40 * (level + 1);
                break;
        case 2: score = 100 * (level + 1);
                break;
        case 3: score = 300 * (level + 1);
                break;
        case 4: score = 1200 * (level + 1); 
                break;
        default: break;
    }
    return score;
}

//loop through all rows starting at bottom (12 rows)
public void checkBottomFull() {
    int lines = 0;
    for(int row = totalRows-1; row > 0; row--) {
        while (isFull(row)) {       
            lines++;
            clearRow(row);   
        }
    }
    totalScore += tallyScore(lines);    
    //check if level needs to be changed based on current score...
    changeLevel();
    //reset lines after score has been incremented
    lines=0;
}

And since I want the Score Panel to display the score, I have these two methods in Game Panel which return the global variables:

public int getScore() {
    return totalScore;
}
public int getLevel() {
    return level;
}

In my Score Panel paintComponent() I have board.getLevel() and board.getScore() (board class is the Game Panel) so I can feed the Game Panel scores to the Score Panel.

    g.setColor(Color.BLACK);
    g.drawString("Level: " + board.getLevel(), this.getWidth()/2, this.getHeight()/2-130);
    g.drawString("Score: " + board.getScore(), this.getWidth()/2, this.getHeight()/2-100);

Yet as you can see from the picture, these scores are not updating.

Any thoughts?

Thanks!


Solution

  • You will want to separate concerns so that they can be shared. Consider making a class for the logic and data that underlies your GUI, and say you call this class you Model class. Then you can give it a level and a score field and make them "bound properties", meaning that other classes can listen for changes to these fields. I usually do this by giving my Model a SwingPropertyChangeSupport object and give it an addPropertyChangeListener(PropertyChangeListener listener) and a removePropertyChangeListener(PropertyChangeListener listener), and then I notify all registered PropertyChangeListener's of changes by calling PropertyChangeSupport's fire. e.g.,

    import java.beans.PropertyChangeListener;
    import javax.swing.event.SwingPropertyChangeSupport;
    
    public class Model {
       public static final String SCORE = "score";
       public static final String LEVEL = "level";
       private SwingPropertyChangeSupport pcSupport = 
             new SwingPropertyChangeSupport(this);
       private int score;
       private int level;
    
       public void addPropertyChangeListener(PropertyChangeListener listener) {
          pcSupport.addPropertyChangeListener(listener);
       }
    
       public void removePropertyChangeListener(PropertyChangeListener listener) {
          pcSupport.removePropertyChangeListener(listener);
       }
    
       public int getScore() {
          return score;
       }
    
       public void setScore(int score) {
          int oldValue = this.score;
          int newValue = score;
    
          this.score = score;
          pcSupport.firePropertyChange(SCORE, oldValue, newValue);
       }
    
       public int getLevel() {
          return level;
       }
    
       public void setLevel(int level) {
          int oldValue = this.level;
          int newValue = level;
    
          this.level = level;
          pcSupport.firePropertyChange(LEVEL, oldValue, newValue);
       }
    
    }
    

    Then any GUI or view component that wishes to listen to changes in the values may do so. The below class combines "View" with "Control" if you're studying MVC structure:

    import java.awt.FlowLayout;
    import java.beans.PropertyChangeEvent;
    import java.beans.PropertyChangeListener;
    
    import javax.swing.*;
    import javax.swing.event.ChangeEvent;
    import javax.swing.event.ChangeListener;
    
    public class UseModelGui {
    
       private static void createAndShowGui() {
          Panel1 panel1 = new Panel1();
          Panel2 panel2 = new Panel2();
          Model model = new Model();
    
          panel1.setModel(model);
          panel2.setModel(model);
    
          JFrame frame = new JFrame("UseModelGui");
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.getContentPane().setLayout(new FlowLayout());
          frame.getContentPane().add(panel1);
          frame.getContentPane().add(panel2);
          frame.pack();
          frame.setLocationByPlatform(true);
          frame.setVisible(true);
       }
    
       public static void main(String[] args) {
          SwingUtilities.invokeLater(new Runnable() {
             public void run() {
                createAndShowGui();
             }
          });
       }
    }
    
    class Panel1 extends JPanel {
       private JTextField scoreField = new JTextField(2);
       private JTextField levelField = new JTextField(2);
    
       public Panel1() {
          scoreField.setFocusable(false);
          scoreField.setEditable(false);
          levelField.setFocusable(false);
          levelField.setEditable(false);
    
          add(new JLabel("score:"));
          add(scoreField);
          add(new JLabel("Level:"));
          add(levelField);
          setBorder(BorderFactory.createTitledBorder("Check Values"));
       }
    
       public void setModel(Model model) {
          model.addPropertyChangeListener(new PropertyChangeListener() {
    
             @Override
             public void propertyChange(PropertyChangeEvent pcEvt) {
                if (Model.LEVEL.equals(pcEvt.getPropertyName())) {
                   String level = pcEvt.getNewValue().toString();
                   levelField.setText(level);
                } else if (Model.SCORE.equals(pcEvt.getPropertyName())) {
                   String score = pcEvt.getNewValue().toString();
                   scoreField.setText(score);
                }
    
             }
          });
       }
    
    }
    
    class Panel2 extends JPanel {
       private JSpinner scoreSpinner = new JSpinner(new SpinnerNumberModel(0, 0,
             20, 1));
       private JSpinner levelSpinner = new JSpinner(new SpinnerNumberModel(0, 0,
             10, 1));
       private Model model;
    
       public Panel2() {
          add(new JLabel("score:"));
          add(scoreSpinner);
          add(new JLabel("Level:"));
          add(levelSpinner);
          setBorder(BorderFactory.createTitledBorder("Set Values"));
    
          scoreSpinner.addChangeListener(new ChangeListener() {
    
             @Override
             public void stateChanged(ChangeEvent evt) {
                int score = ((Integer) scoreSpinner.getValue()).intValue();
                if (model != null) {
                   model.setScore(score);
                }
             }
          });
          levelSpinner.addChangeListener(new ChangeListener() {
    
             @Override
             public void stateChanged(ChangeEvent evt) {
                int level = ((Integer) levelSpinner.getValue()).intValue();
                if (model != null) {
                   model.setLevel(level);
                }
             }
          });
       }
    
       public void setModel(Model model) {
          this.model = model;
       }
    
    }
    

    The beauty of this is that Panel1 has no knowledge of Panel2, and Model has no knowledge of either. All Panel1 knows of is if Model changes. All Panel2 knows is that it is changing the Model's state. All the Model knows is that its state can change, and its values can be listened to.

    You're right that in this simple example this is over-kill, but as soon as you start having complex data and state, this makes sense and becomes quite helpful.