Home > Mobile >  JPanel and JLabels stretching to fit size of parent, regardless of maximum size - java wordle clone
JPanel and JLabels stretching to fit size of parent, regardless of maximum size - java wordle clone

Time:05-29

In the class that extends JFrame, I have the following code:

public Frame() {
        setTitle("WORDLE");

        // creates all containers
        Container mainContainer = getContentPane();
        JPanel gridContainer = new JPanel();
        JPanel optionsContainer = new JPanel();

        // sets size of main frame
        setSize(WIDTH, 900);

        // sets background size and background colors of all containers
        gridContainer.setBackground(GameTheme.BACKGROUND); // TODO: add message label inside of gridContainer below grid JPanel
        gridContainer.setPreferredSize(new Dimension(WIDTH - 150, 500));
        gridContainer.setMaximumSize(new Dimension(WIDTH - 150, 500));
        gridContainer.setLayout(new BorderLayout(10, 0));

        optionsContainer.setBackground(GameTheme.BACKGROUND);
        optionsContainer.setPreferredSize(new Dimension(WIDTH, 100));

        // creates grid with boxes for letter
        grid = new JPanel();
        grid.setLayout(new GridLayout(6, 5, 5, 5));

        grid.setPreferredSize(new Dimension(WIDTH - 150, 400));
        grid.setMaximumSize(new Dimension(WIDTH - 150, 400));

        grid.setBackground(GameTheme.BACKGROUND);
        createBoxes();

        // JLabel for messages
        JLabel alertLabel = new JLabel("test");
        alertLabel.setOpaque(true);
        alertLabel.setForeground(GameTheme.WHITE);
        alertLabel.setBackground(GameTheme.BACKGROUND);

        // listens for presses in the JFrame, with the Game class implementing the method for the keypress event
        addKeyListener(new Game(boxes));

        // lays out all containers and their sub-containers
        // title label at top, grid container at center, options container at bottom
        mainContainer.setLayout(new BorderLayout());
        mainContainer.add(createTitle(), BorderLayout.NORTH); // createTitle returns a JLabel
        mainContainer.add(gridContainer, BorderLayout.CENTER);
        mainContainer.add(optionsContainer, BorderLayout.SOUTH);
        gridContainer.add(grid, BorderLayout.NORTH);
        gridContainer.add(alertLabel, BorderLayout.SOUTH);

        // boilerplate frame configuration
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
        setLocationRelativeTo(null);
        pack();
        setVisible(true);
}

public void createBoxes() {
        // initializes 2d array for letter boxes
        boxes = new LetterBox[6][5];

        // populates 2d array and adds boxes to the grid container
        for (int r = 0; r < 6; r  ) {
            for (int c = 0; c < 5; c  ) {
                LetterBox letterBox = new LetterBox(r, c);
                boxes[r][c] = letterBox;
                grid.add(letterBox.getBox());
            }
        }
}

I have set a maximum size on all of the components within gridContainer, and even on the gridContainer itself, yet the JLabels are still stretching to take up the size of the GUI. Can someone help me figure out why this is happening, and how I can fix it?

To make the problem clear, here are some images that show what I want vs what I am getting:

Expected: expected result

Actual: actual result

Since the code I added is probably not enough, I add the full source code to GitHub: https://github.com/jgorelik23/JavaWordle

I tried to describe as clearly as possible please comment and let me know if there is anything I need to clarify.

CodePudding user response:

Don't keep playing with the preferred/minimum/maximum sizes of parent panels and the frame. Each panel should be able to calculate its preferred size based on the preferred size of the components added to the panel. So you add components to a panel and the panels to the frame, then you pack the frame and everything will be displayed at the proper size.

So in your case you can use a GridLayout for your "gridContainer". Then you add your JLabels to the gridContainer. For your LetterBox component you should use a "monospaced" Font so all characters will take the same space. Then you can initialize your label with a default value of " " to give the label a size.

This size will be too small so you will then also want to add an EmptyBorder to the LetterBox. So in the constructor you can add something like:

setBorder( new EmptyBorder(10, 10, 10, 10) );

Now the LeterBox can calculates its preferred size correctly and the "gridContainer" will be able to calculate its preferred size correctly.

However, when you add the panel the the CENTER of the BorderLayout, the BorderLayout will attempt to resize each component to fill the space available in the frame. To prevent this you can use a "wrapper" panel:

JPanel wrapper = new JPanel(); // uses FlowLayout by default
wrapper.add( gridContainer );
mainContainer.add(wrapper, BorderLayout.CENTER);
//mainContainer.add(gridContainer, BorderLayout.CENTER);

Now if the frame is resized the extra space will go to the wrapper panel NOT the gridContainer.

CodePudding user response:

In case you would like to relax a little the requirement of the frame being unresizable, here follow some implementations which restrict the maximum size of the grid to the preferred, but do not restrict the minimum. As a result in both implementations the grid will not get bigger than its preferred size, making it possible for the user to enlarge the frame and empty spaces to appear by the LayoutManagers used to fill the remaining space.

The minimum size of the grid is not restricted however, just in case the user wants to resize the frame to a smaller size. This is supposed to be an extra feature, although if you want to disable it the easiest way would be to uncomment the frame.setMinimumSize(frame.getSize()); line in each implementation.

Both implementations take into account the sizes of the grid.

BoxLayout implementation:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class BoxMain {
    
    private static void createAndShowGUI() {
        final int rows = 6, cols = 15;
        final JPanel grid = new JPanel(new GridLayout(rows, 0, 5, 5));
        for (int row = 0; row < rows;   row)
            for (int col = 0; col < cols;   col)
                grid.add(new JLabel(Integer.toString(row * cols   col), JLabel.CENTER));
        final Dimension sz = grid.getPreferredSize();
        grid.setMaximumSize(sz);
        grid.setMinimumSize(new Dimension(0, sz.height));
        
        final JPanel box = new JPanel();
        box.setLayout(new BoxLayout(box, BoxLayout.X_AXIS));
        box.add(Box.createGlue());
        box.add(grid);
        box.add(Box.createGlue());
        
        final JPanel mainPanel = new JPanel(new BorderLayout());
        mainPanel.add(box, BorderLayout.CENTER);
        mainPanel.add(new JLabel("Alert label", JLabel.CENTER), BorderLayout.PAGE_END);
        
        final JFrame frame = new JFrame("Wordle");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        //frame.setMinimumSize(frame.getSize());
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(BoxMain::createAndShowGUI);
    }
}

SpringLayout implementation:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Spring;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;

public class SpringMain {
    
    private static void createAndShowGUI() {
        final int rows = 6, cols = 15;
        final JPanel grid = new JPanel(new GridLayout(rows, 0, 5, 5));
        for (int row = 0; row < rows;   row)
            for (int col = 0; col < cols;   col)
                grid.add(new JLabel(Integer.toString(row * cols   col), JLabel.CENTER));
        grid.setMaximumSize(grid.getPreferredSize());
        grid.setMinimumSize(new Dimension());
        
        final SpringLayout layout = new SpringLayout();
        final JPanel spring = new JPanel(layout);
        spring.add(grid);
        final Spring flexible = Spring.constant(0, 0, Short.MAX_VALUE);
        layout.putConstraint(SpringLayout.WEST, grid, flexible, SpringLayout.WEST, spring);
        layout.putConstraint(SpringLayout.EAST, spring, flexible, SpringLayout.EAST, grid);
        layout.putConstraint(SpringLayout.NORTH, grid, flexible, SpringLayout.NORTH, spring);
        layout.putConstraint(SpringLayout.SOUTH, spring, flexible, SpringLayout.SOUTH, grid);
        
        final JPanel mainPanel = new JPanel(new BorderLayout());
        mainPanel.add(spring, BorderLayout.CENTER);
        mainPanel.add(new JLabel("Alert label", JLabel.CENTER), BorderLayout.PAGE_END);
        
        final JFrame frame = new JFrame("Wordle");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        //frame.setMinimumSize(frame.getSize());
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(SpringMain::createAndShowGUI);
    }
}
  • Related