Search code examples
javaswingjtable

How to center table cells and remove a gap near the java swing JTable?


The JTable is expected to be the implementation of the Pac-Man game board, therefore the cells of the table are squared. The scalability of the table should also be considered. The problem is that if in the JPanel I use BorderLayout the table is scalable, but aligned to the left and when the BorderLayout is not passed to the JPanel constructor, the table is centered but not scalable.

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

public class Game extends JFrame {
    public static void main(String[] args) {
        new Game("Table");
    }

    Game(String title) {
        super(title);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new BorderLayout());

        JPanel p = new JPanel(new BorderLayout());
        p.setBackground(Color.BLACK);

        BoardView b = new BoardView(new Board(36, 28));
        p.add(b);
        add(p);


        pack();
        setLocationRelativeTo(null);
        setVisible(true);
    }

    public class BoardView extends JTable {
        private Board board;

        public BoardView(Board board) {
            super(board);
            this.board = board;

            setTableHeader(null);
            setBackground(Color.WHITE);

            addComponentListener(new ComponentAdapter() {
                @Override
                public void componentResized(ComponentEvent e) {
                    int size = Math.min(getHeight() / getRowCount(), getWidth() / getColumnCount());
                    setRowHeight(size);
                    for (int i = 0; i < getColumnCount(); i++) {
                        TableColumn column = getColumnModel().getColumn(i);
                        column.setMaxWidth(size);
                    }
                }
            });

        }
    }

    public class Board extends AbstractTableModel {
        private int rows;
        private int columns;
        private int[][] boardData;

        public Board(int rows, int columns) {
            this.rows = rows;
            this.columns = columns;
            this.boardData = new int[rows][columns];
        }

        @Override
        public int getRowCount() {
            return rows;
        }

        @Override
        public int getColumnCount() {
            return columns;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return boardData[rowIndex][columnIndex];
        }

        @Override
        public void setValueAt(Object value, int rowIndex, int columnIndex) {
            boardData[rowIndex][columnIndex] = (int) value;
            fireTableCellUpdated(rowIndex, columnIndex);
        }
    }
}

Could someone please provide guidance on how to resolve this issue?


Solution

  • Introduction

    I'd still create a drawing JPanel and draw the boxes rather than bastardize a JTable like this.

    It took me a while, but I modified the code you posted enough to create the following GUI:

    Table 1

    When you expand the frame, the GUI looks like this:

    Table 2

    Explanation

    Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section.

    All Swing applications must start with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.

    I modified your code to use a JFrame, JPanel, and JTable, rather than extend anything. This gave me more flexibility, allowed me to separate my concerns, and focus on one part of the GUI at a time.

    The JPanel uses a BorderLayout. By adjusting the margins of an empty border, I was able to center the JTable in the JPanel.

    When constructing the JPanel and the JTable, I had to calculate the size of the JTable and JPanel before I could construct the GUI. This is why the JPanel has a size calculation method and the ComponentAdapter has a separate size calculation.

    I created a separate ComponentAdapter class just so I could get the code out of the JPanel class.

    Code

    Here's the complete runnable code. I made the additional classes inner classes so I could post the code as one block.

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Insets;
    import java.awt.event.ComponentAdapter;
    import java.awt.event.ComponentEvent;
    
    import javax.swing.BorderFactory;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JTable;
    import javax.swing.SwingUtilities;
    import javax.swing.border.Border;
    import javax.swing.table.AbstractTableModel;
    import javax.swing.table.TableColumn;
    
    public class JTableLayout {
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new JTableLayout("Table");
                }
            });
        }
    
        private JTable table;
    
        JTableLayout(String title) {
            Board board = new Board(36, 28);
    
            JFrame frame = new JFrame(title);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            frame.add(new BoardView(board).getPanel(), BorderLayout.CENTER);
    
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        public class BoardView {
    
            private Board board;
    
            private JPanel panel;
    
            public BoardView(Board board) {
                this.board = board;
                this.panel = createJTablePanel();
            }
    
            private JPanel createJTablePanel() {
                JPanel panel = new JPanel(new BorderLayout());
                panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    
                table = new JTable(board);
                table.setTableHeader(null);
                table.setBackground(Color.WHITE);
                table.setPreferredSize(calculateBoardDimension(table));
                panel.add(table);
    
                panel.addComponentListener(new PanelListener(table, board));
                return panel;
            }
    
            private Dimension calculateBoardDimension(JTable table) {
                int totalHeight = table.getRowHeight() * board.getRowCount();
                int totalWidth = table.getRowHeight() * board.getColumnCount();
                for (int i = 0; i < table.getColumnCount(); i++) {
                    TableColumn column = table.getColumnModel().getColumn(i);
                    column.setMaxWidth(table.getRowHeight());
                }
    
                return new Dimension(totalWidth, totalHeight);
            }
    
            public JPanel getPanel() {
                return panel;
            }
    
        }
    
        public class PanelListener extends ComponentAdapter {
    
            private Board board;
    
            private JTable table;
    
            public PanelListener(JTable table, Board board) {
                this.table = table;
                this.board = board;
            }
    
            @Override
            public void componentResized(ComponentEvent event) {
                JPanel panel = (JPanel) event.getSource();
                Dimension d = panel.getSize();
                int height = d.height / board.getRowCount();
                int width = d.width / board.getColumnCount();
    
                boolean widthLarger = true;
                int size = height;
                if (size > width) {
                    size = width;
                    widthLarger = false;
                }
    
                for (int i = 0; i < table.getColumnCount(); i++) {
                    TableColumn column = table.getColumnModel().getColumn(i);
                    column.setMaxWidth(size);
                }
    
                table.setRowHeight(size);
    
                Border border = panel.getBorder();
                Insets insets = (Insets) border.getBorderInsets(panel);
                if (widthLarger) {
                    int boardWidth = board.getColumnCount() * size;
                    int margin = (d.width - boardWidth) / 2;
                    panel.setBorder(BorderFactory.createEmptyBorder(insets.top,
                            margin, insets.bottom, margin));
                } else {
                    int boardHeight = board.getRowCount() * size;
                    int margin = (d.height - boardHeight) / 2;
                    panel.setBorder(BorderFactory.createEmptyBorder(margin,
                            insets.left, margin, insets.right));
                }
            }
    
        }
    
        public class Board extends AbstractTableModel {
    
            private static final long serialVersionUID = 1L;
    
            private int rows;
            private int columns;
            private int[][] boardData;
    
            public Board(int rows, int columns) {
                this.rows = rows;
                this.columns = columns;
                this.boardData = new int[rows][columns];
            }
    
            @Override
            public int getRowCount() {
                return rows;
            }
    
            @Override
            public int getColumnCount() {
                return columns;
            }
    
            @Override
            public Object getValueAt(int rowIndex, int columnIndex) {
                return boardData[rowIndex][columnIndex];
            }
    
            @Override
            public void setValueAt(Object value, int rowIndex, int columnIndex) {
                boardData[rowIndex][columnIndex] = (int) value;
                fireTableCellUpdated(rowIndex, columnIndex);
            }
        }
    }