1

I am designing a Java app with Swing, and I have trouble designing the GUI without a layout.

My purpose is to design a GUI with one JPanel and four JButtons. I've done the math to set buttons and panel on the right place and coded like the following:

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

public class MainFrame extends JFrame {
    public MainFrame() {
        this.setTitle("Example Frame");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLayout(null);

        JPanel randomPanel = new JPanel();
        randomPanel.setOpaque(true);
        randomPanel.setBackground(Color.RED);
        randomPanel.setBounds(10, 10, 430, 530);

        JButton addButton = new JButton("Add");
        addButton.setBounds(10, 550, 100, 40);
        addButton.setBackground(Color.GRAY);

        JButton deleteButton = new JButton("Delete");
        deleteButton.setBounds(120, 550, 100, 40);
        deleteButton.setBackground(Color.GRAY);

        JButton refreshButton = new JButton("Refresh");
        refreshButton.setBounds(230, 550, 100, 40);
        refreshButton.setBackground(Color.GRAY);

        JButton devButton = new JButton("Developer");
        devButton.setBounds(340, 550, 100, 40);
        devButton.setBackground(Color.GRAY);

        this.add(randomPanel);
        this.add(addButton);
        this.add(deleteButton);
        this.add(refreshButton);
        this.add(devButton);

        this.setSize(900, 600);
        this.setResizable(false);
        this.setVisible(true);
    }

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

Following to the code, the components are expected to be placed as following:

Expected Form

However, the actual form was displayed as following:

Actual Form

The components exceed the form, which does not match with the expected look.

What is the problem of this and what should be done for an accurate placement of components?

cylee
  • 550
  • 6
  • 21
  • 2
    Without doing the math, I assume that you computed the sizes based on the size of the *frame*, and did not take into account the size of the title bar and borders. In general, you should **not** use `setBounds` for placing components. Use a [LayoutManager](https://docs.oracle.com/javase/tutorial/uiswing/layout/index.html) instead. In this case, you could have one "main" panel with a `BorderLayout`. The red panel could be in the `CENTER` of the "main" panel. The buttons could be in an own panel with a `GridLayout(1,4)`, which in turn is located in the `SOUTH` of the "main" panel. – Marco13 Dec 08 '18 at 00:56
  • @Marco13 I was also confused that if the size matters with title bar, so I screenshot the form and figured out the size on photoshop application. First the width and height were neither correct, and I resized width to 900px, then the height was 602px. I think the swing app itself is incorrect. I will try your solution, but I am doubt because I want the sizes precise. – cylee Dec 08 '18 at 01:07
  • @cylee There are simple APIs available which solve all these problems, start with the layout management API – MadProgrammer Dec 08 '18 at 03:38

3 Answers3

2

There are two main problems...

  • setLayout(null)
  • setSize

What you've not taken into account is the fact that the amount of space available to the content of the window, is the size of the window MINUS the frame decorations.

Pixel perfect layouts are an illusion in modern UI development and are best avoided.

You could have a look at:

for more details.

A better solution is to make use one or more available layout managers. The example below simply makes use of BorderLayout and GridLayout with the help of EmptyBorder to provide some padding

See Laying Out Components Within a Container for more details

Simple

Benefits

  • Adaptable layout:
    • The example uses pack to "pack" the window around the content, automatically, without you having to adapt your code to the currently running OS (or frame decorations provided by different look and feels)
    • The user can change the size of the window and the content will resize automatically - bonus to the user.
    • The layout will adapt to the user's system settings, so if they are using a font larger then you've designed for, it won't completely blow up in your face
    • Want to add more buttons? No worries, knock yourself out, just add more buttons, the layout will adapt automatically, no need to "pixel push" ever component on the screen

Runnable example...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;

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();
                }

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

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new BorderLayout());
            setBorder(new EmptyBorder(10, 10, 10, 10));
            add(new SizablePane(430, 530));

            JPanel buttonPane = new JPanel(new GridLayout(1, 3, 20, 0));
            buttonPane.setBorder(new EmptyBorder(10, 0, 0, 0));
            buttonPane.add(new JButton("Add"));
            buttonPane.add(new JButton("Delete"));
            buttonPane.add(new JButton("Refresh"));
            buttonPane.add(new JButton("Developer"));

            add(buttonPane, BorderLayout.SOUTH);
        }

    }

    public class SizablePane extends JPanel {

        private Dimension size;

        public SizablePane(int width, int height) {
            size = new Dimension(width, height);
            setBackground(Color.RED);
        }

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

    }

}

Need to add more buttons? Easy...

JPanel buttonPane = new JPanel(new GridLayout(1, 0, 20, 0));
buttonPane.setBorder(new EmptyBorder(10, 0, 0, 0));
buttonPane.add(new JButton("Add"));
buttonPane.add(new JButton("Delete"));
buttonPane.add(new JButton("Refresh"));
buttonPane.add(new JButton("Developer"));
buttonPane.add(new JButton("Some"));
buttonPane.add(new JButton("More"));
buttonPane.add(new JButton("Buttons"));
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • 1
    This is one of the cases that I referred to in https://meta.stackoverflow.com/q/373852/3182664 . (Frankly: That's the reason of why I hesitate to upvote this. But I did **not** hesitate to downvote the currently accepted one... \*sigh\*). Do we have a nice, canonical question for "Why shouldn't you use setBounds, but a LayoutManger instead?" If not, then the usual suspects (you, Hovercraft, camickr, and ... me, for that matter) should definitely create one! – Marco13 Dec 08 '18 at 11:55
  • @Marco13 I’ve written software for Windows, MacOS, Linux, iOS and Android and in all those cases (excluding gaming) I’ve not found a reason NOT to use the platforms/apis layout system. The amount of work involved in not doing so just doesn’t warrant the effort – MadProgrammer Dec 08 '18 at 21:10
  • @Marco13 I've even [animated layout managers](https://stackoverflow.com/questions/27463989/java-moving-jlabel-twice-using-timer/27466801#27466801), [randomised them](https://stackoverflow.com/questions/11819669/absolute-positioning-graphic-jpanel-inside-jframe-blocked-by-blank-sections/11822601#11822601), [customised them](https://stackoverflow.com/questions/24622279/laying-out-a-keyboard-in-swing/24625704#24625704) – MadProgrammer Dec 08 '18 at 21:28
  • 1
    @Marco13 And even used them in a "non traditional" [game based concept](https://stackoverflow.com/questions/33883330/cant-move-jlabel-on-jpanel/33884907#33884907). The point is, layout managers deal with a lot of variable issues, including DPI, screen resolutions, font metrics and rendering pipeline differences ... about the only time I "might" be tempted to NOT use a layout manager, is when I might want animate a component from one position to another (cross component boundaries), but once the component landed in the target component, it would be under the control of a layout manager again – MadProgrammer Dec 08 '18 at 21:30
  • @Marco13 You've also got to consider that in many cases, the question is about "how" to achieve the result, the fact is, in this case, this answer just describes (IMHO) the better solution to the over problem, so I don't think there is a single "you should use layout managers" q/a that would cover all the possible scenarios, but it would defiantly make it nicer then having to repeat ourselves all the time :P – MadProgrammer Dec 08 '18 at 21:34
  • @Marco13 [What's wrong with the Null Layout in Java?](https://stackoverflow.com/questions/21242626/whats-wrong-with-the-null-layout-in-java); [Why is it frowned upon to use a null layout in Swing?](https://stackoverflow.com/questions/6592468/why-is-it-frowned-upon-to-use-a-null-layout-in-swing); [Why null layout and absolute positions are bad practice in Java Swing?](https://stackoverflow.com/questions/40311819/why-null-layout-and-absolute-positions-are-bad-practice-in-java-swing) – MadProgrammer Dec 08 '18 at 21:40
  • @Marco13 You've also got to forgive me, as I'm very doggit about this issue as I spent the better part of 5 years undoing stupid mistakes of developer which simply broke the UI every time it ran on a different machine, to which I got most of the blame and criticised for the delays to the system :/ – MadProgrammer Dec 08 '18 at 21:44
  • That layout animator [looks familiar](https://stackoverflow.com/a/22135556/3182664) in some way ;-) Regarding custom layout managers: The built-in ones cover most practical cases, and I never felt the necessity for more than an [AspectLayout](https://github.com/javagl/CommonUI/blob/master/src/main/java/de/javagl/common/ui/layout/AspectLayout.java), but YMMV. The only point where I got kinky was when trying to [use layout managers for internal frames](https://github.com/javagl/CommonUI/blob/master/src/main/java/de/javagl/common/ui/utils/desktop/JDesktopPaneLayout.java) - yay... – Marco13 Dec 08 '18 at 23:31
  • Regarding the potential duplicates that you linked to: The *questions* may be ok-ish, but the (accepted) answers are somewhat text-heavy. I think it would be ideal to have a link to https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html , the (important!) hint that layout managers can be tremendously powerful when *nesting* the containers appropriately, maybe a pointer at "empty borders", and last but not least: Some screenshots and examples accompanied with code snippets... (yeah, could be some effort...) – Marco13 Dec 08 '18 at 23:34
  • @Marco13 I personally, wouldn't use layout managers with a `JDesktopPane`, expect in the case of providing some kind of "auto" system for convince, as it's contraindicative to what the `JDesktopPane` is designed for. The problem with just linking to the tutorials is, it doesn't actually "answer" the question, or at least not directly, as it doesn't cover the aspect of "why" you should use layout managers in great detail. Personally for me, coming from a number of environments where layout managers didn't exists, I intuitively understand why layout managers are important and the role they play – MadProgrammer Dec 08 '18 at 23:39
  • Sure, layouting Desktop panes is just an experiment (at best, a quirky "workaround" for not using nested split panes, a full fledged docking frames framework, or ... a rich client platform like Eclipse RCP...). I didn't mean to **only** link to the tutorial. But I think the link should be an accompanying part. Like: "You can nest e.g. BorderLayout and GridLayout like that `[short code]`, or other layout managers that are shown here `[link]`". – Marco13 Dec 08 '18 at 23:49
  • @Marco13 Sure, I usually do, must have been lazy this time :P - Besides, your comment already did ;) – MadProgrammer Dec 08 '18 at 23:59
0

I'm quite late, I don't think this will be helpful to OP anymore... But to anyone else in the same situation.

As others mentioned, when you setSize on a JFrame, that includes the title bar and borders. There's a way to get the size values for those, but... If you want to lay things out manually in your content pane, why not prepare a content pane first, then add it to the JFrame?

class MainPanel extends JPanel {
    public MainPanel() {
        setLayout(null);
        setPreferredSize(new Dimension(900, 600));
        // JFrame will have some layouting going on,
        // it won't listen to setSize

        JPanel randomPanel = new JPanel();
        randomPanel.setOpaque(true);
        randomPanel.setBackground(Color.RED);
        randomPanel.setBounds(10, 10, 430, 530);

        JButton addButton = new JButton("Add");
        addButton.setBounds(10, 550, 100, 40);
        addButton.setBackground(Color.GRAY);

        JButton deleteButton = new JButton("Delete");
        deleteButton.setBounds(120, 550, 100, 40);
        deleteButton.setBackground(Color.GRAY);

        JButton refreshButton = new JButton("Refresh");
        refreshButton.setBounds(230, 550, 100, 40);
        refreshButton.setBackground(Color.GRAY);

        JButton devButton = new JButton("Developer");
        devButton.setBounds(340, 550, 100, 40);
        devButton.setBackground(Color.GRAY);

        this.add(randomPanel);
        this.add(addButton);
        this.add(deleteButton);
        this.add(refreshButton);
        this.add(devButton);
    }

    public static void main(String[] args) {
        JFrame mainFrame = new JFrame();
        mainFrame.setTitle("Example Frame");
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        mainFrame.setContentPane(new MainPanel());

        mainFrame.pack();
        mainFrame.setResizable(false);
        mainFrame.setVisible(true);
    }
}

Screenshot of result. A JFrame slightly larger than 900 times 600 pixels, containing a 900 times 600 pixels sized JPanel, with child components laid out correctly (based on how the margins between and outside them look like)

If you mess with JFrame directly you're sort of bypassing the component system. Whereas this way, you're doing components just fine! Now, you have a JFrame fit to a single child panel, which has some things laid out manually.

This is how I normally do things, in such a situation.

P.S. "Don't lay things out manually, just use layout managers" is not something you can apply everywhere. You may need custom components sometimes, especially for something like a video game, where you have a game screen that you're custom rendering. Inside the game screen, you would be doing manual layout. They can coexist just fine, as long as you know which is which.

Gilbert Le Blanc
  • 50,182
  • 6
  • 67
  • 111
-1

You need to override the getInsets() method of the underlying JFrame.

@Override
public Insets getInsets() {
    return new Insets(0, 0, 0, 0);
}

Take a look at this question for more information.

Trevor Tippins
  • 2,827
  • 14
  • 10
  • No problem.The other way you can do it is to get the insets from the JFrame and then use those as offsets for your component positions, remembering to resize the JFrame to accommodate the additional space required by those borders. – Trevor Tippins Dec 08 '18 at 01:12
  • 2
    There is absolutely no reason to use a null layout for something like this. This the type of problem you have when you try to play with pixels. A layout manager will solve the problem in seconds. – camickr Dec 08 '18 at 01:31
  • @cylee - the others are right. More typically you'd use a layout manager in Swing (or, now, JavaFX) to manage your component layouts. This will give you an adaptive/responsive interface should you need it. However, as you've shown awareness of other LayoutManagers and seemed to expressly decided not to use one, I answered your question as asked rather than preaching at you.. – Trevor Tippins Dec 08 '18 at 22:43