javaswinggridbaglayout

Change Insets when changing screen size with Grid Bag Constraints


I have a program intended to be a start menu for a game. So far I have 3 JPanels: homePanel, mainPanel and optionsPanel. The 'homePanel' is a panel holding both menu and options panel.

Home menu image

mainPanel has 3 buttons: Start Game, Options, and Quit. optionsPanel has 3 optionsButton 1 , 2 and 3 as well as a backButton.

Option menu image

The first button changes the program window size between 4 different sizes. I use GridBagLayout to place the buttons because it has been the most consistent layout to align itself to the center of the screen.

I am trying to place the buttons in the center of the screen, vertically, and automatically set the button height so it leaves 10% free space on top and bottom. And spaces the buttons by a fraction of the screen, I currently use 1/40 as the spacing. Then the buttons fill the rest of the space.

This works when I set it up, placing the buttons. I use:

gbcOptions.insets = new Insets(spacing, 0, spacing, 0);

to set the spacing, but when I try to change the screen size the spacing doesn't change. I am able to correctly calculate the button height and the new spacing value. But the actual spacing between the buttons does not change. They are the same as the first time the buttons were added to the panel.

Here the program is enlarged and the spacing between the buttons is too small: Large options image


Methods:

Start of class:

public class MainMenuGUI extends JFrame {

private JPanel homePanel;
private JPanel menuPanel;
private JPanel optionsPanel;
private JButton[] optionButtons;
private int[] optionValues;
private int windowHeight;
private int windowWidth;
private int maxContentWidth;
private int maxContentHeight;
private int spacing;
//window size options
private String[] windowSizeOptions = {"Small (1/4)", "Medium (1/2)", "Large (3/4)", "Fullscreen"};
private int currentWindowSizeIndex = 0; // Index of the current window size option
...

Here are my methods:

Here is where I set everything up:

public MainMenuGUI() {
    // Set up the main window properties
    setTitle("Game Main Menu");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    //window size, calculate the maximum content size based on the window size
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

    //set up the window size with the method
    //Calculate values based on window size
    setWindowSize(windowSizeOptions[currentWindowSizeIndex]);

    //Print out to se the screen size and max content sizes
    System.out.println("window Width: " + windowWidth + "\nWindow height: " + windowHeight
                    + "\nMaxContentWidth: " + maxContentWidth + "\nMaxContentHeight: " + maxContentHeight);

    setResizable(false);
    setUndecorated(true);
    setSize(windowWidth, windowHeight);
    setLocationRelativeTo(null); // Center the window on the screen


    //homePanel holds any Panel outside the gamePanel
    homePanel = new JPanel();
    homePanel.setLayout(new BorderLayout());
    homePanel.setBackground(new Color(20, 20, 20));
    add(homePanel, BorderLayout.CENTER);


    // Create the main menu components
    menuPanel = new JPanel();
    menuPanel.setLayout(new GridBagLayout());


    // Buttons
    JButton startButton = createStyledButton("Start Game", buttonWidth);
    JButton optionsButton = createStyledButton("Options", buttonWidth);
    JButton quitButton = createStyledButton("Quit", buttonWidth);


    // Add buttons to the panel
    GridBagConstraints gbc = new GridBagConstraints();
    gbc.fill = GridBagConstraints.HORIZONTAL;
    gbc.insets = new Insets(spacing, 0, spacing, 0);

    gbc.gridy = 0;
    menuPanel.add(startButton, gbc);

    gbc.gridy = 1;
    menuPanel.add(optionsButton, gbc);

    gbc.gridy = 2;
    menuPanel.add(quitButton, gbc);


    //Option Panel

    // Initialize option buttons and values
    optionButtons = new JButton[3];
    optionValues = new int[]{1, 1, 1};

    // Create the options menu components
    optionsPanel = new JPanel();
    optionsPanel.setLayout(new GridBagLayout());


    //Options Buttons
    JButton optionButton1 = createStyledButton("Screen Size: " + windowSizeOptions[currentWindowSizeIndex], buttonWidth);
    JButton optionButton2 = createStyledButton("Option 2: " + optionValues[1], buttonWidth);
    JButton optionButton3 = createStyledButton("Option 3: " + optionValues[2], buttonWidth);
    JButton optionBackButton = createStyledButton("Back", buttonWidth);


    //adding buttons to panel
    GridBagConstraints gbcOptions = new GridBagConstraints();
    gbcOptions.fill = GridBagConstraints.HORIZONTAL;


    gbcOptions.insets = new Insets(spacing, 0, spacing, 0);

    gbcOptions.gridy = 0;
    optionsPanel.add(optionButton1, gbcOptions);

    gbcOptions.gridy = 1;
    optionsPanel.add(optionButton2, gbcOptions);

    gbcOptions.gridy = 2;
    optionsPanel.add(optionButton3, gbcOptions);

    gbcOptions.gridy = 3;
    optionsPanel.add(optionBackButton, gbcOptions);



    // Set the desired starting index for the window size options
    currentWindowSizeIndex = 1;

    // Update the text of the optionsButton
    optionButton1.setText("Screen Size: " + windowSizeOptions[currentWindowSizeIndex]);

    // Update the main window size based on the selected option
    setWindowSize(windowSizeOptions[currentWindowSizeIndex]);


    //adjust button height
    setButtonSize(menuPanel);
    setButtonSize(optionsPanel);

    //set background
    menuPanel.setBackground(new Color(20,20,20));
    optionsPanel.setBackground(new Color(20,20,20));

    //Action Listeners

    /.../

    // Show the main menu panel when the program starts
    showMainMenu();


}

This method sets the window to size and calculates a few variables:

private void setWindowSize(String windowSizeOption) {
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    int newWindowWidth;
    int newWindowHeight;

    if (windowSizeOption.equals("Small (1/4)")) {
        newWindowWidth = (int) (screenSize.width * 0.25);
        newWindowHeight = (int) (screenSize.height * 0.25);

    } else if (windowSizeOption.equals("Medium (1/2)")) {
        newWindowWidth = (int) (screenSize.width * 0.5);
        newWindowHeight = (int) (screenSize.height * 0.5);
    } else if (windowSizeOption.equals("Large (3/4)")) {
        newWindowWidth = (int) (screenSize.width * 0.75);
        newWindowHeight = (int) (screenSize.height * 0.75);
    } else { // Fullscreen
        newWindowWidth = screenSize.width;
        newWindowHeight = screenSize.height;
    }


    //window size and content size
    windowWidth = newWindowWidth;
    windowHeight = newWindowHeight;
    maxContentWidth = Math.min(newWindowWidth, newWindowHeight * 2);
    maxContentHeight = Math.min(newWindowHeight, newWindowWidth * 2);

    //Button sizes
    buttonWidth = maxContentWidth / 3;
    spacing = maxContentHeight /40;


    // Set the new window size
    setSize(newWindowWidth, newWindowHeight);

    // Center the window on the screen
    setLocationRelativeTo(null);
}

Here is my method that changing the button sizes:

private void setButtonSize(JPanel panel) {
    int numButtons = countButtons(panel);
    int availableHeight = (int) (maxContentHeight * 0.8);
    int totalSpacing = (numButtons - 1) * spacing;
    int buttonHeight = (availableHeight - totalSpacing) / numButtons;

    for (Component component : panel.getComponents()) {
        if (component instanceof JButton) {
            JButton button = (JButton) component;
            button.setPreferredSize(new Dimension(buttonWidth, buttonHeight));
        }
    }

}

Here is a method to return the number of buttons in the panel:

private int countButtons(JPanel panel) {
    int numButtons = 0;
    for (Component component : panel.getComponents()) {
        if (component instanceof JButton) {
            numButtons++;
        }
    }
    return numButtons;
}

Here is the method that stylized the buttons:

private JButton createStyledButton(String text, int width) {
    JButton button = new JButton(text);
    button.setBackground(new Color(5,5,5));
    button.setForeground(Color.WHITE);
    button.setPreferredSize(new Dimension(width, 50));
    button.setBorder(BorderFactory.createLineBorder(Color.WHITE, 2));
    button.setAlignmentX(Component.CENTER_ALIGNMENT);
    button.setFocusable(false);
    return button;
}

Here is the ActionListener for the button that changes screen size (in the same method where the buttons get added):

optionButton1.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            // Cycle through the window size options
            currentWindowSizeIndex = (currentWindowSizeIndex + 1) % windowSizeOptions.length;

            // Update the text of the optionsButton
            optionButton1.setText("Screen Size: " + windowSizeOptions[currentWindowSizeIndex]);

            // Update the main window size based on the selected option
            setWindowSize(windowSizeOptions[currentWindowSizeIndex]);

            //adjust button height
            setButtonSize(menuPanel);
            setButtonSize(optionsPanel);

            revalidate();
            repaint();

        }
    });

And my methods to show the different panels:

private void showOptionsMenu() {
    // Hide the main menu panel
    homePanel.remove(menuPanel);

    // Show the options panel
    homePanel.add(optionsPanel, BorderLayout.CENTER);

    // Refresh the options panel
    homePanel.revalidate();
    homePanel.repaint();
}



private void showMainMenu() {
    // Remove the options menu panel from the main window
    homePanel.remove(optionsPanel);

    // Add the main menu panel back to the main window
    homePanel.add(menuPanel, BorderLayout.CENTER);

    // Refresh the main window to display the main menu
    homePanel.revalidate();
    homePanel.repaint();
}

public static void main(String[] args) {
    SwingUtilities.invokeLater(() -> {
        MainMenuGUI mainMenu = new MainMenuGUI();
        mainMenu.setVisible(true);
    });
}

What I have tried:

I have tried to change the Insets in the setButtonSize method:

GridBagConstraints gbcOptions = ((GridBagLayout) panel.getLayout()).getConstraints(panel);
gbcOptions.insets = new Insets(spacing, 0, spacing, 0);

Doing this once for the panel and also tried doing it for every button in the for loop:

for (Component component : panel.getComponents()) {
        if (component instanceof JButton) {
            JButton button = (JButton) component;
            button.setPreferredSize(new Dimension(buttonWidth, buttonHeight));

            GridBagConstraints gbcOptions = ((GridBagLayout) panel.getLayout()).getConstraints(button);
            gbcOptions.insets = new Insets(spacing, 0, spacing, 0);
        }
    }

I tried to change it at the end of the ActionListener for the Screen Size button which is within the same method as the buttons are added.

Any way I try it doesn't change from the initial value. If I want a bigger spacing I can set it and the button height is decreased but because I start at 1/2 screen size, when switching to 1/4 the buttons get much smaller:

Small buttons image

I always put:

revalidate();
repaint();

after, and I also try to put the panel names in front but no difference.

I have also tried to make the Insets accessible in the class:

private Insets inset = new Insets(0, 0, spacing, 0);

And use this to change the spacings:

gbcOptions.insets = inset;

But I can't get it to actually change the spacing, the variable spacing does change but not the actual space between the buttons.


So I'd like to be able to change the spacing somehow, I'd like to use the GridBagLayout as it has been the easiest to get the buttons to align to the center. But if there is an easier / better way to go about this I could use that instead, I'm not completely set on using this layout, I just need it to function as I want it without too much trouble.

And as mentioned above:

I am trying to place the buttons in the center of the screen, vertically, and automatically set the button height so it leaves 10% free space on top and bottom. And spaces the buttons by a fraction of the screen, I currently use 1/40 as the spacing. Then the buttons fill the rest of the space.


Solution

  • leaves 10% free space on top and bottom.

    spaces the buttons by a fraction of the screen, I currently use 1/40 as the spacing

    Which would be 2.5% between each button, or 7.5% for the space between the 4 buttons.

    Then the buttons fill the rest of the space.

    So this leaves 72.5% of space for the 4 buttons or 18.125% for each button.

    What you have is a custom layout where each component takes a fixed percentage of the space available. The logic should be part of a layout manager, not handled by application logic.

    Check out the Relative Layout which allows components to grow/shrink based on the relative size of each component and the space available to all components.

    You would use the RelativeLayout with code something like:

    RelativeLayout rl = new RelativeLayout(RelativeLayout.Y_AXIS);
    JPanel panel = new JPanel(rl);
    panel.add(Box.createVerticalStrut(1), new Float(10));
    panel.add(button1, new Float(18.125));
    panel.add(Box.createVerticalStrut(1), new Float(2.5));
    panel.add(button2, new Float(18.125));
    panel.add(Box.createVerticalStrut(1), new Float(2.5));
    panel.add(button3, new Float(18.125));
    panel.add(Box.createVerticalStrut(1), new Float(2.5));
    panel.add(button4, new Float(18.125));
    panel.add(Box.createVerticalStrut(1), new Float(10));