1

I am writing a GUI with Swing. I'm using a GridBagLayout to display multiple JLabels in a grid (basically like a chess board). As soon as I use a self made label class derived from JLabel instead of JLabel, the GridBagLayout stacks every label on the top left corner of the JPanel.

Either my subclass TileLabel is incorrect or I don't use the layout and constraints the right way. I think the last one because I can't see what would be a problem in such a minimal subclass.

This is how it looks using JLabel (L represents a label):

(MenuBar)
L L L L L L L L L
L L L L L L L L L
L L L L L L L L L

This is how it looks using TileLabel (S represents all the labels stacked):

(MenuBar)
S 

This is my simple subclass from JLabel:

import javax.swing.JLabel;

public class TileLabel extends JLabel {
    private static final long serialVersionUID = 6718776819945522562L;
    private int x;
    private int y;

    public TileLabel(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }
}

And this is the GUI class. I Marked the three lines where I used my custom label which lead to the layout problem.

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class MainGUI extends JPanel {
    private static final long serialVersionUID = -8750891542665009043L;
    private JFrame frame;
    private MainMenuBar menuBar;
    private TileLabel[][] labelGrid; // <-- LINE 1
    private GridBagConstraints constraints;
    private int gridWidth;
    private int gridHeight;

     // Basic constructor.
    public MainGUI(int frameWidth, int frameHeight) {
        super(new GridBagLayout());
        constraints = new GridBagConstraints();
        buildFrame(frameWidth, frameHeight);
        buildLabelGrid(frameWidth, frameHeight);
    }

    // Builds the frame.
    private void buildFrame(int frameWidth, int frameHeight) {
        menuBar = new MainMenuBar();
        frame = new JFrame("Carcasonne");
        frame.getContentPane().add(this);
        frame.setJMenuBar(menuBar);
        frame.setResizable(false);
        frame.setVisible(true);
        frame.setSize(frameWidth, frameHeight);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBackground(new Color(165, 200, 245));
    }

    // Creates the grid of labels.
    private void buildLabelGrid(int frameWidth, int frameHeight) {
        gridWidth = frameWidth / 100;
        gridHeight = frameHeight / 100;
        labelGrid = new TileLabel[gridWidth][gridHeight]; // <-- LINE 2
        for (int x = 0; x < gridWidth; x++) {
            for (int y = 0; y < gridHeight; y++) {
                labelGrid[x][y] = new TileLabel(x, y); // <-- LINE 3
                constraints.gridx = x;
                constraints.gridy = y;
                add(labelGrid[x][y], constraints); // add label with constraints
            }
        }
    }

    // sets the icon of a specific label
    public void paint(Tile tile, int x, int y) {
        if (x >= 0 && x < gridWidth && y >= 0 && y < gridHeight) {
            labelGrid[x][y].setIcon(tile.getImage());
        } else {
            throw new IllegalArgumentException("Invalid label grid position (" + x + ", " + y + ")");
        }
    }

    // Just to test this GUI:
    public static void main(String[] args) {
        MainGUI gui = new MainGUI(1280, 768);
        Tile tile = TileFactory.createTile(TileType.Road);
        for (int x = 0; x < 12; x++) {
            for (int y = 0; y < 7; y++) {
                gui.paint(tile, x, x);
            }
        }
    }
}

Where is the problem?

  • 1
    *"basically like a chess board"* See also [Making a robust, resizable Swing Chess GUI](http://stackoverflow.com/q/21142686/418556) for tips on layout and components to use (e.g. a `JButton` instead of a `JLabel` for each grid square). – Andrew Thompson Jul 25 '16 at 11:34

2 Answers2

3

There are quite a few things to fix in your code, but your problem originates from 3 things:

  1. Your method definitions in your custom label:

    public class TileLabel extends JLabel {
    
        // @Override !!!!
        public int getX() {
            return x;
        }
    
        // @Override !!!!
        public int getY() {
            return y;
        }
    }
    

    You are overriding JComponent's getX() and getY(), which are responsible for returning their coordinates. This messes up the layout completely.

    Be careful with your paint method, a method with the same name exists in a superclass, though you are saved in this case since the arguments are different.

  2. You have a typo at your loop: gui.paint(tile, x, x) should be gui.paint(tile, x, y).

  3. The order in which you call your methods is wrong. First, you create the frame and display it, then you change its contents by adding the panel with the labels to it, then you change the text in the labels. You should do this the other way around.

My recommendations:

  • Make your paint method be made a member of your TileLabel class. It makes more sense.
  • Set the icons during creation of the labels, unless they are not known. If you can't, you might need to recalculate the space requirements.
  • Never make your layout dependent on the size of the screen or its resolution. It makes for a fragile GUI (as noted in the comments). Use pack() for the frame to calculate the correct size.
user1803551
  • 12,965
  • 5
  • 47
  • 74
  • That was the problem. Thanks fo the help and the additional tips. – ConveniencePatterns Jul 25 '16 at 12:59
  • @ConveniencePatterns Well, you had more than 1 :) By the way, your GUI doesn't work for me because the size is too large and it overflows my screen. See my recommendations and if you need help I can post some code. – user1803551 Jul 25 '16 at 13:03
  • Yeah that's true. Problem 2 occurred when I shortened my code for stack overflow. Problem 3 is definitely a design mistake I will fix ASAP. Do you mean it overflows because its fixed size? Making the GUI size dynamic is on my TODO list. – ConveniencePatterns Jul 25 '16 at 13:12
  • @ConveniencePatterns Yes, it's too large and can't be resized. Allow it to be resized and use `pack()` instead of `setSize(...)`. That's all you need to do. Make sure the order is `pack`, `seLocationRelativeTo` and then `setVisible`. – user1803551 Jul 25 '16 at 13:14
  • When using `pack()` to resize the GUI, I need the maximal size the frame could possibly have. The grid size should be maximal in height and width for every resolution and tiles of size 100x100 (See method `buildLabelGrid()`). I don't need the resolution, I need the frame size. What's the most efficient way to solve this? – ConveniencePatterns Jul 26 '16 at 12:18
  • @ConveniencePatterns Not sure I understand. You want your tiles to have a constant size of 100x100, but the number of tiles should change according to the size of the frame, which should be maximal? And you don't want to allow resizing? In any case, it should be a separate question. – user1803551 Jul 27 '16 at 02:08
1

Override

You have accidentally overridden JComponent#getX() and JComponent#getY(). The values returned by this method are not consistent with the values that the layout may set internally (via calls to setBounds or so). This messes up the layout.

(Admittedly, I did not really check whether this is the reason, but it likely is, and it is a problem in general!)

Marco13
  • 53,703
  • 9
  • 80
  • 159