'Making JButtons Always Centered Height-Wise in the JFrame?

I've written the following program thus far to help me get familiar with Java Swing GUI Programming. Is there a way that I could modify my code so that the two JButton controls (addButton & subtractButton) are always centered height-wise in the JFrame as the window is resized?

public class GUITesting extends JFrame implements ActionListener {

private static final int INITIAL_WIDTH = 400;
private static final int INITIAL_HEIGHT = 300;
JButton addButton, subtractButton;

public GUITesting() {
    
    setSize(INITIAL_WIDTH, INITIAL_HEIGHT);
    setLocation(200,200);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setTitle("0");
    
    addButton = new JButton("Add");
    addButton.addActionListener(this);
    add(addButton); // Adding Button to the Window Frame Creation
    subtractButton = new JButton("Subtract");
    subtractButton.addActionListener(this);
    
    setLayout(null);
    
    addButton.setSize(INITIAL_WIDTH / 4, INITIAL_HEIGHT / 6);
    subtractButton.setSize(INITIAL_WIDTH / 4, INITIAL_HEIGHT / 6);
    
    addButton.setLocation(INITIAL_WIDTH / 6, INITIAL_HEIGHT / 6);
    subtractButton.setLocation(7 * INITIAL_WIDTH / 12, INITIAL_HEIGHT / 6);
    
    add(addButton);
    add(subtractButton);
    
    addComponentListener(new Resizer());
    addMouseListener(new MouseAdapter() {
        public void mouseClicked(MouseEvent e) {
            int x = e.getX();
            int y = e.getY();
            setTitle("Mouse clicked at (" + x + "," + y + ")");
        }
    });
    
}

public void actionPerformed(ActionEvent event) {
    
    int value = Integer.parseInt(getTitle());
    
    if (event.getActionCommand().equals("Add")) {
        value = value + 1;
    }
    else {
        value = value - 1;
    }
    
}

private class Resizer extends ComponentAdapter {
    
    // Override Some of the Abstract Methods from ComponentAdapter
    public void componentResized(ComponentEvent event) {
        int width = getWidth();
        int height = getHeight();
        addButton.setSize(width / 4, height / 6); 
        subtractButton.setSize(width / 4, height / 6);
        addButton.setLocation(width / 6, height / 6);
        subtractButton.setLocation(7 * width / 12, height / 6);
    }
    
}

public static void main(String[] args) {
    GUITesting window = new GUITesting();
    window.setVisible(true);
}
}


Solution 1:[1]

Without doing anything special, you could simply make use of a GridBagLayout

enter image description here

import java.awt.EventQueue;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {
        public TestPane() {
            setLayout(new GridBagLayout());

            add(new JButton("Add"));
            add(new JButton("Subtract"));
        }
    }
}

See Laying Out Components Within a Container and How to Use GridBagLayout for more details

If you want the buttons to be the same size, you could use a compound layout, utilising both GridBagLayout and GridLayout, for example...

enter image description here

import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {
        public TestPane() {
            setLayout(new GridBagLayout());

            JPanel contentPane = new JPanel(new GridLayout(1, 2));
            contentPane.add(new JButton("Add"));
            contentPane.add(new JButton("Subtract"));

            add(contentPane);
        }
    }
}

But, if you want to do something REALLY funky, you could build your own layout manager, like a ButtonLayout, for example...

enter image description here

This basically does the same as the second example, but offers some additional alignment options which are easier achieved then doing them through GridBagLayout

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.LayoutManager2;
import java.awt.Point;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {
        public TestPane() {
            setLayout(new ButtonLayout(ButtonLayout.Alignment.HORIZONTAL, ButtonLayout.Anchor.CENTER));
            add(new JButton("Add"));
            add(new JButton("Subtract"));
        }
    }

    public class ButtonLayout implements LayoutManager2 {

        public enum Alignment {
            VERTICAL, HORIZONTAL
        }

        public enum Anchor {
            LEADING, CENTER, TRAILING
        }

        private Alignment alignment;
        private Anchor anchor;
        private int padding;

        private Dimension virtualBounds;

        public ButtonLayout() {
            this(Alignment.HORIZONTAL, Anchor.TRAILING, 0);
        }

        public ButtonLayout(Alignment alignment, Anchor anchor) {
            this(alignment, anchor, 0);
        }

        public ButtonLayout(Alignment alignment, Anchor anchor, int padding) {
            this.alignment = alignment;
            this.padding = padding;
            this.anchor = anchor;
        }

        public Alignment getAlignment() {
            return alignment;
        }

        public Anchor getAnchor() {
            return anchor;
        }

        protected int getPadding() {
            return padding;
        }

        protected int getTotalPadding(Container parent) {
            int padding = getPadding();
            return (padding * parent.getComponentCount()) - padding;
        }

        @Override
        public void addLayoutComponent(Component comp, Object constraints) {
        }

        @Override
        public void addLayoutComponent(String name, Component comp) {
        }

        @Override
        public void removeLayoutComponent(Component comp) {
        }

        @Override
        public void invalidateLayout(Container target) {
            virtualBounds = null;
        }

        protected Dimension virtualLayout(Container parent) {
            if (virtualBounds != null) {
                return virtualBounds;
            }
            int maxWidth = 0;
            int maxHeight = 0;

            for (Component component : parent.getComponents()) {
                Dimension preferredSize = component.getPreferredSize();
                maxHeight = Math.max(maxHeight, preferredSize.height);
                maxWidth = Math.max(maxWidth, preferredSize.width);
            }

            int padding = 0;
            int width = 0;
            int height = 0;
            int componentCount = parent.getComponentCount();
            switch (alignment) {
                case HORIZONTAL:
                    width = (maxWidth * componentCount) + getTotalPadding(parent);
                    height = maxHeight;
                    break;
                case VERTICAL:
                    width = maxWidth;
                    height = (maxHeight * componentCount) + getTotalPadding(parent);
                    break;
            }

            virtualBounds = new Dimension(width, height);
            return virtualBounds;
        }

        @Override
        public Dimension maximumLayoutSize(Container parent) {
            return virtualLayout(parent);
        }

        @Override
        public Dimension preferredLayoutSize(Container parent) {
            return virtualLayout(parent);
        }

        @Override
        public Dimension minimumLayoutSize(Container parent) {
            return virtualLayout(parent);
        }

        @Override
        public float getLayoutAlignmentX(Container target) {
            return 0.5f;
        }

        @Override
        public float getLayoutAlignmentY(Container target) {
            return 0.5f;
        }

        @Override
        public void layoutContainer(Container parent) {
            int maxWidth = 0;
            int maxHeight = 0;

            for (Component component : parent.getComponents()) {
                Dimension preferredSize = component.getPreferredSize();
                maxHeight = Math.max(maxHeight, preferredSize.height);
                maxWidth = Math.max(maxWidth, preferredSize.width);
            }

            Dimension defaultSize = new Dimension(maxWidth, maxHeight);
            Point point = offsetForAnchor(parent, defaultSize);

            int xDelta = 0;
            int yDelta = 0;
            switch (alignment) {
                case HORIZONTAL:
                    xDelta = getPadding() + defaultSize.width;
                    break;
                case VERTICAL:
                    yDelta = getPadding() + defaultSize.height;
                    break;
            }
            for (Component component : parent.getComponents()) {
                component.setSize(defaultSize);
                component.setLocation(point);
                point = new Point(point.x + xDelta, point.y + yDelta);
            }
        }

        protected Point offsetForAnchor(Container parent, Dimension defaultSize) {
            switch (anchor) {
                case LEADING:
                    return leadingOffSet(parent, defaultSize);
                case TRAILING:
                    return trailingOffSet(parent, defaultSize);
                case CENTER:
                    return centerOffSet(parent, defaultSize);
            }
            return new Point(0, 0);
        }

        protected Point leadingOffSet(Container parent, Dimension defaultSize) {
            Point point = new Point(0, 0);
            switch (alignment) {
                case HORIZONTAL:
                    point.x = padding;
                    point.y = (parent.getHeight() - defaultSize.height) / 2;
                    break;
                case VERTICAL:
                    point.x = (parent.getWidth() - defaultSize.width) / 2;
                    point.y = padding;
                    break;
            }
            return point;
        }

        protected Point trailingOffSet(Container parent, Dimension defaultSize) {
            Point point = new Point(0, 0);
            int componentCount = parent.getComponentCount();
            switch (alignment) {
                case HORIZONTAL:
                    int totalWidth = (defaultSize.width * componentCount) + getTotalPadding(parent);
                    point.x = parent.getWidth() - totalWidth;
                    point.y = (parent.getHeight() - defaultSize.height) / 2;
                    break;
                case VERTICAL:
                    int totalHeight = (defaultSize.height * componentCount) + getTotalPadding(parent);
                    point.x = (parent.getWidth() - defaultSize.width) / 2;
                    point.y = parent.getHeight() - totalHeight;
                    break;
            }
            return point;
        }

        protected Point centerOffSet(Container parent, Dimension defaultSize) {
            Point point = new Point(0, 0);
            int componentCount = parent.getComponentCount();
            switch (alignment) {
                case HORIZONTAL: {
                    int totalWidth = (defaultSize.width * componentCount) + getTotalPadding(parent);
                    point.x = (parent.getWidth() - totalWidth) / 2;
                    point.y = (parent.getHeight() - defaultSize.height) / 2;
                }
                break;
                case VERTICAL: {
                    int totalHeight = (defaultSize.height * componentCount) + getTotalPadding(parent);
                    point.x = (parent.getWidth() - defaultSize.width) / 2;
                    point.y = (parent.getHeight() - totalHeight) / 2;
                }
                break;
            }
            return point;
        }

    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1