4

If I have a JPanel with multiple subcomponents, how would I make it so that JPanel remains a square despite how its parent is resized? I have tried variations on the following code, but it does not result in the subcomponents also being square.

public void paint(Graphics g){
    if(this.isSquare()){
        Dimension d = this.getSize();
        if(d.height > d.width){
            this.setSize(d.width, d.width);
        } else {
            this.setSize(d.height, d.height);
    }
    super.paint(g);
}

Here is an SSCCE.

The containing parent:

import javax.swing.JFrame;

public class TestFrame extends JFrame{

    public TestFrame(){
        this.add(new TestPanel());
    }

    public static void main(String[] args){
        TestFrame tf = new TestFrame();

        tf.setSize(500, 500);
        tf.setVisible(true);
    }
}

What should be a square JPanel:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;

import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;


public class TestPanel extends JPanel{
    private boolean isSquare;

    public TestPanel(){
        this.setSquare(true);
        this.setLayout(new BorderLayout());

        JLabel panel1 = new JLabel();
        panel1.setBorder(new LineBorder(Color.RED, 4));
        panel1.setBackground(Color.CYAN);

        JLabel panel2 = new JLabel();
        panel2.setBorder(new LineBorder(Color.BLUE, 4));
        panel2.setBackground(Color.CYAN);


        this.setBorder(new LineBorder(Color.GREEN, 4));
        this.setBackground(Color.CYAN);

        this.add(panel1, BorderLayout.WEST);
        this.add(panel2, BorderLayout.EAST);
    }

    public void paint(Graphics g){
        if(this.isSquare()){
            Dimension d = this.getSize();
            if(d.height > d.width){
                this.setSize(d.width, d.width);
            } else {
                this.setSize(d.height, d.height);
            }
            super.paint(g);
        }
    }

    private boolean isSquare() {
        return isSquare;
    }

    private void setSquare(boolean isSquare) {
        this.isSquare = isSquare;
    }
}
MirroredFate
  • 12,396
  • 14
  • 68
  • 100
  • Use `JPanel#getPreferredSize` and a layout component that respects the preferred size of it's components – MadProgrammer Apr 18 '13 at 05:21
  • 1
    1) For `JPanel` override `paintComponent(Graphics)` instead of `paint(Graphics)` 2) Do not call `setSize(..)` from within either of those methods. 3) For better help sooner, post an [SSCCE](http://sscce.org/). 4) What is the point of this square component? – Andrew Thompson Apr 18 '13 at 05:24
  • @AndrewThompson I posted an SSCCE... The square component will act as a game board, but that is just in my specific instance. This question and subsequent answers will hopefully help everyone trying to figure out how to make any JComponent consistently square for any reason. – MirroredFate Apr 18 '13 at 05:44

2 Answers2

8
import java.awt.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;

public class SoSquare {

    public static void main(String[] args) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                // the GUI as seen by the user (without frame)
                // A single component added to a GBL with no constraint
                // will be centered.
                JPanel gui = new JPanel(new GridBagLayout());

                gui.setBackground(Color.WHITE);

                SquarePanel p = new SquarePanel();
                p.setBackground(Color.red);
                gui.add(p);

                JFrame f = new JFrame("Demo");
                f.add(gui);
                // Ensures JVM closes after frame(s) closed and
                // all non-daemon threads are finished
                f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                // See http://stackoverflow.com/a/7143398/418556 for demo.
                f.setLocationByPlatform(true);

                // ensures the frame is the minimum size it needs to be
                // in order display the components within it
                f.pack();
                f.setSize(400,100);
                // should be done last, to avoid flickering, moving,
                // resizing artifacts.
                f.setVisible(true);
            }
        };
        // Swing GUIs should be created and updated on the EDT
        // http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
        SwingUtilities.invokeLater(r);
    }
}

/**
 * A square panel for rendering. NOTE: To work correctly, this must be the only
 * component in a parent with a layout that allows the child to decide the size.
 */
class SquarePanel extends JPanel {

    @Override
    public Dimension getPreferredSize() {
        Dimension d = super.getPreferredSize();
        Container c = getParent();
        if (c != null) {
            d = c.getSize();
        } else {
            return new Dimension(10, 10);
        }
        int w = (int) d.getWidth();
        int h = (int) d.getHeight();
        int s = (w < h ? w : h);
        return new Dimension(s, s);
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • *"As long as you use GridBagLayout,.."* ..or `BoxLayout`, though GBL is simpler. See [this answer](http://stackoverflow.com/a/9090772/418556) for details. – Andrew Thompson Apr 18 '13 at 07:35
  • I am wondering if it is really a good practice to rely on the parent size to determine your own preferred size. This looks like an unstable loop. And if you don't call `setSize()` on your `frame` but only `pack()` it gets a (0,0) size. I think it would be better to either have `SquarePanel` have a dedicated LayoutManager which always returns a "square" preferred size or have its parent (`gui`) use a LayoutManager which allocates square sizes. – Guillaume Polet Apr 18 '13 at 08:52
  • @GuillaumePolet If feels like a hack, here. *"..use a LayoutManager which allocates square sizes"* That sounds like the best idea. – Andrew Thompson Apr 18 '13 at 09:15
  • @trashgod `FlowLayout` Won't center a component vertically will it? – Andrew Thompson Apr 18 '13 at 10:16
  • Correct, _and_ it add default gaps. – trashgod Apr 18 '13 at 10:17
  • 1
    What purpose is served by `Dimension d = super.getPreferredSize();`? `d` is only used if `c != null`, so it seems to make more sense to reverse the `if` test and stick everything to do with `d` after the `if` statement. – dfeuer Apr 12 '14 at 18:04
4

Take advantage of a layout manager that respect the preferred/min/max size of a component. Override the getPreferred/Minimum/MaximumSize methods to return the size you want.

enter image description here

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
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 SqaurePaneTest {

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

    public SqaurePaneTest() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new GridBagLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new BorderLayout());
            add(new JLabel("Look ma, I'm a square", JLabel.CENTER));
            setBorder(new LineBorder(Color.GRAY));
        }

        @Override
        public Dimension getMinimumSize() {
            return getPreferredSize();
        }

        @Override
        public Dimension getMaximumSize() {
            return getPreferredSize();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }
    }

}

Alternatively, create your own layout manager that does the same thing (makes all the components square)

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • It's not about forcing them to be a specific square, it's about keeping them square upon the parent being re-sized... – MirroredFate Apr 18 '13 at 05:39
  • Well, the above example will force the component to remain it's preferred size, so long as you use a layout manager that will respect those values. Try resizing the frame...the component will remain at it's preferred size – MadProgrammer Apr 18 '13 at 05:44
  • But that's the problem, you see. I don't want it to be a static size, I just want it to be so that the height and width are always equal, even when resized. – MirroredFate Apr 18 '13 at 05:46