javaswingsizelayout-managerboxlayout

Why does my box Layout change size even after setting Sizes of JPanels


I have been trying to adjust my JPanel sizes while using LayoutManager such as BoxLayout to arrange components inside that Layout in a specific order, I achieved the way I want it to look by NOT USING the Box Layout Manager.

Original idea which I want in this image:

enter image description here

But I want to achieve that while using Box Layout Manager for the Panels and when I do that, I get this output :

The output I get when I add BoxLayout:

enter image description here

This is the code :

AppPanel.java :

import javax.swing.*;
import java.awt.*;

public class AppPanel extends JPanel {

int Width, Height;
MenuButton mb;

AppPanel(int width, int height)
{
    this.Width = width;
    this.Height = height;
    this.setMinimumSize(new Dimension(this.Width/ 4, this.Height));
    this.setPreferredSize(new Dimension(3 * this.Width/ 4, this.Height));
    this.setMaximumSize(new Dimension(this.Width/ 4, this.Height));
    this.setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
    this.setBackground(Color.GREEN);

    this.mb = new MenuButton("HELLO",this.getPreferredSize());
    this.add(mb);
}

public void update(int width, int height){
    this.Width = width;
    this.Height = height;
    this.setPreferredSize(new Dimension(3 * this.Width/ 4, this.Height));
    //        this.mb.update(this.mb.getGraphics());
    }
}

MenuPanel.java:

import javax.swing.*;
import java.awt.*;

public class MenuPanel extends JPanel {

JLabel l_AppMenu;
int Width, Height;
MenuButton mb;

MenuPanel(int width, int height)
{
    this.Width = width;
    this.Height = height;
    this.setMinimumSize(new Dimension(this.Width/ 4, this.Height));
    this.setPreferredSize(new Dimension(this.Width/ 4, this.Height));
    this.setMaximumSize(new Dimension(this.Width/ 4, this.Height));
    this.setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
    this.setBackground(Color.BLACK);

    this.mb = new MenuButton("HELLO 1",this.getPreferredSize());
    l_AppMenu = new JLabel("App Menu");

    l_AppMenu.setForeground(Color.GRAY);
    this.add(l_AppMenu);
    this.add(mb);
}

public void update(int width, int height){
    this.Width = width;
    this.Height = height;
    this.setPreferredSize(new Dimension(3 * this.Width/ 4, this.Height));
    //        this.mb.update(this.mb.getGraphics());
    }
}

MainFrame.java:

import javax.swing.*;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;

public class MainFrame extends JFrame implements ComponentListener {

int Width, Height;
MenuPanel mp;
AppPanel ap;

public MainFrame()
{
    this.Width = 1600;
    this.Height = 900;

    mp = new MenuPanel(this.Width, this.Height);
    ap = new AppPanel(this.Width, this.Height);

    this.setSize(this.Width, this.Height);
    this.setVisible(true);
    this.setTitle("ToolKit");
    this.setDefaultCloseOperation(this.EXIT_ON_CLOSE );

    this.add(mp);
    this.add(ap);
    addComponentListener(this);

    this.getContentPane().setLayout(new     BoxLayout(this.getContentPane(),BoxLayout.X_AXIS));
}

public static void main(String[] args)
{
    MainFrame m = new MainFrame();
}

@Override
public void componentResized(ComponentEvent componentEvent) {
    System.out.println(this.getWidth() + " " + this.getHeight());
    mp.update(this.getWidth(), this.getHeight());
    ap.update(this.getWidth(), this.getHeight());
//        this.setSize(this.getHeight() * 16 / 9 , this.getHeight());
}

@Override
public void componentMoved(ComponentEvent componentEvent) {

}

@Override
public void componentShown(ComponentEvent componentEvent) {

}

@Override
public void componentHidden(ComponentEvent componentEvent) {

}
}

MenuButton.java:

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class MenuButton extends JButton implements MouseListener {

String Text;
int Entered = 0;
int Pressed = 0;

MenuButton(String str, Dimension d)
{
    super(str);
    this.Text = str;
    addMouseListener(this);
    this.setMinimumSize(new Dimension((int) d.getWidth() - 20,40));
    this.setMaximumSize(new Dimension((int) d.getWidth() - 20,40));
    this.setPreferredSize(new Dimension((int) d.getWidth() - 20,40));
    this.setBorder(null);
}

public void paintComponent(Graphics g)
{
    if(this.Pressed == 0) {
        g.setColor(new Color(182, 25, 25));
        g.fillRect(0, 0, this.getWidth(), this.getHeight());

        g.setColor(new Color(255, 107, 107));
        g.fillRect(10, 10, this.getWidth() - 20, this.getHeight() - 20);
    }
    else{
        g.setColor(new Color(255, 107, 107));
        g.fillRect(0, 0, this.getWidth(), this.getHeight());

        g.setColor(new Color(255, 107, 107));
        g.fillRect(10, 10, this.getWidth() - 20, this.getHeight() - 20);
    }

    if(this.Entered == 0) {
        g.setColor(new Color(253, 210, 199));
        g.drawString(this.Text, this.getWidth() / 2 - g.getFontMetrics().stringWidth(this.Text) / 2, 25);
    }
    else{
        g.setColor(new Color(1, 36, 67));
        g.drawString(this.Text, this.getWidth() / 2 - g.getFontMetrics().stringWidth(this.Text) / 2, 25);
    }
}

@Override
public void mouseClicked(MouseEvent mouseEvent) {

}

@Override
public void mousePressed(MouseEvent mouseEvent) {
    this.Pressed = 1;
}

@Override
public void mouseReleased(MouseEvent mouseEvent) {
    this.Pressed = 0;
}

@Override
public void mouseEntered(MouseEvent mouseEvent) {
    this.Entered = 1;
}

@Override
public void mouseExited(MouseEvent mouseEvent) {
    this.Entered = 0;
}
}

Solution

  • By reading the source code of MenuButton I realized that it paints the border and the background depending on mouse events. You don't have to do this yourself since JButtons support both Borders and background colors. So you can instead create a MouseListener which updates those properties and let it repaint itself with those (I will demonstrate on the code sample that follows).

    Anyway, back to your main problem, here is how you could do it, with using BoxLayouts exclusively:

    import java.awt.Color;
    import java.awt.Component;
    import java.awt.Dimension;
    import java.awt.event.ActionListener;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.util.Objects;
    import javax.swing.BorderFactory;
    import javax.swing.Box;
    import javax.swing.BoxLayout;
    import javax.swing.JButton;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    import javax.swing.border.Border;
    
    public class BoxLayoutTest {
        
        public static final class MainButtonAdapter extends MouseAdapter {
            
            private final Color fgOnEntered, fgOnNotEntered, bgOnPressed, bgOnNotPressed;
            private final Border onPressed, onNotPressed;
    
            public MainButtonAdapter(final Color fgOnEntered,
                                     final Color fgOnNotEntered,
                                     final Color bgOnPressed,
                                     final Color bgOnNotPressed,
                                     final Border onPressed,
                                     final Border onNotPressed) {
                this.fgOnEntered = Objects.requireNonNull(fgOnEntered);
                this.fgOnNotEntered = Objects.requireNonNull(fgOnNotEntered);
                this.bgOnPressed = Objects.requireNonNull(bgOnPressed);
                this.bgOnNotPressed = Objects.requireNonNull(bgOnNotPressed);
                this.onPressed = Objects.requireNonNull(onPressed);
                this.onNotPressed = Objects.requireNonNull(onNotPressed);
            }
            
            private void inside(final Component src) {
                if (src != null) {
                    src.setForeground(fgOnEntered);
                    src.repaint();
                }
            }
    
            private void released(final Component src) {
                if (src != null) {
                    if (src instanceof JComponent)
                        ((JComponent) src).setBorder(onNotPressed);
                    src.setBackground(bgOnNotPressed);
                    src.repaint();
                }
            }
    
            //@Override
            //public void mouseWheelMoved(final MouseWheelEvent e) {
            //    inside(e.getComponent());
            //}
            //
            //@Override
            //public void mouseDragged(final MouseEvent e) {
            //    inside(e.getComponent());
            //}
    
            @Override
            public void mouseMoved(final MouseEvent e) {
                inside(e.getComponent());
            }
    
            @Override
            public void mouseClicked(final MouseEvent e) {
                released(e.getComponent());
            }
    
            @Override
            public void mousePressed(final MouseEvent e) {
                final Component src = e.getComponent();
                if (src != null) {
                    if (src instanceof JComponent)
                        ((JComponent) src).setBorder(onPressed);
                    src.setBackground(bgOnPressed);
                    src.repaint();
                }
            }
    
            @Override
            public void mouseReleased(final MouseEvent e) {
                released(e.getComponent());
            }
    
            @Override
            public void mouseEntered(final MouseEvent e) {
                inside(e.getComponent());
            }
    
            @Override
            public void mouseExited(final MouseEvent e) {
                final Component src = e.getComponent();
                if (src != null) {
                    src.setForeground(fgOnNotEntered);
                    src.repaint();
                }
            }
        }
        
        public static JButton createMainButton(final String text,
                                               final MouseAdapter adapter,
                                               final Border border,
                                               final Color background,
                                               final Color foreground,
                                               final ActionListener aL) {
            final JButton button = (text == null)? new JButton(): new JButton(text);
            if (adapter != null) {
                button.addMouseListener(adapter);
                button.addMouseMotionListener(adapter);
                button.addMouseWheelListener(adapter);
            }
            if (border != null)
                button.setBorder(border);
            if (background != null) {
                button.setContentAreaFilled(false);
                button.setOpaque(true);
                button.setBackground(background);
            }
            if (foreground != null)
                button.setForeground(foreground);
            if (aL != null)
                button.addActionListener(aL);
            button.setFocusPainted(false);
            return button;
        }
        
        public static JPanel createBoxLayoutPanel(final int axis,
                                                  final Component... components) {
            final JPanel panel = new JPanel();
            panel.setLayout(new BoxLayout(panel, axis));
            if (components != null)
                for (final Component c: components) {
                    if (!c.isMinimumSizeSet())
                        c.setMinimumSize(c.getPreferredSize());
                    if (!c.isMaximumSizeSet())
                        c.setMaximumSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE));
                    panel.add(c);
                }
            return panel;
        }
        
        public static JLabel label(final String text,
                                   final Component labelFor,
                                   final Color foreground) {
            final JLabel jLabel = new JLabel(text, JLabel.CENTER);
            jLabel.setForeground(foreground);
            jLabel.setLabelFor(labelFor);
            return jLabel;
        }
        
        public static Box.Filler createNonOpaqueVerticalGlue() {
            final Box.Filler vglue = new Box.Filler(new Dimension(0,0), new Dimension(0,0), new Dimension(0, Short.MAX_VALUE));
            vglue.setOpaque(false);
            return vglue;
        }
        
        public static void main(final String[] args) {
            SwingUtilities.invokeLater(() -> {
                
                final int buttonBorderThickness = 10,
                          paddingThickness = 20;
                final Color borderOnPressed = new Color(182, 25, 25),
                            borderOnNotPressed = new Color(255, 107, 107),
                            backgroundOnPressed = new Color(255, 107, 107),
                            backgroundOnNotPressed = new Color(255, 107, 107),
                            foregroundOnEntered = new Color(253, 210, 199),
                            foregroundOnNotEntered = new Color(1, 36, 67),
                            leftBackground = Color.BLACK,
                            rightBackground = Color.GREEN,
                            labelForeground = Color.LIGHT_GRAY;
                final String firstButtonText = "Main button with long text as label",
                             secondButtonText = "Short",
                             labelText = "Some random text.";
                
                final Border onPressed = BorderFactory.createLineBorder(borderOnPressed, buttonBorderThickness),
                             onNotPressed = BorderFactory.createLineBorder(borderOnNotPressed, buttonBorderThickness),
                             padding = BorderFactory.createEmptyBorder(paddingThickness, paddingThickness, paddingThickness, paddingThickness);
                final MainButtonAdapter mainButtonAdapter = new MainButtonAdapter(foregroundOnEntered, foregroundOnNotEntered, backgroundOnPressed, backgroundOnNotPressed, onPressed, onNotPressed);
                
                final JButton firstButton = createMainButton(firstButtonText, mainButtonAdapter, onNotPressed, backgroundOnNotPressed, foregroundOnNotEntered, null),
                              secondButton = createMainButton(secondButtonText, mainButtonAdapter, onNotPressed, backgroundOnNotPressed, foregroundOnNotEntered, null);
                
                final Dimension secondButtonPreferredSize = secondButton.getPreferredSize();
                secondButton.setMaximumSize(new Dimension(Short.MAX_VALUE, secondButtonPreferredSize.height));
                
                final JLabel lbl = label(labelText, firstButton, labelForeground);
                lbl.setMaximumSize(lbl.getPreferredSize());
                
                final JPanel firstPanel = createBoxLayoutPanel(BoxLayout.X_AXIS, lbl, Box.createHorizontalStrut(paddingThickness), firstButton);
                firstPanel.setOpaque(false);
                firstPanel.setMaximumSize(new Dimension(Short.MAX_VALUE, firstPanel.getPreferredSize().height));
                
                //Ucomment the following lines to get bigger size proportion for the right (green) panel...
                //secondButtonPreferredSize.width = Math.max(500, secondButtonPreferredSize.width);
                //secondButton.setMinimumSize(secondButtonPreferredSize);
                //secondButton.setPreferredSize(secondButtonPreferredSize);
                
                final JPanel left = createBoxLayoutPanel(BoxLayout.Y_AXIS, firstPanel, createNonOpaqueVerticalGlue() /*replace this "glue" with your components...*/);
                left.setBorder(padding);
                left.setBackground(leftBackground);
                
                final JPanel right = createBoxLayoutPanel(BoxLayout.Y_AXIS, secondButton, createNonOpaqueVerticalGlue() /*replace this "glue" with your components...*/);
                right.setBorder(padding);
                right.setOpaque(false); //Could almost be 'right.setBackground(rightBackground);' instead.
                
                final JPanel contents = createBoxLayoutPanel(BoxLayout.X_AXIS, left, right);
                contents.setBackground(rightBackground); //That's why we called 'right.setOpaque(false);' previously.
                
                final JFrame frame = new JFrame("BoxLayout test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.getContentPane().add(contents);
                frame.pack();
                //frame.setSize(Math.max(1600, frame.getWidth()), Math.max(900, frame.getHeight()));
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            });
        }
    }
    

    Basically the content pane contains the two colored panels in a BoxLayout on X_AXIS. Then the left panel contains another BoxLayout panel with the label and the first button. Then it's all about taking care of minimum, preferred and maximum sizes of each Component.

    If you want to avoid nesting panels, I guess you can use a GridBagLayout on a custom painted panel probably (to paint the different background colors), which will also give you more power to fine tune the resizing behaviour using weight factors. I haven't tried that though.

    When you run it, remember to resize the frame with the mouse to see how it behaves.

    Later on you can replace the two createNonOpaqueVerticalGlue method calls with your desired Components, because I assumed you will later need more content inside the black and green panels. If you then need some vertical gaps use a vertical strut.