Search code examples
javaswingjpaneltic-tac-toe

Trouble with graphics in a Java JPanel, but it works in a frame


I'm new to this and am using the book Big Java by Cay Hortsmann to learn. For one of the early projects, I'm making a tic-tac-toe game with a GUI, but I'm having trouble getting the board to show up on a JPanel. It shows up fine just in the frame, but since I want to add buttons, I try to put the board in a JPanel and then add the JPanel to the frame. Unfortunately, this isn't working. I'd appreciate any feedback you can give:

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



public class TicTacToePlayer {

    private static final int FRAME_WIDTH = 400;
    private static final int FRAME_HEIGHT = 400;

    private int count = 0;

    public static void main(String[] args) {

        final JFrame frame = new JFrame();

        // create board
        final LinesComponent boardLines = new LinesComponent();     
        JPanel panel = new JPanel();
        panel.add(boardLines);
        frame.add(panel);
        frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);

    }

}
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JComponent;


public class LinesComponent extends JComponent {
    public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;

        Lines boardLines = new Lines();
        boardLines.draw(g2);
    }

}
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;

import javax.swing.JComponent;


public class Lines extends JComponent {
    public Lines() {
        setPreferredSize(new Dimension(400, 400));

    }

    public void draw(Graphics2D g2) {
        Line2D.Double leftVertLine = new Line2D.Double(150, 50, 150, 350);
        Line2D.Double rightVertLine = new Line2D.Double(250, 50, 250, 350);
        Line2D.Double topHorizLine = new Line2D.Double(50, 150, 350, 150);
        Line2D.Double bottomHorizLine = new Line2D.Double(50, 250, 350, 250);
        g2.draw(leftVertLine);
        g2.draw(rightVertLine);
        g2.draw(topHorizLine);
        g2.draw(bottomHorizLine);
    }
}

Solution

  • Lets start with the obvious...

    • JPanel uses a FlowLayout which honours the preferredSize of the components that are added to it. JComponent has a default size of 0x0, meaning that when you add boardLines to it, it is effectivly made invisible and never painted. You can create this by using a BorderLayout on panel, for example JPanel panel = new JPanel(new BorderLayout());
    • While technically not required, it's always good pratice to call super.paintComponent when performing custom painting
    • You should not be creating short lived objects within paintComponent, especially when there state doesn't change in any way (that is, the result of creating a new instance is the same as it was for the previous instance you created). paintComponent can be called repeatedly and in short order, this could an unnecessary strain on your system
    • Lines doesn't need to extend from JComponent, seriously, it's just not doing anything...
    • Use pack instead of setSize on JFrame, it will make life a lot easier...
    • Swing UI's should be created and manipulated on from within the context of the Event Dispatching Thread

    As an example:

    import java.awt.BorderLayout;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.geom.Line2D;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class TicTacToePlayer {
    
        private static final int FRAME_WIDTH = 400;
        private static final int FRAME_HEIGHT = 400;
    
        private int count = 0;
    
        public static void main(String[] args) {
            new TicTacToePlayer();
        }
    
        public TicTacToePlayer() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    }
    
                    final JFrame frame = new JFrame();
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
                    // create board
                    final LinesComponent boardLines = new LinesComponent();
                    JPanel panel = new JPanel(new BorderLayout());
                    panel.add(boardLines);
                    frame.add(panel);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public static class LinesComponent extends JComponent {
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2 = (Graphics2D) g;
                draw(g2);
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(400, 400);
            }
    
            public void draw(Graphics2D g2) {
                Line2D.Double leftVertLine = new Line2D.Double(150, 50, 150, 350);
                Line2D.Double rightVertLine = new Line2D.Double(250, 50, 250, 350);
                Line2D.Double topHorizLine = new Line2D.Double(50, 150, 350, 150);
                Line2D.Double bottomHorizLine = new Line2D.Double(50, 250, 350, 250);
                g2.draw(leftVertLine);
                g2.draw(rightVertLine);
                g2.draw(topHorizLine);
                g2.draw(bottomHorizLine);
            }
        }
    }
    

    You should considering having a look at Performing Custom Painting for more details