简体   繁体   中英

GridLayout not filling JPanel

I'm having a problem with GridLayout and the entire JPanel not being filled. I have a N * M Grid, and I'm filling it with N * M Tiles (They extend JPanel). After adding all these tiles there is still space on the bottom and the right side of the JPanel. I thought GridLayout was supposed to fill up the entire JPanel, and make everything in it evenly sized?

Edit: I didn't want to post all the code since there are multiple classes. Thought maybe there was a simple solution.

public class MainFrame extends JFrame {

    private static final long serialVersionUID = 3985493842977428355L;
    private final int FRAME_HEIGHT = 800;
    private final int FRAME_WIDTH = 700;

    private MainPanel mainPanel;

    public MainFrame() {
        super("Test");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(FRAME_HEIGHT, FRAME_WIDTH);
        setLocationRelativeTo(null);    
        mainPanel = new MainPanel();
        getContentPane().add(mainPanel);
        setVisible(true);
    }
}

public class MainPanel extends JPanel {
    /**
     * 
     */
    private static final long serialVersionUID = 3421071253693531472L;

    private RoutePanel routePanel;
    private ControlPanel controlPanel;
    private GridBagConstraints gridBagConstraints;
    private GridBagLayout gridBagLayout;

    public MainPanel() {
        routePanel = new RoutePanel();
        controlPanel = new ControlPanel();
        setBackground(Color.black);
        gridBagLayout = new GridBagLayout();
        setLayout(gridBagLayout);
        gridBagConstraints = new GridBagConstraints();
        createLayout();
    }

    private void createLayout() {
        gridBagConstraints.weightx = 1;
        gridBagConstraints.weighty = 1;
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = GridBagConstraints.NORTH;
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagLayout.setConstraints(routePanel, gridBagConstraints);
        add(routePanel);

        gridBagConstraints.weightx = 1;
        gridBagConstraints.weighty = .05;
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = GridBagConstraints.SOUTH;
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagLayout.setConstraints(controlPanel, gridBagConstraints);
        add(controlPanel);
    }
}

public class RoutePanel extends JPanel{
    /**
     * 
     */
    private static final long serialVersionUID = 4366703088208967594L;
    private final int GRID_ROWS = 30;
    private final int GRID_COLS = 47;


    public RoutePanel() {
        setLayout(new GridLayout(GRID_ROWS, GRID_COLS, 0, 0));
        for(int i = 0; i < GRID_ROWS * GRID_COLS; i++) {
            add(new Tile());
        }
        setBackground(Color.orange);
    }
}

public ControlPanel() {

    }

public Tile() {
        setBackground(Color.gray);
        Border blackline;
        blackline = BorderFactory.createLineBorder(Color.black);
        setBorder(blackline);
    }

Since you're not posting code, we are forced to guess (and this is not good).

My guess: you may be setting the size or the preferred sizes of some of your components or the GUI, and this is causing gaps at the edges of your GUI.

Solution: don't do this. Let the components size themselves via their preferred sizes and the layout managers when pack() is called on the GUI.

For more helpful help, create and post your sscce . For a question like this, code is really mandatory, and I'm a bit surprised actually that you didn't post yours.


Edit 1

For example, note the difference in the GUI's produced:

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

import javax.swing.*;

public class GapExample {
   private static final int M = 5;
   private static final int N = 6;
   private static final int PREF_W = 700;
   private static final int PREF_H = 500;

   @SuppressWarnings("serial")
   private static void createAndShowGui() {

      // *** attempt 1: set preferredSize of the mainPanel JPanel ***
      JPanel mainPanel = new JPanel(new GridLayout(M, N));
      mainPanel.setBorder(BorderFactory.createLineBorder(Color.red));
      mainPanel.setPreferredSize(new Dimension(PREF_W, PREF_H));

      for (int i = 0; i < M; i++) {
         for (int j = 0; j < N; j++) {
            String text = String.format("Foo [%d, %d]", i, j);
            JLabel label = new JLabel(text, SwingConstants.CENTER);
            label.setBorder(BorderFactory.createLineBorder(Color.blue));
            mainPanel.add(label);
         }
      }
      JOptionPane.showMessageDialog(null, mainPanel,
            "Attempt 1, setPreferredSize of mainPane",
            JOptionPane.PLAIN_MESSAGE);

      // *** attempt 2: override the getPreferredSize of the JLabel cells in the
      // grid ***
      mainPanel = new JPanel(new GridLayout(M, N));
      mainPanel.setBorder(BorderFactory.createLineBorder(Color.red));
      final Dimension labelPrefSize = new Dimension(PREF_W / N, PREF_H / M);
      for (int i = 0; i < M; i++) {
         for (int j = 0; j < N; j++) {
            String text = String.format("Foo [%d, %d]", i, j);
            JLabel label = new JLabel(text, SwingConstants.CENTER) {
               @Override
               public Dimension getPreferredSize() {
                  return labelPrefSize;
               }
            };
            label.setBorder(BorderFactory.createLineBorder(Color.blue));
            mainPanel.add(label);
         }
      }
      JOptionPane
            .showMessageDialog(null, mainPanel,
                  "Attempt 2, override getPreferredSize of the JLabel cells in the grid",
                  JOptionPane.PLAIN_MESSAGE);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

In the second dialog, I have the JLabels return a preferred size and let the containers that hold them set the best size to display the GUI, and you'll with the second attempt, the grid fits much better.

This displays for the first (bad) attempt a gap between the inner and outer components:

在此输入图像描述

And the second (good) attempt shows no such gap:

在此输入图像描述

GridLayout was supposed to fill up the entire JPanel, and make everything in it evenly sized

these two are opposing forces, the manager can do both at the same time only if the panel width is a multiple of the column width:

panel.width == column.width * column.count

If that's not possible, it favors the equal-size constraint, by sizing the children to the largest width to make them equal and positions the whole block in the center of parent, thus exposing the parent's background. Basically, you are seeing the effects of integer math :-)


As to the discussion in the other answer ,

First off: my take on setXXSize is well known - it's always wrong to use in application code I think it's a design accident, should never have happened in the first place because most of the time it introduces more problems than it seems (superficially!) to solve.

Next-nearest solution seems to override getXXSize: if done to return an arbitrary hard-coded fixed size, it's only marginally better than calling setXXSize (in not propagating worst practices :): the return value should be related to internal state. It should keep internal calculation (if any) of super intact and can modify it. Plus strictly speaking, it should comply to super's contract which indeed states (a bit hidden in setXXSize) that the dimension applied via setXXSize takes precedence

Sets the preferred size of this component to a constant value. Subsequent calls to getPreferredSize will always return this value.

In code:

// always wrong in application code
someComponent.setPreferredSize(labelPrefSize);

// suboptimal: arbitrary hard-coded fixed size 
@Override
public Dimension getPreferredSize() {
    return new Dimension(labelPrefSize);
}

// good: fixed value based on internal state
@Override
public Dimension getPreferredSize() {
    return new Dimension(labelPrefSize);
}
// the size has some meaning f.i. when painting
@Override
protected void paintComponent(Graphics g) {
    g.drawRect(0, 0, labelPrefSize.width, labelPrefSize.height);
}

// good: prefsize relative to super's calculation
@Override
public Dimension getPreferredSize() {
    Dimension labelPrefSize = super.getPreferredSize();
    int maxSide = Math.max(labelPrefSize.width, labelPrefSize.height);
    labelPrefSize.setSize(maxSide, maxSide);
    return labelPrefSize;
}

// better: prefsize relative to default calculation
// _and_ comply to super's contract
@Override
public Dimension getPreferredSize() {
    Dimension labelPrefSize = super.getPreferredSize();
    if (isPreferredSizeSet()) return labelPrefSize;
    // any of the good thingies ...
}

Ideally, though, you would not touch the internal calculation of sizing hints at all . Instead, use a LayoutManager that allows to fine tune the layout to exactly suit your needs. Remember: the manager takes the hints and is free to size however she likes anyway :-)

Update

edited the above trying to emphasize the "related to internal state" thingy: sizing hints should return their own isolated, ego-centric requirement without caring about context. So overriding to return something that pleases an outside need is suboptimal.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM