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:
However, the actual form was displayed as following:
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?
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:
What's wrong with the Null Layout in Java?
Why is it frowned upon to use a null layout in Swing?
Why null layout and absolute positions are bad practice in Java Swing?
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
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"));
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);
}
}
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.
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.
Related
When I run the below code, I am unable to see the background color as red. It's showing default one. Is there anything that I have to add to these lines?
import java.awt.*;
import java.awt.Graphics;
import javax.swing.*;
public class gfix extends JPanel {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.fillRect(80, 100, 150, 75);
}
public static void main(String[] args){
gfix gg=new gfix();
JFrame frame = new JFrame("RISK");
frame.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(null);
frame.add(panel);
JButton button = new JButton("test");
button.setBounds(100, 100, 150, 150);
panel.add(button);
frame.setVisible(true);
}
}
Your are overriding painGraphics() in gfix class so add gfix class object into your frame not Java provided JPanel class object.
gfix gg=new gfix();
JFrame frame = new JFrame("RISK");
frame.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//JPanel panel = new JPanel(); Not needed
//panel.setLayout(null);
frame.add(gg);
JButton button = new JButton("test");
button.setBounds(100, 100, 150, 150);
gg.add(button);
frame.setVisible(true);
for g.fillRect(80, 100, 150, 75); give proper panel bounds to fill complete panel background. OR use int width = getWidth();
int height = getHeight(); in paintGraphics to get actual height and width.
import java.awt.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class gfix {
public static void main(String[] args) {
JFrame frame = new JFrame("RISK");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setBackground(Color.RED);
frame.add(panel);
JButton button = new JButton("test");
// adjust numbers as needed
button.setMargin(new Insets(20,40,20,40));
panel.add(button);
// adjust numbers as needed
panel.setBorder(new EmptyBorder(10,40,50,200));
frame.pack();
frame.setVisible(true);
}
}
Other tips:
Java GUIs have to work on different OS', screen size, screen resolution etc. using different PLAFs in different locales. As such, they are not conducive to pixel perfect layout. Instead use layout managers, or combinations of them along with layout padding and borders for white space.
Provide ASCII art or a simple drawing of the intended layout of the GUI at minimum size, and if resizable, with more width and height - to show how the extra space should be used.
Please learn common Java nomenclature (naming conventions - e.g. EachWordUpperCaseClass, firstWordLowerCaseMethod(), firstWordLowerCaseAttribute unless it is an UPPER_CASE_CONSTANT) and use it consistently.
The class extends JPanel,
public void createDisplay(){
frame = new JFrame();
frame.setTitle(title);
frame.setSize(new Dimension(width, height));
frame.setVisible(true);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setPreferredSize(new Dimension(width, height));
this.setMaximumSize(new Dimension(width, height));
this.setMinimumSize(new Dimension(width, height));
this.setLayout(null); //have tried default and BorderLayout
this.setSize(new Dimension(width, height));
this.setBounds(0, 0, width, height);
//basically trying everything
frame.add(this);
frame.pack();
}
on startup this code works fine and the JPanel completely covers the size of the Parent frame
However my program later tries to add a new JPanel to the class's extend JPanel with:
public void gameOverWindow(){ ;
JPanel panel = new JPanel(new BorderLayout());
panel.setPreferredSize(new Dimension(200, 100));
JLabel label = new JLabel("Game Over");
label.setPreferredSize(new Dimension(40, 15));
//trying setPosition also doesn't work with BorderLayout or FlowLayout
JButton button_01 = new JButton("new");
button_01.setPreferredSize(new Dimension(100, 10));
JButton button_02 = new JButton("exit");
button_02.setPreferredSize(new Dimension(100, 10));
panel.add(label, BorderLayout.NORTH);
panel.add(button_01, BorderLayout.WEST);
panel.add(button_02, BorderLayout.EAST);
this.add(panel);
this.revalidate();
}
This new JPanel appears with the contents within the correct BorderLayout format, however the JPanel itself will remain at the top center of the extended JPanel, I know this is because the default Layout is set to FlowLayout, however setting this to BorderLayout will just cause the panel to take up the entire screen. Setting the Layout to null completely breaks the frame and nothing appears but the Minimize and Close buttons of the Frame. Trying to set the position or Bounds of this JPanel doesn't work with any Layout either. I have read a lot of other post online about this but they all seem to differ and become confusing, how do I gain control of the position of my new JPanel?
Normally I'd recommend using a CardLayout for switching between different views, but it's difficult to ascertain from the available information if that would help or not
Instead, you could make use of compounding layouts. That is wrap one container in another using different layouts.
In this example, I simply use a combination of BorderLayout and GridBagLayout
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
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());
JButton gameOver = new JButton("Game over man");
gameOver.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JPanel panel = new JPanel(new BorderLayout());
panel.add(new JLabel("Game Over Man", JLabel.CENTER), BorderLayout.NORTH);
panel.add(new JButton("New"), BorderLayout.WEST);
panel.add(new JButton("Exit"), BorderLayout.EAST);
removeAll();
JPanel inner = new JPanel(new GridBagLayout());
inner.add(panel);
add(inner);
revalidate();
repaint();
}
});
JPanel inner = new JPanel(new GridBagLayout());
inner.add(gameOver);
add(inner);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
what is the purpose of removing the components of a different panel instead of just directly adding it to GridBagLayout?
Because they interfere with the layout of other components.
i then want a small Jpanel to popup within and be unobtrusive
You could make use of the frame's glassPane or use a OverlayLayout
For example:
Floating JPanel above a JPanel with BorderLayout
Rectangle is not drawn on top
Placing a marker within the image
Display a message on the screen
Much of this information should have been in your original question, it would have wasted less of each other's time
When I change a JLabel's font size or contents in a translucent JFrame, then call pack() on the frame, a ghosted version of the previous contents is visible. This only happens on macOS. The same code works fine on Windows.
Here's an enlarged example of what happens:
Here is code to reproduce the problem; make sure the JFrame is on top of a white background to better see the problem, then click the "Bigger" and "Smaller" buttons:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class ScratchSpace {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Test Transparent Frame");
frame.getRootPane().putClientProperty("apple.awt.draggableWindowBackground", true);
frame.setUndecorated(true);
frame.setBackground(new Color(0, 0, 0, 25));
JPanel contentPane = new JPanel(new FlowLayout());
contentPane.setOpaque(false);
contentPane.setBackground(new Color(0, 0, 0, 25));
JLabel label = new JLabel("Hello cruel world");
label.setForeground(Color.WHITE);
contentPane.add(label);
contentPane.add(new JButton(new AbstractAction("Bigger") {
#Override
public void actionPerformed(ActionEvent e) {
label.setFont(label.getFont().deriveFont(label.getFont().getSize() + 1f));
frame.pack();
}
}));
contentPane.add(new JButton(new AbstractAction("Smaller") {
#Override
public void actionPerformed(ActionEvent e) {
label.setFont(label.getFont().deriveFont(label.getFont().getSize() - 1f));
frame.pack();
}
}));
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
I've tried changing all components to non-opaque. I've tried revalidating the content pane after packing. What do I need to do to solve this?
Swing doesn't know how to paint transparent backgrounds properly so you need to do the painting yourself.
Check out Background With Transparency for more information and a couple of solutions.
But to set a frame transparent you should be using the setOpacity(...) method of the frame. Check out the section from the Swing tutorial on How to Create Translucent Windows;
I have the following code
JFrame frame = new JFrame("Organizer");
frame.setBounds(100, 100, 700, 700);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
JButton testbutton = new JButton("testbutton");
testbutton.setBounds(0, 0, 55, 55);
JButton testbutton2 = new JButton("tdestbutton2");
testbutton2.setBounds(55, 0, 44, 44);
frame.add(testbutton2);
frame.add(testbutton);
and the result sometimes is correct and sometimes is this
what im doing wrong?
What am I doing wrong?
Don't use setBounds(); do use a layout manager.
Invoke setVisible() after adding components to the enclosing container.
Construct and manipulate Swing GUI objects only on the event dispatch thread.
The example below adds a panel having an empty border and a GridLayout that is padded to match. For such an application, also consider JToolBar for the buttons and CardLayout for the working screens.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* #see http://stackoverflow.com/a/37366846/230513
*/
public class Test {
private static final int PAD = 50;
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel p = new JPanel(new GridLayout(0, 1, PAD, PAD));
p.setBorder(BorderFactory.createEmptyBorder(PAD, PAD, PAD, PAD));
p.add(new JButton("Test Button 1"));
p.add(new JButton("Test Button 2"));
f.add(p, BorderLayout.CENTER);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Test()::display);
}
}
testbutton.setBounds(0, 0, 55, 55);
testbutton.setLocation(50, 50);
testbutton2.setBounds(55, 0, 44, 44);
testbutton2.setLocation(50, 100);
Get rid of those statement because then do nothing.
They do nothing because the default layout manager for the content pane of a JFrame is a BorderLayout. The BorderLayout will reset the size/location of each component.
The BorderLayout expects you to provide a "constraint", when you add the component to the frame. If you don't provide the constraint, then the "CENTER" is used. Only a single component can be added to the "CENTER" so only the last button added is displayed.
A simple solution to understand the basic concept of using a BorderLayout is to use:
//frame.add(testbutton2);
//frame.add(testbutton);
frame.add(testbutton2, BorderLayout.PAGE_START);
frame.add(testbutton, BorderLayout.PAGE_END);
This will display the button on two rows.
Read the Swing tutorial on Layout Managers for more information and examples. Download the demo code and modify that code will follow Swing conventions.
For example, you need to add components to the frame BEFORE you make the frame visible. So the order of the code should be:
frame.add(...);
frame.add(...);
frame.pack();
frame.setVisible(true);
How can I place an object in a specific location (x,y) on a JFrame?
Here find the Absolute Positioning Tutorials. Please do read carefully, as to why this approach is discouraged over using LayoutManagers
To add say a JButton to your JPanel, you can use this :
JButton button = new JButton("Click Me");
button.setBounds(5, 5, 50, 30);
panel.add(button);
Here try this example program :
import java.awt.*;
import javax.swing.*;
public class AbsoluteLayoutExample
{
private void displayGUI()
{
JFrame frame = new JFrame("Absolute Layout Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel contentPane = new JPanel();
contentPane.setOpaque(true);
contentPane.setBackground(Color.WHITE);
contentPane.setLayout(null);
JLabel label = new JLabel(
"This JPanel uses Absolute Positioning"
, JLabel.CENTER);
label.setSize(300, 30);
label.setLocation(5, 5);
JButton button = new JButton("USELESS");
button.setSize(100, 30);
button.setLocation(95, 45);
contentPane.add(label);
contentPane.add(button);
frame.setContentPane(contentPane);
frame.setSize(310, 125);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String... args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new AbsoluteLayoutExample().displayGUI();
}
});
}
}
Try these 2... in combination with each other...
setLocation() and setBounds()
Its even better to use GroupLayout, developed by NetBeans team in 2005. WindowsBuilder Pro is a good tool for Building Gui in java
Check out this absolute layout code sample:
Absolute Layout demo
In the class inheriting the frame:
setLayout(null);
In your component:
setLocation(x,y);