cylee
cylee

Reputation: 570

Java Swing Pixels Being Inaccurate

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?

Upvotes: 1

Views: 653

Answers (3)

SusuKacangSoya
SusuKacangSoya

Reputation: 7

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.

Upvotes: 0

MadProgrammer
MadProgrammer

Reputation: 347184

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"));

Upvotes: 2

Trevor Tippins
Trevor Tippins

Reputation: 2847

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.

Upvotes: -1

Related Questions