Search code examples
javaswingjlabelpaintcomponentrepaint

JLabels in GridLayout are Not Always Painted


I am creating a basic Tic-Tac-Toe application in Java Swing, and for this purpose, I have started investigating drawing. However, I have encountered an issue when a subclass of JPanel contains more than one instance of a subclass of JLabel, which overrides the paintComponent(Graphic) method, in a GridLayout array format embedded in itself. The issue is that only the first element in this array is painted.

An example for the customized subclass of JLabel:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JLabel;

@SuppressWarnings("serial")
public class DrawLabel extends JLabel {

    private boolean hasPaint;

    public DrawLabel() {
        super();
        this.hasPaint = false;
    }

    public void draw() {
        hasPaint = true;
        repaint();
    }

    public void clear() {
        hasPaint = false;
        repaint();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(200, 200);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D) g;

        if (hasPaint) {
            g2.drawOval(getX(), getY(), getWidth(), getHeight());
        } else {
            g2.clearRect(getX(), getY(), getWidth(), getHeight());
        }
    }
}

An example for the customized subclass of JPanel:

import java.awt.Dimension;
import java.awt.GridLayout;

import javax.swing.JPanel;

@SuppressWarnings("serial")
public class DrawPanel extends JPanel {

    private Dimension size;
    private DrawLabel[][] fields;

    public DrawPanel(Dimension d) {
        super();
        this.size = d;
        this.fields = new DrawLabel[size.height][size.width];

        this.setLayout(new GridLayout(size.height, size.width));

        for (int i = 0; i < size.height; i++) {
            for (int j = 0; j < size.width; j++) {
                this.fields[i][j] = new DrawLabel();
                this.add(fields[i][j]);
            }
        }
    }

    public void draw(int row, int col) {
        fields[row][col].draw();
    }

    public void clear() {
        for (int i = 0; i < size.height; i++) {
            for (int j = 0; j < size.width; j++) {
                fields[i][j].clear();
            }
        }
    }
}

An example of an issue-provoking scenario:

import java.awt.BorderLayout;
import java.awt.Dimension;

import javax.swing.JFrame;

public class Driver {

    public static void main(String[] args) {
        DrawPanel board = new DrawPanel(new Dimension(2, 1));

        JFrame canvas = new JFrame();
        canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        canvas.add(board, BorderLayout.CENTER);
        canvas.pack();

        canvas.setLocationRelativeTo(null);
        canvas.setVisible(true);

        board.draw(0, 0); // This gets painted.
        board.draw(0, 1); // This does not get painted!
    }

}

Curiously, if the DrawPanel.draw(int, int) is changed to set the text of the element rather than draw upon it, only the second element is updated, while the first is not, which is the exact opposite of the original problem.

I have tried to look up other issues and questions related to painting of subcomponents, but I have yet to find an issue like this one, where it seems that every instance beyond the first in the GridLayout are not drawable in the same way. What could be wrong?

Thank you for your time and effort!


Solution

  • g2.drawOval(getX(), getY(), getWidth(), getHeight());
    

    Painting of a component is done relative to the component, not the panel the component is painted in. The getX/Y() methods return the location of the component relative to the parent component.

    So if the size of each component is (200, 200) then the oval of the first component will be painted using

    g2.drawOval(0, 0, 200, 200);
    

    The second component will be painted at:

    g2.drawOval(200, 0, 200, 200);
    

    but since the component is only 200 pixels wide, the start x location of 200 is outside the bounds of the component so there is nothing to paint.

    Instead just use:

    g2.drawOval(0, 0, getWidth(), getHeight());