Search code examples
javaswinggridviewmodel-view-controllerjlabel

Create JLabels at Runtime: View in Swing does not update as intended


In a project I am working on I have problems with a View that does not update properly. I have extracted the relevant code (self containing and executable).

When the update button is hit, I expect the view in the upper section of the screen to display the data held in the PlayerDump-List. Therefore I first clear the view by calling removeAll(). Next, for each PlayerDump one new row should be created, each consisting of three labels.

When I run the debugger I can see that the labels are created and added to the parent view. But I cannot see the labels in my view. Any ideas why that is?

//imports [...]

public class Main {
    public static void main(String[] args) {
        Random r = new Random();

        JFrame frame = new JFrame();
        frame.setLayout(new BorderLayout());

        MultiplayerStatsTableView tv = new MultiplayerStatsTableView();
        frame.add(tv, BorderLayout.CENTER);

        Button b = new Button("Update");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ArrayList<PlayerDump> l = new ArrayList<PlayerDump>();

                for (int i = 0; i < 5; i++) {
                    l.add(new PlayerDump("" + r.nextInt(), r.nextInt(), r.nextInt()));
                }

                tv.statsChanged(l);
            }
        });

        frame.add(b, BorderLayout.SOUTH);

        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        frame.setSize(500, 500);
        frame.setVisible(true);
    }
}

class MultiplayerStatsTableView extends JPanel {
    private JPanel dataPanel;

    public MultiplayerStatsTableView() {
        setLayout(new BorderLayout());

        dataPanel = new JPanel(new GridLayout(0, 3, 2, 2));
        add(dataPanel, BorderLayout.NORTH);
    }

    public synchronized void statsChanged(ArrayList<PlayerDump> playersDump) {
        for (PlayerDump dump : playersDump) {
            System.out.println(dump.toString());
        }

        Runnable r = new Runnable() {
            @Override
            public void run() {
                //delete old labels
                dataPanel.removeAll();

                //build column names
                dataPanel.add(new JLabel("Name"));
                dataPanel.add(new JLabel("Score"));
                dataPanel.add(new JLabel("Level"));

                //create and add new labels
                for (PlayerDump dump : playersDump) {

                    dataPanel.add(new JLabel(dump.getName()));
                    dataPanel.add(new JLabel("" + dump.getScore()));
                    dataPanel.add(new JLabel("" + dump.getLevel()));
                }
            }
        };

        EventQueue.invokeLater(r);
    }
}

class PlayerDump {
    protected String name;
    protected int score;
    protected int level;

    public PlayerDump(String name, int score, int level) {
        this.name = name;
    }

    //getters
    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }

    public int getLevel() {
        return level;
    }

    //override
    @Override
    public String toString() {
        return "Player: " + name + ", Score: " + score + ", Level: " + level;
    }
}

Solution

  • When I run the debugger I can see that the labels are created and added to the parent view. But I cannot see the labels in my view. Any ideas why that is?

    The size of the label is (0, 0) so there is nothing to paint.

    When you add components to a visible GUI you need to invoke the layout manager so the component can be given a size and location.

    So the basic code is:

    panel.add(...);
    panel.revalidate();
    panel.repaint();
    

    If you add multiple components then you only do the revalidate() and repaint() after all the components have been added.