Is setting preferred size of JFrame using setPreferredSize() considered bad?
If it is bad, what is good way to change JFrame window size to dimension I need.
I know laying components in way to reflect final JFrame dimension that I need. But if I use shortcut to change preferred size using call setPreferredSize() just before call to pack() to change final JFrame size is bad? If so why?
For example I have sample form:
This is displayed without setting preferred size.
Now I can resize form with call to setPreferredSize() before call to pack().
This is displayed with call: setPreferredSize(new Dimension(500, 300));
I can have similar effect with setting components size while laying it out. But what is disadvantage of setting frame size with just call to setPreferredSize().
I can think setting preferred size as resizing displayed window manually with mouse after it has been displayed. Isn't it?
Code:
import java.awt.Dimension;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class MyFrame extends JFrame {
private static final long serialVersionUID = 1L;
private JTextField fullNameTextField = new JTextField();
private JTextField emailIDTextField = new JTextField();
private JTextArea addressTextArea = new JTextArea();
private JButton submitButton = new JButton("Submit");
private JButton cancelButton = new JButton("Cancel");
public MyFrame(){
super("MyFrame");
layoutComponents();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationByPlatform(true);
setPreferredSize(new Dimension(500, 300));
pack();
}
private void layoutComponents(){
GroupLayout layout = new GroupLayout(getContentPane());
getContentPane().setLayout(layout);
JLabel fullNameLabel = new JLabel("Full Name:");
JLabel emailIDLabel = new JLabel("Email ID:");
JLabel addressLabel = new JLabel("Address:");
JScrollPane addressScrollPane = new JScrollPane(addressTextArea);
layout.setHorizontalGroup(
layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(10)
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(fullNameLabel)
.addComponent(emailIDLabel)
.addComponent(addressLabel)
)
.addGap(15)
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(fullNameTextField, 10, 200, Short.MAX_VALUE)
.addComponent(emailIDTextField)
.addComponent(addressScrollPane)
.addGroup(layout.createSequentialGroup()
.addGap(0, 0, Short.MAX_VALUE)
.addComponent(submitButton)
.addGap(10)
.addComponent(cancelButton)
)
)
)
)
.addGap(10)
)
);
layout.setVerticalGroup(
layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(10)
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(fullNameLabel)
.addComponent(fullNameTextField)
)
.addGap(5)
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(emailIDLabel)
.addComponent(emailIDTextField)
)
.addGap(5)
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(addressLabel)
.addComponent(addressScrollPane, 20, 60, Short.MAX_VALUE)
)
.addGap(15)
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(submitButton)
.addComponent(cancelButton)
)
.addGap(10)
)
);
layout.linkSize(submitButton, cancelButton);
getRootPane().setDefaultButton(submitButton);
}
public static void main(String [] args){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new MyFrame().setVisible(true);
}
});
}
}
The main reason it would be considered bad (bad is probably too strong a word, unwise might be better) is the use of unknown (magic) numbers, rather than empirical values.
Every platform (and even similar OS running on different hardware and settings) has it's own means for rendering content which can change the amount of space that individual components require.
In regards to things like text fields and text-areas, you can make suggestions about the number of columns (and rows for text areas) which can be used to change a frames final size, using setColumns and setRows for example...
So, using the following code...
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestLayout101 {
public static void main(String[] args) {
new TestLayout101();
}
public TestLayout101() {
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 {
private JTextField fullNameTextField = new JTextField(10);
private JTextField emailIDTextField = new JTextField(10);
private JTextArea addressTextArea = new JTextArea(10, 20);
private JButton submitButton = new JButton("Submit");
private JButton cancelButton = new JButton("Cancel");
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(4, 4, 4, 4);
add(new JLabel("Full Name: "), gbc);
gbc.gridy++;
add(new JLabel("Email ID: "), gbc);
gbc.gridy++;
gbc.anchor = GridBagConstraints.NORTHWEST;
add(new JLabel("Address: "), gbc);
gbc.gridx++;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1;
add(fullNameTextField, gbc);
gbc.gridy++;
add(emailIDTextField, gbc);
gbc.gridy++;
gbc.weighty = 1;
add(new JScrollPane(addressTextArea), gbc);
JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT, 4, 4));
buttons.add(submitButton);
buttons.add(cancelButton);
gbc.gridx = 0;
gbc.gridy++;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.weighty = 0;
add(buttons, gbc);
}
}
}
Which produces...
Now by changing only one line...
private JTextArea addressTextArea = new JTextArea(10, 20);
// Only this value ---^
It produces this...
And again...
private JTextArea addressTextArea = new JTextArea(10, 40);
// Only this value ---^
I could change the number of rows for the JTextArea and effect the height of the window as well.
The difference is, these values are used in combination with the systems font metrics to calculate the preferred size of the components when the program is run, so it will be different for different systems and platforms...
The main point to layouts is to spend your time focused on the intent and the work flow and not trying to get a pixel perfect solution, because there's simply no such thing...talk to web developers, they have the same issues, just much worse (multiple browsers on a single system, all rendering differently)
Personally, I think there is nothing wrong with using frame.setPreferredSize() to resize a JFrame window. In fact, I think it is the most appropriate solution for resizing it.
But this is a good time to make a distinction between what some of the most common resizing methods do:
frame.pack(), as explained here basically takes the prefered sizes for each of the individual elements.
frame.setSize() resizes the window as expected, but only if a component's parent has no layout manager expressly defined.
There is a discussion concerning the use of this method here.
That doesn't really leave any other methods to use to define the size of a window, which is why I think setting the preferred window size is the best way go about it. I would still make sure to define setMinimumSize() and setMaximumSize() to ensure the components are resized correctly in all cases.
Edit:
Other posts in this thread got me thinking about the use of 'magic constants' in setting a components. In web development, for instance, it is generally frowned upon to use pixels, for example, as this changes widely by system. I wanted to see if this was also true with dimensions. In short, my findings concluded it doesn't.
This was a JFrame with a prefered size of new Dimension(200, 300) taken on my MacBook Pro with Retina Display:
This was a JFrame with a prefered size of new Dimension(200, 300) taken on my Asus Windows Tablet (non-retina display):
These windows looked the exact same and, although I couldn't find this published, the Dimenion is really an already-scaled proportion, taking into consideration the height, width, and resolution of the display to produce a near accurate rendering across all devices. This, I guess you could say, makes sense because it is the Java way.
That being said, here's some things I think need to watched out for:
The difference between UIManager LookAndFeel across platforms as MadProgrammer pointed out
Honestly, I think the final decision is left up to the programmer. After this testing, I pretty much still conclude that using the frame.setPreferredSize() is one of the best things available if you don't intend to a implement a semi-complex window manager. Even then, I think there will always be some unaccounted variation.
Related
I am trying to layout my JPanel such that:
The information label is at the top,
The text area is directly below, this and the info label go across (horizontally) the entire panel,
Below the text from left to right (equally spaced) are the config, save and then clear buttons,
Below those is the success/failure label (central, under the save button) and the home button (right, under the clear button).
I have tried many combinations and used other stack answers but cannot get it right, something about group layout I can't get my head around!
import javax.swing.*;
public class JFrameTest {
private static JFrame mainApp;
private static JPanel mainPanel;
public JFrameTest() {
mainApp = new JFrame("Application");
mainApp.setSize(640, 480);
mainApp.setLocationRelativeTo(null);
mainApp.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainApp.add(mainPanel());
mainApp.setVisible(true);
}
private JPanel mainPanel() {
JFrame.setDefaultLookAndFeelDecorated(true);
mainPanel= new JPanel();
mainPanel.setSize(600,450);
Container container = mainApp.getContentPane();
JLabel labelInfo = new JLabel("add necessary information here");
JLabel labelSOrF = new JLabel("Success/Failure");
// labelSOrF.setVisible(false);
JTextArea textArea = new JTextArea();
JButton configButton= new JButton("config");
JButton saveButton= new JButton("save");
JButton clearButton= new JButton("clear");
JButton homeButton= new JButton("Home");
GroupLayout layout = new GroupLayout(container);
container.setLayout(layout);
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
layout.setHorizontalGroup(
layout.createSequentialGroup()
.addGroup(layout.createParallelGroup()
.addComponent(configButton)
.addGroup(layout.createParallelGroup()
.addComponent(saveButton)
.addComponent(labelSOrF, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup()
.addComponent(clearButton)
.addComponent(homeButton)
.addComponent(labelInfo)
.addComponent(textArea, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
)))
);
layout.setVerticalGroup(
layout.createSequentialGroup()
.addComponent(labelInfo)
.addComponent(textArea, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup()
.addGroup(layout.createSequentialGroup()
.addComponent(configButton)
.addComponent(saveButton)
.addComponent(buttonClear))
.addGroup(layout.createParallelGroup()
.addComponent(labelSOrF, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(homeButton)))
);
edit - added code
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section.
GroupLayout was designed for GUI builders. No human being will deliberately use a GroupLayout. It's too hard to understand and as you've discovered, maintain.
Here's a GUI that meets your requirements.
Swing was designed to be constructed from the inside out. You layout the Swing components and let the JPanels and the JFrame size themselves. You don't start with the JFrame and fit all the Swing components.
All Swing applications must start with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.
I created a main JPanel with three subordinate JPanels. To construct a complex GUI, you nest simple JPanels.
I added the Swing components to each JPanel in column, row order. That helps me to organize the code and makes it easier for readers of your code to understand the code.
Here's the complete runnable code.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class JFrameTest implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new JFrameTest());
}
#Override
public void run() {
JFrame mainApp = new JFrame("Application");
mainApp.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainApp.add(createMainPanel(), BorderLayout.CENTER);
mainApp.pack();
mainApp.setLocationRelativeTo(null);
mainApp.setVisible(true);
}
private JPanel createMainPanel() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
panel.add(createTitlePanel());
panel.add(createTextAreaPanel());
panel.add(createButtonPanel());
return panel;
}
private JPanel createTitlePanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5));
JLabel labelInfo = new JLabel("add necessary information here");
panel.add(labelInfo);
return panel;
}
private JPanel createTextAreaPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5));
JTextArea textArea = new JTextArea(5, 40);
JScrollPane scrollPane = new JScrollPane(textArea);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
panel.add(scrollPane);
return panel;
}
private JPanel createButtonPanel() {
JPanel panel = new JPanel(new GridLayout(0, 3, 20, 5));
panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5));
JButton configButton = new JButton("config");
panel.add(configButton);
JButton saveButton = new JButton("save");
panel.add(saveButton);
JButton clearButton = new JButton("clear");
panel.add(clearButton);
JLabel dummy = new JLabel(" ");
panel.add(dummy);
JLabel labelSOrF = new JLabel("Success/Failure");
panel.add(labelSOrF);
JButton homeButton = new JButton("Home");
panel.add(homeButton);
return panel;
}
}
I only learn Java Swing 1 week so I tried complete some exercises. This is my code. I set 9 icon to 9 button but it doesn't show on button.
package mypack;
import java.awt.Color;
import javax.swing.*;
public class PuzzleGame extends JFrame{
static JButton bt1,bt2,bt3,bt4,bt5,bt6,bt7,bt8,bt9,btNew,btExit;
static JLabel move, moveNum, time, timeNum;
public PuzzleGame(){
createMyGUI();
}
public static void createMyGUI(){
JFrame jf = new JFrame("Game Puzzle Java");
JPanel jpl = new JPanel();
Icon icSpace = new ImageIcon("images/0.png");
Icon ic1 = new ImageIcon("images/1.png");
Icon ic2 = new ImageIcon("images/2.png");
Icon ic3 = new ImageIcon("images/3.png");
Icon ic4 = new ImageIcon("images/4.png");
Icon ic5 = new ImageIcon("images/5.png");
Icon ic6 = new ImageIcon("images/6.png");
Icon ic7 = new ImageIcon("images/7.png");
Icon ic8 = new ImageIcon("images/8.png");
jpl.setSize(100,100);
jpl.setBounds(480, 50, 200, 200);
jpl.setBackground(Color.BLUE);
move = new JLabel("Move:");
move.setBounds(480,10,50,20);
moveNum = new JLabel("0");
moveNum.setBounds(530, 10, 50, 20);
time = new JLabel("Time:");
time.setBounds(580, 10, 50, 20);
timeNum = new JLabel("0");
timeNum.setBounds(630,10,50,20);
btNew = new JButton("New Game");
btNew.setBounds(480, 270, 200, 80);
btExit = new JButton("Exit");
btExit.setBounds(480, 370, 200, 80);
jf.add(move);
jf.add(moveNum);
jf.add(time);
jf.add(timeNum);
jf.add(btNew);
jf.add(btExit);
jf.add(jpl);
jf.setSize(700, 500);
jf.setLocation(300,20);
jf.setLayout(null);
jf.setResizable(false);
jf.setVisible(true);
bt1 = new JButton();
bt1.setBounds(10, 10, 150, 150);
bt1.setIcon(ic1);
bt2 = new JButton();
bt2.setBounds(160, 10, 150, 150);
bt2.setIcon(ic2);
bt3 = new JButton();
bt3.setBounds(310, 10, 150, 150);
bt3.setIcon(ic3);
bt4 = new JButton();
bt4.setBounds(10, 160, 150, 150);
bt4.setIcon(ic4);
bt5 = new JButton();
bt5.setBounds(160, 160, 150, 150);
bt5.setIcon(ic5);
bt6 = new JButton();
bt6.setBounds(310, 160, 150, 150);
bt6.setIcon(ic6);
bt7 = new JButton();
bt7.setBounds(10, 310, 150, 150);
bt7.setIcon(ic7);
bt8 = new JButton();
bt8.setBounds(160, 310, 150, 150);
bt8.setIcon(ic8);
bt9 = new JButton();
bt9.setBounds(310, 310, 150, 150);
bt9.setIcon(icSpace);
jf.add(bt1);
jf.add(bt2);
jf.add(bt3);
jf.add(bt4);
jf.add(bt5);
jf.add(bt6);
jf.add(bt7);
jf.add(bt8);
jf.add(bt9);
}
public static void main(String[] args){
PuzzleGame.createMyGUI();
}
}
I think method setIcon is not apply for Button. Besides, someone show me how to set a action to arrange mess picture into a complete picture in Puzzle Game with my code.
I see some issues in your code:
You're extending JFrame and creating a new JFrame object inside your class. You're never using the JFrame of your class (the extended one). So it's wise to just remove it.
You should avoid extending JFrame because that means that your class is a JFrame, JFrame is a rigid container, instead make your programs based on JPanels and add them to other Containers. For reference see: Using extends JFrame vs calling it inside of class.
You're over using the static keyword. static is not a cross method passing word, it will harm you a lot, stop using it. Instead create instances of your class and call your methods that way.
You have multiple objects that do the same:
Icon ic1 = new ImageIcon("images/1.png");
Icon ic2 = new ImageIcon("images/2.png");
...
Why not have an Icon[] icons and iterate over it?
You're using the evil null layout and setBounds(...), stop using it and instead make use of the layout managers along with EmptyBorders for extra spacing between components.
While pixel perfect positioning might be like the easiest way to Swing newbies to create complex GUIs, the more you use it, the more problems you'll find regarding this. Swing has to deal with different platforms, screen sizes, resolutions, PLAFs, etc. That's why pixel perfect GUIs are just an illusion. For reference see Null layout is evil and the answers in this question for a further explanation on why you should avoid null layout.
You're making your JFrame visible before you have added all your components, this could cause your GUI to not be painted fully before it's shown and could cause a "bug" that the components don't display until you hover where they should be. JFrame#setVisible(...) should be one of the last lines to be called.
You're calling JFrame#setSize(...), you should instead override the getPreferredSize of your inner JPanels and then call JFrame#pack(), so your JFrame reduces its size to the minimum size where all your components are visible on their preferred sizes. See Should I avoid the use of setPreferred|Maximum|MinimumSize in Java Swing? (The general consensus says "yes").
You're not placing your program on the Event Dispatch Thread (EDT), Swing is not Thread safe and this could make your program to freeze sometimes, you can solve this by changing your main(...) method like this one:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
//Your constructor here
}
});
}
Your images probably aren't located, but they'll become embedded resources once you package your program as a JAR file, and thus, it's wise to start treating the files (or images) as if they already were.
You can change for example:
Icon ic1 = new ImageIcon("images/1.png");
To this:
Icon ic1 = null;
try {
ic1 = new ImageIcon(ImageIO.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("images/1.png")));
} catch (IOException e) {
e.printStackTrace();
}
That will make your image to be loaded. See this question and the accepted answer for reference.
That should solve your question, and your GUI (I did it with 2 icons) shoudl look like this one:
But if you want to follow my above recommendations you could try this code, which uses layout managers, empty borders, overrides getPreferredSize() methods and uses pack(), etc and generates a really similar GUI like the one you already have:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ImagesInResourcesExample {
private JFrame frame;
private JPanel buttonsPane;
private JPanel rightPane;
private JPanel scorePanel;
private JPanel colorPanel;
private BufferedImage img;
private JButton[][] buttons;
private JLabel moveLabel;
private JLabel timeLabel;
private JButton newGameButton;
private JButton exitButton;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ImagesInResourcesExample().createAndShowGui();
}
});
}
#SuppressWarnings("serial")
public void createAndShowGui() {
frame = new JFrame(getClass().getSimpleName());
buttons = new JButton[3][3];
moveLabel = new JLabel("Move: 0");
timeLabel = new JLabel("Time: 0");
colorPanel = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
};
colorPanel.setBackground(Color.BLUE);
colorPanel.setOpaque(true);
newGameButton = new JButton("New Game");
exitButton = new JButton("Exit");
try {
img = ImageIO.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("images/arrow.png"));
} catch (IOException e) {
e.printStackTrace();
}
buttonsPane = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
};
buttonsPane.setLayout(new GridLayout(3, 3));
buttonsPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
for (int i = 0; i < buttons.length; i++) {
for (int j = 0; j < buttons.length; j++) {
buttons[i][j] = new JButton(new ImageIcon(img));
buttonsPane.add(buttons[i][j]);
}
}
rightPane = new JPanel();
rightPane.setLayout(new GridBagLayout());
scorePanel = new JPanel();
scorePanel.setLayout(new GridLayout(1, 2, 10, 10));
scorePanel.add(moveLabel);
scorePanel.add(timeLabel);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.BOTH;
gbc.insets = new Insets(10, 10, 10, 10);
rightPane.add(scorePanel, gbc);
gbc.gridx = 0;
gbc.gridy = 1;
rightPane.add(colorPanel, gbc);
gbc.gridy = 2;
gbc.ipadx = 30;
gbc.ipady = 80;
rightPane.add(newGameButton, gbc);
gbc.gridy = 3;
rightPane.add(exitButton, gbc);
rightPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
frame.add(buttonsPane, BorderLayout.CENTER);
frame.add(rightPane, BorderLayout.EAST);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
As you can see, the code is at most 20 lines longer than the one you already have, but if you keep adding elements to both programs, the one I did will, in a future, be shorter than the one you'll end up by using null layout.
I hope you follow the above recommendations and take this example and try to understand it and improve it.
I'm coding a GUI by hand and I've run into difficulty positioning a JLabel on a JPanel. I'm trying to put it in the top left hand side above the JTextField but it's defaulting to the middle even though I'm settings the bounds:
Relevant code:
JPanel mainPanel = new JPanel();
JLabel myFleetLabel = new JLabel("My Fleet");
myFleetLabel.setBounds(1,1, 10, 10);
mainPanel.add(myFleetLabel);
add(mainPanel);
Here's what it looks like:
There are a few ways you might be able achieve this, one might be to use a GridBagLayout as the primary layout manager, for example
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
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 GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.NORTHWEST;
JLabel label = new JLabel("My Fleet: ");
add(label, gbc);
JTextArea ta = new JTextArea(10, 20);
gbc.gridx++;
add(new JScrollPane(ta), gbc);
gbc.gridx = 0;
gbc.gridy++;
gbc.gridwidth = 2;
gbc.fill = GridBagConstraints.HORIZONTAL;
add(new JScrollPane(new JTextArea(5, 10)), gbc);
JPanel actions = new JPanel();
actions.add(new JButton("Create Ship"));
actions.add(new JButton("Flip Coins"));
gbc.gridy++;
add(actions, gbc);
}
}
}
See Laying Out Components Within a Container and How to Use GridBagLayout for more details.
Remember, it's unlikely that a single layout manager will solve all your problems and some times you will need to use two or more to accomplish the overall effect
Typically you need to use layouts to place objects in a containter. You should get acquainted with layouts to really code properly in swing.
The being said, the reason your code isn't working as is, is because containers have a layout by default. You CAN remove the layout as follows
mainPanel.setLayout(null);
but this is very bad practice and should be avoided always.
I've been having trouble with my Java Swing GUI. First of all, I created a panel with the GridBagLayout on it and added all my labels to it. However, I also created a panel to the right of the other JPanel that adds in a button and 2 sliders which are suppose to like match with the labels.
The problem is that the JLabels are smaller than the components from the right of the other panel which, makes it look like this....
ex. water option -- JSLIDER (the jslider looks a lot bigger)
I tried to make the components larger by adding ipadx to a bigger value and also I've tried to set the grid width for the labels panel bigger, but nothing seems to work. It just doesn't respond.
Here is the code:
package gui;
import java.awt.GridBagConstraints;
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.JSlider;
public class Options extends JFrame{
//water option (button changes text on or off)
//make a button listener
JSlider renderDistance;
JSlider grassDensity;
JButton waterToggleButton;
JLabel rdTitle;
JLabel gdTitle;
JLabel wtTitle;
JButton buttonClose;
static final int RD_MIN_VALUE = 0;
static final int RD_MAX_VALUE = 1000;
static final int RD_INIT_VALUE = 500;
static final int GD_MIN_VALUE = 0;
static final int GD_MAX_VALUE = 1000;
static final int GD_INIT_VALUE = 500;
public Options() {
this.setTitle("Settings");
this.setSize(getMaximumSize());
this.setLocationRelativeTo(null);
createView();
this.setVisible(true);
}
private void createView() {
//Making panels and adding them to window*
JPanel pOptions = new JPanel();
this.add(pOptions);
//These are for the labels that I added so people know which option they are using
JPanel pOptionLabels = new JPanel(new GridBagLayout());
pOptions.add(pOptionLabels);
//These are for the middle columns, the objects like button and slider
JPanel pOptionObjects = new JPanel(new GridBagLayout());
pOptions.add(pOptionObjects);
//Making panels and adding them to window*
//Initializing Objects*
GridBagConstraints gbcLabels = new GridBagConstraints();
GridBagConstraints gbcObjects = new GridBagConstraints();
renderDistance = new JSlider(RD_MIN_VALUE, RD_MAX_VALUE, RD_INIT_VALUE);
grassDensity = new JSlider(GD_MIN_VALUE, GD_MAX_VALUE, GD_INIT_VALUE);
waterToggleButton = new JButton("On");
rdTitle = new JLabel("Render Distance");
gdTitle = new JLabel("Grass Density");
wtTitle = new JLabel("Water Terrain Visibility");
//Initializing Objects*
//Giving objects some attributes using methods*
renderDistance.setMinorTickSpacing(100);
renderDistance.setMajorTickSpacing(500);
renderDistance.setPaintTicks(true);
renderDistance.setPaintLabels(true);
grassDensity.setMinorTickSpacing(100);
grassDensity.setMajorTickSpacing(500);
grassDensity.setPaintTicks(true);
grassDensity.setPaintLabels(true);
gbcLabels.gridx = 0;
gbcLabels.gridy = 0;
gbcLabels.anchor = GridBagConstraints.LINE_START;
gbcObjects.gridx = 0;
gbcObjects.gridy = 0;
waterToggleButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(waterToggleButton.getText().equals("Off")) {
waterToggleButton.setText("On");
}else {
waterToggleButton.setText("Off");
}
}
});
//Giving objects some attributes using methods*
//Add things to panel ex. p.add();
pOptionLabels.add(rdTitle, gbcLabels);
gbcLabels.gridy++;
pOptionLabels.add(gdTitle, gbcLabels);
gbcLabels.gridy++;
pOptionLabels.add(wtTitle, gbcLabels);
gbcObjects.gridx++;
pOptionObjects.add(renderDistance, gbcObjects);
gbcObjects.gridy++;
pOptionObjects.add(grassDensity, gbcObjects);
gbcObjects.gridy++;
pOptionObjects.add(waterToggleButton, gbcObjects);
}
public static void main(String[] args) {
new Options();
}
}
Change the way you are thinking. Instead of trying to layout all the labels and all the "other" components in separate containers, consider putting them all in the same container.
This way, when they are laid out, the layout calculations are made in relationship to all the components in a single context.
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
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.JSlider;
public class Options extends JFrame {
//water option (button changes text on or off)
//make a button listener
JSlider renderDistance;
JSlider grassDensity;
JButton waterToggleButton;
JLabel rdTitle;
JLabel gdTitle;
JLabel wtTitle;
JButton buttonClose;
static final int RD_MIN_VALUE = 0;
static final int RD_MAX_VALUE = 1000;
static final int RD_INIT_VALUE = 500;
static final int GD_MIN_VALUE = 0;
static final int GD_MAX_VALUE = 1000;
static final int GD_INIT_VALUE = 500;
public Options() {
this.setTitle("Settings");
createView();
pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
}
private void createView() {
//Making panels and adding them to window*
JPanel pOptions = new JPanel();
this.add(pOptions);
//These are for the labels that I added so people know which option they are using
JPanel pOptionLabels = new JPanel(new GridBagLayout());
pOptions.add(pOptionLabels);
GridBagConstraints gbc = new GridBagConstraints();
renderDistance = new JSlider(RD_MIN_VALUE, RD_MAX_VALUE, RD_INIT_VALUE);
grassDensity = new JSlider(GD_MIN_VALUE, GD_MAX_VALUE, GD_INIT_VALUE);
waterToggleButton = new JButton("On");
rdTitle = new JLabel("Render Distance");
gdTitle = new JLabel("Grass Density");
wtTitle = new JLabel("Water Terrain Visibility");
//Initializing Objects*
//Giving objects some attributes using methods*
renderDistance.setMinorTickSpacing(100);
renderDistance.setMajorTickSpacing(500);
renderDistance.setPaintTicks(true);
renderDistance.setPaintLabels(true);
grassDensity.setMinorTickSpacing(100);
grassDensity.setMajorTickSpacing(500);
grassDensity.setPaintTicks(true);
grassDensity.setPaintLabels(true);
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.LINE_START;
gbc.insets = new Insets(2, 2, 2, 2);
waterToggleButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (waterToggleButton.getText().equals("Off")) {
waterToggleButton.setText("On");
} else {
waterToggleButton.setText("Off");
}
}
});
//Giving objects some attributes using methods*
//Add things to panel ex. p.add();
pOptionLabels.add(rdTitle, gbc);
gbc.gridy++;
pOptionLabels.add(gdTitle, gbc);
gbc.gridy++;
pOptionLabels.add(wtTitle, gbc);
gbc.gridx++;
gbc.gridy = 0;
pOptionLabels.add(renderDistance, gbc);
gbc.gridy++;
pOptionLabels.add(grassDensity, gbc);
gbc.gridy++;
pOptionLabels.add(waterToggleButton, gbc);
}
public static void main(String[] args) {
new Options();
}
}
What you are experiencing is technically correct since you have two JPanels with two separate instances of the GridBagLayout manager so the rows (and columns) in one layout manager have no information about the rows in the other.
A single GridBagLayout is the way to go if you plan to use JComponents inside them and create a table like appearance. You can later add others JPanels (with different layout managers or with more GridBagLayouts!) but INSIDE your main GridBagLayout JPanel.
GridBagLayout is a really powerful layout, I would recommend sticking with is and master it since it will help you layout things nicely (and fast) in the long run.
This is a great resource to learn more about the GridBagLayout:
https://docs.oracle.com/javase/tutorial/uiswing/layout/gridbag.html
One small comment: IMO the GridBagLayout is not the best layout manager to use as the "root" layout container. I would instead recommend creating a "root" JPanel with a BorderLayout() and add your GridBagLayout JPanel to the center of the BorderLayout panel add(yourGridPanel, BorderLayout.CENTER) since the BorderLayout usually gives for free the ability for things to fill horizontally/vertically and will let you pack your window nicely at the center (with the possibility of adding more things later to the NORTH/SOUTH/EAST/WEST sides).
Good luck!
I'm trying to create this basic GUI, but cannot get my panels to setup correctly.(Numbers are pixel sizes)
I've tried using this tutorial as a reference (http://www.youtube.com/watch?v=Kl3klve_rmQ) but, mine never works the same.
My code declares variables in the top of the class, then creates a constructor which add the components (panels, buttons, etc), then it calls the constructor in the main method.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class FinalProject extends JPanel
{
private static final long serialVersionUID = 1L;
static JPanel nav;
static JPanel queue;
static JPanel menu;
GridBagConstraints gbc = new GridBagConstraints();
public FinalProject()
{
nav = new JPanel();
nav.setLayout(new GridBagLayout());
nav.setBackground(Color.RED);
gbc.gridy = 0;
gbc.gridx = 0;
gbc.gridheight = 1;
gbc.gridwidth = 1;
add(nav, gbc);
queue = new JPanel();
queue.setLayout(new GridBagLayout());
queue.setBackground(Color.GREEN);
gbc.gridy = 1;
gbc.gridx = 1;
gbc.gridheight = 1;
gbc.gridwidth = 1;
add(queue, gbc);
menu = new JPanel();
menu.setLayout(new GridBagLayout());
menu.setBackground(Color.BLUE);
gbc.gridy = 2;
gbc.gridx = 2;
gbc.gridheight = 1;
gbc.gridwidth = 1;
add(menu, gbc);
}
public static void main(String[] args)
{
FinalProject p = new FinalProject();
JFrame f = new JFrame();
f.add(nav);
f.add(queue);
f.add(menu);
f.setTitle("Subway");
f.setSize(800, 500);
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
f.setResizable(false);
f.add(p);
}
}
How should I go about getting this layout right? Panels in Panels, Panels independent of each other, etc.?
No, don't use a GridBagLayout for this as you'll be adding more complexity than is actually needed. Myself, I try to avoid using this layout and all its potential pitfalls as much as possible, and usually you can get all you need by nesting JPanels, each using its own more simple layout. For instance here, all you need is a BorderLayout:
Place the top JPanel in the BorderLayout.NORTH position
Place the left JPanel in the BorderLayout.WEST position
Place the center JPanel in the BorderLayout.CENTER position.
That's it.
Again, please check out the Swing and the layout manager tutorials as the information is well presented there.
Edit
Note that nothing shows up on your JFrame because you're not adding your JPanel to the JFrame!
Edit 2
For example:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import javax.swing.*;
public class SimpleLayout {
private static final Color GREEN = new Color(200, 255, 200);
private static final Color BLUE = new Color(200, 200, 255);
private static void createAndShowGui() {
JFrame frame = new JFrame("SimpleLayout");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// note that a JFrame's contentPane uses BorderLayout by default
frame.getContentPane().add(new ColorPanel(Color.pink, 800, 80), BorderLayout.NORTH);
frame.getContentPane().add(new ColorPanel(GREEN, 300, 420), BorderLayout.WEST);
frame.getContentPane().add(new ColorPanel(BLUE, 500, 420), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class ColorPanel extends JPanel {
private static final float FONT_POINTS = 24f;
private int prefW;
private int prefH;
public ColorPanel(Color color, int prefW, int prefH) {
setBackground(color);
this.prefW = prefW;
this.prefH = prefH;
// GBL can be useful for simply centering components
setLayout(new GridBagLayout());
String text = String.format("%d x %d", prefW, prefH);
JLabel label = new JLabel(text, SwingConstants.CENTER);
label.setFont(label.getFont().deriveFont(FONT_POINTS));
label.setForeground(Color.gray);
add(label);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(prefW, prefH);
}
}
This displays as:
GridLayout I've never actually used but I'm not sure if it can do this.
BorderLayout can do this and is simpler to use than GridBagLayout.
GridBagLayout can do this.
In general GridBagLayout is one of the most flexible LayoutManagers and is well worth learning so I'd recommend using it here to get used to it on a simple case before you need it anyway for something more complex.
If you are familiar with HTML then think of GridBagLayout as working like HTML tables.
Very quickly - create the three panels and set the sizes/borders/whatever you need in them.
Add one in cell 0,0 with a colspan of 2.
Add one in cell 1,0
Add one in cell 1,1
After that you are done although you will probably want to specify resize/anchor/etc behaviour.