Search code examples
javaswinglayout-managergridbaglayout

Trying to get a swing menu to work with GridBagLayout


I am trying to build an app with two vertical columns, anchored on the left side, and one big box in the right. I can get the first menu to stick to the right, but for some reason the second menu won't appear next to the first menu. I have read something about the extra space being pushed towards last column and last row (to the right). How am I supposed to handle that ?

P.S. I am using grid bag layout.

Here's what I've got:

Main UserView class: package gui;

import actions.DepositAddButtonAction;
import actions.DepositButtonAction;

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

public class UserView {
    public JFrame frame;
    private JPanel menuPanel;
    private JPanel secondMenuPanel;
    private JPanel contentPanel;
    private JButton depositButton;
    private JButton creditButton;
    private JButton exchangeButton;
    private JButton simulationButton;
    private JButton informationButton;
    private JLabel menuLabel;
    private GridBagLayout gridBagLayout;
    private GridBagConstraints constraints;
    private Border border;

    public UserView() {
        frame = new JFrame("E-Banking");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        menuPanel = new JPanel();
        secondMenuPanel = new JPanel();
        contentPanel = new JPanel();
        depositButton = new JButton("Deposit: ", new ImageIcon(UserView.class.getResource("/money_box_icon.png")));
        creditButton = new JButton("Credit: ", new ImageIcon(UserView.class.getResource("/credit_icon.png")));
        exchangeButton = new JButton("Exchange: ", new ImageIcon(UserView.class.getResource("/exchange_icon.png")));
        simulationButton = new JButton("Simulation: ", new ImageIcon(UserView.class.getResource("/simulation_icon.png")));
        informationButton = new JButton("Information: ", new ImageIcon(UserView.class.getResource("/info_icon.png")));
        menuLabel = new JLabel(new ImageIcon(UserView.class.getResource("/bank_icon.png")), SwingConstants.LEFT);
        gridBagLayout = new GridBagLayout();
        constraints = new GridBagConstraints();
        border = BorderFactory.createLineBorder(new Color(102, 102, 153), 1, true);
        frame.setSize(800, 600);
        applyButtonStyles();
        initialize();
    }

    private void applyButtonStyles() {
        depositButton.setHorizontalTextPosition(SwingConstants.RIGHT);
        creditButton.setHorizontalTextPosition(SwingConstants.RIGHT);
        exchangeButton.setHorizontalTextPosition(SwingConstants.RIGHT);
        simulationButton.setHorizontalTextPosition(SwingConstants.RIGHT);
        informationButton.setHorizontalTextPosition(SwingConstants.RIGHT);
        menuLabel.setHorizontalAlignment(SwingConstants.RIGHT);

        menuPanel.setBorder(border);
        secondMenuPanel.setBorder(border);
        secondMenuPanel.setVisible(false);
        contentPanel.setBorder(border);
        contentPanel.setVisible(false);

    }

    private void initialize() {
        menuLabel.setText("E-Banking");

        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.anchor = GridBagConstraints.FIRST_LINE_START;
        constraints.fill = GridBagConstraints.BOTH;
        constraints.insets = new Insets(4, 4, 4, 4);
        menuPanel.setLayout(gridBagLayout);

        menuPanel.add(menuLabel);
        constraints.gridy++;
        menuPanel.add(depositButton, constraints);
        constraints.gridy++;
        menuPanel.add(creditButton, constraints);
        constraints.gridy++;
        menuPanel.add(exchangeButton, constraints);
        constraints.gridy++;
        menuPanel.add(simulationButton, constraints);
        constraints.gridy++;
        menuPanel.add(informationButton, constraints);

        constraints.gridx = 1;
        constraints.gridy = 0;

        frame.getContentPane().setLayout(gridBagLayout);
        constraints.gridx = 0;
        constraints.gridy = 0;

        constraints.weightx = 0.4;
        constraints.weighty = 0.4;
        constraints.fill = GridBagConstraints.NONE;
        frame.getContentPane().add(menuPanel, constraints);
        constraints.gridx++;

        frame.getContentPane().add(secondMenuPanel, constraints);
        constraints.gridx++;

        frame.getContentPane().add(contentPanel, constraints);
        constraints.gridx++;

        DepositAddButtonAction depositAddButtonAction = new DepositAddButtonAction(contentPanel);
        DepositButtonAction depositButtonAction = new DepositButtonAction(secondMenuPanel, contentPanel, depositAddButtonAction, null, null);
        depositButton.addActionListener(depositButtonAction);


        }

}

Another class which represents the behavior of the first button:

package actions;

import gui.UserView;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class DepositButtonAction implements ActionListener {
    private JPanel secondMenuPanel;
    private JPanel contentPanel;
    private JButton addButton;
    private JButton queryButton;
    private JLabel operationLabel;
    private GridBagLayout gridBagLayout;
    private GridBagConstraints constraints;

    public DepositButtonAction(JPanel secondMenuPanel, JPanel contentPanel, ActionListener otherDepositAddButtonAction,
                               ActionListener otherDepositRemoveButtonAction, ActionListener otherDepositQueryButtonAction) {

        this.secondMenuPanel = secondMenuPanel;
        secondMenuPanel.setVisible(false);
        this.contentPanel = contentPanel;
        addButton = new JButton("Request", new ImageIcon(UserView.class.getResource("/add_icon.png")));
        queryButton = new JButton("Query", new ImageIcon(UserView.class.getResource("/info_icon.png")));
        operationLabel = new JLabel(new ImageIcon(UserView.class.getResource("/options_icon.png")));
        operationLabel.setText("Options ");
        gridBagLayout = new GridBagLayout();
        constraints = new GridBagConstraints();
        applyStyles();
        addButton.addActionListener(otherDepositAddButtonAction);
//        removeButton.addActionListener(otherDepositRemoveButtonAction);
//        queryButton.addActionListener(otherDepositQueryButtonAction);
    }

    private void applyStyles() {
        secondMenuPanel.setLayout(gridBagLayout);
        addButton.setHorizontalTextPosition(SwingConstants.RIGHT);
        queryButton.setHorizontalTextPosition(SwingConstants.RIGHT);
    }

    private void initialize() {
        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.anchor = GridBagConstraints.FIRST_LINE_START;
        constraints.fill = GridBagConstraints.BOTH;


        constraints.insets = new Insets(4, 4, 4, 4);
        secondMenuPanel.add(operationLabel, constraints);
        constraints.gridy++;

        secondMenuPanel.add(addButton, constraints);
        constraints.gridy++;

        secondMenuPanel.add(queryButton, constraints);

    }


    @Override
    public void actionPerformed(ActionEvent arg0) {
        secondMenuPanel.setVisible(true);
        contentPanel.setVisible(false);
        secondMenuPanel.removeAll();
        contentPanel.removeAll();
        contentPanel.revalidate();
        contentPanel.repaint();
        initialize();
        secondMenuPanel.revalidate();
        secondMenuPanel.repaint();
    }

}

What I am trying to get: enter image description here

Thanks in advance!


Solution

  • Remember, you're not confined to a using a single layout manager. You can combine layout managers by using multiple containers, allowing you to focus on the individual needs of each container and further isolating functionality and reducing the complexity of overlapping layout requirements.

    You should also have a look at How to Use CardLayout, which will provide you means for flipping between different views.

    To this end, I went a slightly different way, I started with a concept of a "menu container" which can contain "sub menus"

    public class MenuPane extends JPanel {
    
        public MenuPane() {
            setLayout(new GridBagLayout());
        }
    
        public void addSubMenuPane(SubMenuPane pane) {
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridy = 0;
            gbc.anchor = GridBagConstraints.NORTH;
            gbc.weighty = 1;
            add(pane, gbc);
    
            revalidate();
            repaint();
        }
    
        public void removeSubMenu(SubMenuPane pane) {
            remove(pane);
            revalidate();
            repaint();
        }
    
        public void popLastMenu() {
            if (getComponentCount() == 1) {
                return;
            }
            remove(getComponent(getComponentCount() - 1));
            revalidate();
            repaint();
        }
    
    }
    
    public class SubMenuPane extends JPanel {
    
        public SubMenuPane(String name) {
            setLayout(new GridLayout(0, 1));
            setBorder(new LineBorder(Color.DARK_GRAY));
            add(new JLabel(name, JLabel.CENTER));
        }
    
        public SubMenuPane addAction(MenuAction action) {
            JButton btn = new JButton(action);
            add(btn);
            return this;
        }
    
    }
    

    Something I wanted to do was decouple parts of the API and reduce the amount of knowledge that any one part of the API had on the rest.

    Basically, this manifests itself in the SubMenuPane, it's just a container for some buttons, which are configured and managed via the MenuAction class, beyond that, it does nothing.

    public interface MenuAction extends Action {
    
        public MenuController getController();
    }
    
    public abstract class AbstractMenuAction extends AbstractAction implements MenuAction {
    
        private MenuController controller;
    
        public AbstractMenuAction(MenuController controller, String name) {
            this.controller = controller;
            putValue(NAME, name);
        }
    
        @Override
        public MenuController getController() {
            return controller;
        }
    
    }
    

    The MenuAction is based on the Actions API, which provides a self contained and configurable unit of work

    And just for good measure I threw in a controller to sit between the menu pane and the menu actions.

    public interface MenuController {
        public void addSubMenu(SubMenuPane subMenuPane);
        public void popLastMenu();
    }
    
    public class DefaultMenuController implements MenuController {
    
        private MenuPane menuPane;
    
        public DefaultMenuController(MenuPane menuPane) {
            this.menuPane = menuPane;
        }
    
        @Override
        public void addSubMenu(SubMenuPane subMenuPane) {
            menuPane.addSubMenuPane(subMenuPane);
        }
    
        @Override
        public void popLastMenu() {
            menuPane.popLastMenu();
        }
    
    }
    

    The intention here is, I didn't want the actions to be able to adversely modify the menu pane, instead, I've restricted them to just two operations.

    Okay, but how does this help you?

    Well, lets build a menu and find out...

    MenuPane menuPane = new MenuPane();
    DefaultMenuController controller = new DefaultMenuController(menuPane);
    
    SubMenuPane ebanking = new SubMenuPane("E-Banking");
    ebanking.addAction(new AbstractMenuAction(controller, "Deposit") {
        @Override
        public void actionPerformed(ActionEvent e) {
            getController().popLastMenu();
            SubMenuPane deposit = new SubMenuPane("Options").addAction(new AbstractMenuAction(getController(), "Request") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Use Card layout to show next avaliable options
                }
            }).addAction(new AbstractMenuAction(getController(), "Query") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Use Card layout to show next avaliable options
                }
            });
            getController().addSubMenu(deposit);
        }
    }).addAction(new AbstractMenuAction(controller, "Credit") {
        @Override
        public void actionPerformed(ActionEvent e) {
            getController().popLastMenu();
            SubMenuPane deposit = new SubMenuPane("Credit-Options").addAction(new AbstractMenuAction(getController(), "Request") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Use Card layout to show next avaliable options
                }
            }).addAction(new AbstractMenuAction(getController(), "Query") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Use Card layout to show next avaliable options
                }
            });
            getController().addSubMenu(deposit);
        }
    }).addAction(new AbstractMenuAction(controller, "Exchange") {
        @Override
        public void actionPerformed(ActionEvent e) {
            getController().popLastMenu();
            SubMenuPane deposit = new SubMenuPane("Exchange-Options").addAction(new AbstractMenuAction(getController(), "Request") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Use Card layout to show next avaliable options
                }
            }).addAction(new AbstractMenuAction(getController(), "Query") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Use Card layout to show next avaliable options
                }
            });
            getController().addSubMenu(deposit);
        }
    }).addAction(new AbstractMenuAction(controller, "Simulation") {
        @Override
        public void actionPerformed(ActionEvent e) {
            getController().popLastMenu();
            SubMenuPane deposit = new SubMenuPane("Simulation-Options").addAction(new AbstractMenuAction(getController(), "Request") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Use Card layout to show next avaliable options
                }
            }).addAction(new AbstractMenuAction(getController(), "Query") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Use Card layout to show next avaliable options
                }
            });
            getController().addSubMenu(deposit);
        }
    }).addAction(new AbstractMenuAction(controller, "Information") {
        @Override
        public void actionPerformed(ActionEvent e) {
            getController().popLastMenu();
            SubMenuPane deposit = new SubMenuPane("Information-Options").addAction(new AbstractMenuAction(getController(), "Request") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Use Card layout to show next avaliable options
                }
            }).addAction(new AbstractMenuAction(getController(), "Query") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Use Card layout to show next avaliable options
                }
            });
            getController().addSubMenu(deposit);
        }
    });
    controller.addSubMenu(ebanking);
    

    Okay, so that's a lot of "What the ... code". I've used a lot of anonymous classes simply for the sake of brevity, realistically, I probably have subclass setup for each menu action, but this provides the basic ground work.

    The point is, each submenu can be crafted simply and easily, independent of the MenuPane, as they bound together through the controller.

    You could further expand the controller to provide additional functionality to either trigger operations that create the subviews or provide a means directly through the controller to open a subview you provide it.

    The reason I wanted to demonstrate this is idea is because, as you add new submenus, it will have an affect of the remaining content area, if you tried to maintain this in a single layout/container, you would continuously be struggling trying to keep it all together and working nicely together.

    Instead, this solution contains the submenu to their own container, which manages just the menus, it is then added to another container, potentially using a different layout manager, which then managers the outer concerns separately.

    Just as an idea

    Runnable example...

    Putting to together

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Container;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.awt.GridLayout;
    import java.awt.event.ActionEvent;
    import javax.swing.AbstractAction;
    import javax.swing.Action;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.border.LineBorder;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    MenuPane menuPane = new MenuPane();
                    DefaultMenuController controller = new DefaultMenuController(menuPane);
    
                    SubMenuPane ebanking = new SubMenuPane("E-Banking");
                    ebanking.addAction(new AbstractMenuAction(controller, "Deposit") {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            getController().popLastMenu();
                            SubMenuPane deposit = new SubMenuPane("Options").addAction(new AbstractMenuAction(getController(), "Request") {
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                    // Use Card layout to show next avaliable options
                                }
                            }).addAction(new AbstractMenuAction(getController(), "Query") {
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                    // Use Card layout to show next avaliable options
                                }
                            });
                            getController().addSubMenu(deposit);
                        }
                    }).addAction(new AbstractMenuAction(controller, "Credit") {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            getController().popLastMenu();
                            SubMenuPane deposit = new SubMenuPane("Credit-Options").addAction(new AbstractMenuAction(getController(), "Request") {
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                    // Use Card layout to show next avaliable options
                                }
                            }).addAction(new AbstractMenuAction(getController(), "Query") {
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                    // Use Card layout to show next avaliable options
                                }
                            });
                            getController().addSubMenu(deposit);
                        }
                    }).addAction(new AbstractMenuAction(controller, "Exchange") {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            getController().popLastMenu();
                            SubMenuPane deposit = new SubMenuPane("Exchange-Options").addAction(new AbstractMenuAction(getController(), "Request") {
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                    // Use Card layout to show next avaliable options
                                }
                            }).addAction(new AbstractMenuAction(getController(), "Query") {
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                    // Use Card layout to show next avaliable options
                                }
                            });
                            getController().addSubMenu(deposit);
                        }
                    }).addAction(new AbstractMenuAction(controller, "Simulation") {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            getController().popLastMenu();
                            SubMenuPane deposit = new SubMenuPane("Simulation-Options").addAction(new AbstractMenuAction(getController(), "Request") {
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                    // Use Card layout to show next avaliable options
                                }
                            }).addAction(new AbstractMenuAction(getController(), "Query") {
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                    // Use Card layout to show next avaliable options
                                }
                            });
                            getController().addSubMenu(deposit);
                        }
                    }).addAction(new AbstractMenuAction(controller, "Information") {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            getController().popLastMenu();
                            SubMenuPane deposit = new SubMenuPane("Information-Options").addAction(new AbstractMenuAction(getController(), "Request") {
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                    // Use Card layout to show next avaliable options
                                }
                            }).addAction(new AbstractMenuAction(getController(), "Query") {
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                    // Use Card layout to show next avaliable options
                                }
                            });
                            getController().addSubMenu(deposit);
                        }
                    });
                    controller.addSubMenu(ebanking);
    
                    JPanel someContent = new JPanel() {
                        @Override
                        public Dimension getPreferredSize() {
                            return new Dimension(200, 200);
                        }
                    };
                    someContent.setBackground(Color.RED);
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(menuPane, BorderLayout.WEST);
                    frame.add(someContent);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public interface MenuController {
    
            public void addSubMenu(SubMenuPane subMenuPane);
    
            public void popLastMenu();
        }
    
        public class DefaultMenuController implements MenuController {
    
            private MenuPane menuPane;
    
            public DefaultMenuController(MenuPane menuPane) {
                this.menuPane = menuPane;
            }
    
            @Override
            public void addSubMenu(SubMenuPane subMenuPane) {
                menuPane.addSubMenuPane(subMenuPane);
            }
    
            @Override
            public void popLastMenu() {
                menuPane.popLastMenu();
            }
    
        }
    
        public interface MenuAction extends Action {
    
            public MenuController getController();
        }
    
        public abstract class AbstractMenuAction extends AbstractAction implements MenuAction {
    
            private MenuController controller;
    
            public AbstractMenuAction(MenuController controller, String name) {
                this.controller = controller;
                putValue(NAME, name);
            }
    
            @Override
            public MenuController getController() {
                return controller;
            }
    
        }
    
        public class MenuPane extends JPanel {
    
            public MenuPane() {
                setLayout(new GridBagLayout());
            }
    
            public void addSubMenuPane(SubMenuPane pane) {
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.gridy = 0;
                gbc.anchor = GridBagConstraints.NORTH;
                gbc.weighty = 1;
                add(pane, gbc);
    
                revalidate();
                repaint();
            }
    
            public void removeSubMenu(SubMenuPane pane) {
                remove(pane);
                revalidate();
                repaint();
            }
    
            public void popLastMenu() {
                if (getComponentCount() == 1) {
                    return;
                }
                remove(getComponent(getComponentCount() - 1));
                revalidate();
                repaint();
            }
    
        }
    
        public class SubMenuPane extends JPanel {
    
            public SubMenuPane(String name) {
                setLayout(new GridLayout(0, 1));
                setBorder(new LineBorder(Color.DARK_GRAY));
                add(new JLabel(name, JLabel.CENTER));
            }
    
            public SubMenuPane addAction(MenuAction action) {
                JButton btn = new JButton(action);
                add(btn);
                return this;
            }
    
            public void pop() {
                Container parent = getParent();
                if (parent != null) {
                    parent.remove(this);
                    parent.revalidate();
                    parent.repaint();
                }
            }
    
        }
    
    }
    

    nb: This is an incomplete solution intended to promote a "different way of thinking" when approaching these kind of issues