I built a great GUI using the frowned upon null layout (I defined a lot of constants and used a window resize listener to make it easy). Everything worked perfectly until I started using a new computer. Now, the component's are not positioned properly (from the picture you can see that the components are offset down and right). After researching the problem I learned that layout managers make sure that the components are positioned properly throughout different machines. Because of this, I would like to start rebuilding the GUI in an actual layout manager. The problem is that I often feel limited in the way I position components when attempting to use an actual layout manager.
For anyone who is curious, I was originally using a dell inspiron laptop with windows 10, and have moved to an Asus Laptop (I don't know the actual model, but the touch screen can detach from the keyboard), also with windows 10.
My question:
Which layout manager would be the fastest and easiest to build the GUI shown in the picture above (out of the stock Swing Layouts and others). I would like this layout to respect the components' actual sizes for only a few but not all of the components. Using this layout, how would I go about positioning the inventory button (the hammer at the bottom left) so that the bottom left corner of the inventory button is 5 pixels up and right from the bottom left corner of the container, even after resizing the container?
Thanks in advance. All help is appreciated.
EDIT: The "go find a key" and "Attempt to force the door open" options should have their sizes respected.
The simplest solution that comes to my mind is a BorderLayout for the main panel. Add the textarea to NORTH / PAGE_START. Make another BorderLayout containing the inventory button (WEST / LINE_START) and the location label (EAST / LINE_END). Add that to SOUTH / PAGE_END of the main BorderLayout. Then just add a BoxLayout with vertical alignment to the main BorderLayout's CENTER containing the two buttons. Here's a tutorial for the standard layout managers.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class Example {
public Example() {
JTextArea textArea = new JTextArea("There is a locked door");
textArea.setRows(5);
textArea.setBorder(BorderFactory.createLineBorder(Color.GRAY));
textArea.setEditable(false);
WhiteButton button1 = new WhiteButton("Go find a key") {
#Override
public Dimension getMinimumSize() {
return new Dimension(200, 25);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 25);
}
#Override
public Dimension getMaximumSize() {
return new Dimension(200, 25);
}
};
WhiteButton button2 = new WhiteButton("Attempt to force the door open");
button2.setMargin(new Insets(0, 60, 0, 60));
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS));
buttonPanel.add(button1);
buttonPanel.add(Box.createVerticalStrut(5));
buttonPanel.add(button2);
WhiteButton inventoryButton = new WhiteButton(
new ImageIcon(new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB)));
JLabel locationLabel = new JLabel("Location: 0");
locationLabel.setVerticalAlignment(JLabel.BOTTOM);
JPanel southPanel = new JPanel(new BorderLayout());
southPanel.add(inventoryButton, BorderLayout.WEST);
southPanel.add(locationLabel, BorderLayout.EAST);
JPanel mainPanel = new JPanel(new BorderLayout(0, 5));
mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
mainPanel.add(textArea, BorderLayout.NORTH);
mainPanel.add(buttonPanel);
mainPanel.add(southPanel, BorderLayout.SOUTH);
JFrame frame = new JFrame("Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Example();
}
});
}
private class WhiteButton extends JButton {
public WhiteButton() {
setBackground(Color.WHITE);
}
public WhiteButton(String text) {
this();
setText(text);
}
public WhiteButton(ImageIcon icon) {
this();
setIcon(icon);
setBorder(BorderFactory.createLineBorder(Color.GRAY));
}
}
}
Related
I'm trying to implement a feature that (in my test project) once a button is pressed, it adds a random number to my JPanel. (I use the layouts I have because in my real program, I have more items inside and it displays correctly). But I need my program to recognize when the scrollbar is visible (which I implemented that, but it's a little delay. What I mean by delay is I push the button to add a number, if the scrollbar becomes visible nothing happens. But then the next time I press the button it shifts over like I want). The other problem I have (the one I'm focused on now) is that when I dynamically change the size of the JPanel, if the scrollbar is visible, I have it set to change the width to my width - the width of the scrollbar. But It seems like when the scrollbar is visible, the newly inputted number moves over twice the scrollbar width instead of just once. I've been at this part of my program for over a day and can't figure it out. I'll add my full code and some screenshots.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.BorderFactory;
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.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Main {
JFrame frame;
JPanel topPanel;
JPanel memoryPanel;
JScrollPane sPane;
JButton button;
ArrayList<Integer> list = new ArrayList<>();
boolean isVScrollVisible = false;
int scrollBarSize = 0;
public class MyChangeListener implements ChangeListener {
#Override
public void stateChanged(ChangeEvent e) {
isVScrollVisible = (sPane.getVerticalScrollBar().isVisible());
}
}
public class ButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
Random random = new Random();
int r = random.nextInt(10);
list.add(r);
int n;
if (isVScrollVisible) {
n = scrollBarSize;
} else {
n = 0;
}
JPanel nextPanel = new JPanel();
nextPanel.setName("" + r);
nextPanel.setForeground(Color.BLACK);
nextPanel.setPreferredSize(new Dimension(200 - n, 55));
nextPanel.setMinimumSize(new Dimension(200 - n, 55));
nextPanel.setMaximumSize(new Dimension(200 - n, 55));
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new BorderLayout());
JLabel label = new JLabel();
label.setText("" + r);
label.setPreferredSize(new Dimension(200 - n, 55));
label.setMinimumSize(new Dimension(200 - n, 55));
label.setMaximumSize(new Dimension(200 - n, 55));
label.setHorizontalAlignment(JLabel.RIGHT);
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 17));
label.setFont(new Font("Sans-Serif", Font.BOLD, 20));
labelPanel.add(label);
nextPanel.add(labelPanel, BorderLayout.LINE_START);
for (int i = 0; i < memoryPanel.getComponents().length; i++) {
memoryPanel.getComponent(i).setPreferredSize(new Dimension(200 - n, 55));
memoryPanel.getComponent(i).revalidate();
memoryPanel.getComponent(i).repaint();
}
memoryPanel.add(nextPanel, 0);
memoryPanel.revalidate();
memoryPanel.repaint();
sPane.revalidate();
sPane.repaint();
}
}
public Main() {
frame = new JFrame();
topPanel = new JPanel();
memoryPanel = new JPanel();
memoryPanel.setLayout(new BoxLayout(memoryPanel, BoxLayout.Y_AXIS));
sPane = new JScrollPane(memoryPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
sPane.setPreferredSize(new Dimension(200, 300));
sPane.getViewport().addChangeListener(new MyChangeListener());
scrollBarSize = ((Integer)UIManager.get("ScrollBar.width")) + 1;
button = new JButton("Add Random Number");
button.addActionListener(new ButtonListener());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
topPanel.add(button);
frame.add(topPanel, BorderLayout.PAGE_START);
frame.add(sPane, BorderLayout.CENTER);
frame.setResizable(false);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
new Main();
}
}
I need them to look exactly the same. Before I had the code I have now, the scrollbar would appear over the numbers which looked ugly. And the reason I have the frame resizable false is because In my real program I hard coded all the sizes, which in the future I will calculate the correct sizes based on the size of the frame, so right now setting resizable to true is out of the question. Any suggestions on what to do?
This is what I'm trying to accompolish.
Get rid of all the logic that sets the preferred/minimum/maximum sizes. Each component knows what its size should be. Each layout manager will in turn know what the preferred size of the panel should be. Let the layout manager use the information to do its job.
The basic logic for dynamically adding components is:
panel.add(...);
panel.revalidate();
panel.repaint();
Then the scrollbars will appear automatically when required. There is no need for listeners or anything.
Edit:
The reason I set all the sizes is because If I take them out then everything appears centered
Learn how to use layout managers properly and effectively.
For example when using a BoxLayout you can control the alignment of components by using:
component.setAlignmentX(JLabel.RIGHT_ALIGNMENT);
and the component will be aligned to the right edge of the space available to the component.
When using a JLabel you may also need to set a property on the JLabel to align the text to the right edge of the label. Read the JLabel API for the appropriate method.
I have a problem with swing that I cannot resolve since a whole day.
I want to show a popup in JWindow if someone types into a JTextField.
But if the layout uses a JGoodies FormLayout with more components in one row then the display is going to be corrupt.
Do you have any ideas?
Screenshot after typing some letters into the second text field:
After editing Jans code and typing "a" sowly three times:
Code in Java:
package eu.eyan;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JWindow;
import com.jgoodies.forms.factories.CC;
import com.jgoodies.forms.layout.FormLayout;
public class Snippet {
public static void main(String[] args) {
JTextField tf = new JTextField(10);
JPanel panel = new JPanel();
panel.setLayout(new FormLayout("p", "p,p,p"));
panel.add(new JTextField("before"), CC.xy(1, 1));
panel.add(tf, CC.xy(1, 2));
panel.add(new JTextField("after"), CC.xy(1, 3));
JFrame frame = new JFrame();
frame.setLayout(new FormLayout("p,p,p", "p"));
frame.add(new JLabel("bef"), CC.xy(1, 1));
frame.add(panel, CC.xy(2, 1));
frame.add(new JLabel("aft"), CC.xy(3, 1));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.pack();
JWindow popup = new JWindow();
popup.setLayout(new FormLayout("p", "p"));
JLabel l = new JLabel("popup");
popup.add(l, CC.xy(1, 1));
popup.pack();
tf.addKeyListener(new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
popup.setVisible(true);
}
});
}
}
The best way how to get help with a layout is providing a sketch of
your UI, either as picture or as ASCII. Here I am only guessing what
you had in mind.
First, FormLayout is a good layout; it creates layouts that are portable. Unlike old layouts, like BoxLayout or GridBagLayout, it uses dialog units instead of pixels. This way a portable layout to various screen sizes can be created. Actually, FormLayout was the first Java layout manager that enabled to create truly portable UIs. Other two that can do it are MigLayout and GroupLayout.
If you can, try using MigLayout instead. MigLayout was inspired by
FormLayout and it significantly improved it. For instance, in MigLayout you use set the gaps once, whereas in FormLayout, you have
to tediously take the gaps into account when doing your layout.
Corrections:
1) Call the pack() method before the setVisible() method.
2) Don't use unnecessary panels to create the layout. You probably saw some examples where panels were used to create the layout. This was because managers like BoxLayout were so simplistic that we needed
them. With FormLayout and MigLayout, this is not necessary.
3) You also need to add gaps to your layout in dialogs units.
Here is an example of what I thought you might want to achieve:
package com.zetcode.formlayoutex;
import com.jgoodies.forms.factories.CC;
import com.jgoodies.forms.layout.FormLayout;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JWindow;
public class FormLayoutEx {
public static void main(String[] args) {
JTextField tf = new JTextField(10);
JFrame frame = new JFrame();
frame.setLayout(new FormLayout("6dlu, p, 5dlu, p:g, 3dlu",
"6dlu, p, 4dlu, p, 4dlu, p, 6dlu"));
frame.add(new JLabel("Before"), CC.xy(2, 2));
frame.add(new JTextField("before"), CC.xy(4, 2));
frame.add(tf, CC.xywh(2, 4, 3, 1));
frame.add(new JLabel("After"), CC.xy(2, 6));
frame.add(new JTextField("after"), CC.xy(4, 6));
frame.pack();
frame.setTitle("FormLayout example");
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
JWindow popup = new JWindow();
popup.setLayout(new FormLayout("p", "p"));
JLabel l = new JLabel("popup");
popup.add(l, CC.xy(1, 1));
popup.pack();
tf.addKeyListener(new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
popup.setVisible(true);
}
});
}
}
Screenshot:
I'm trying to align a JLabel to the right in a JPanel. I'm adding a JTabbedPane, a JPanel which contains my JLabel and JTextArea to a main JPanel.
I have searched SO and tried some methods like setAlignmentX, setHorizontalAlignment(SwingConstants.LEFT) and nested containers to no avail.
Here's my code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class LabelProblem
{
public static void main(String[] args)
{
JFrame frame = new JFrame("Label Problem");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel Main = new JPanel();
Main.setLayout(new BoxLayout(Main, BoxLayout.Y_AXIS));
JPanel ComponentPanel = new JPanel();
JLabel label = new JLabel("Sample Text");
label.setHorizontalAlignment(SwingConstants.LEFT);
label.setBorder(BorderFactory.createLineBorder(Color.BLACK));
label.setAlignmentX(Component.RIGHT_ALIGNMENT);
ComponentPanel.add(label);
JTabbedPane Tab = new JTabbedPane();
Tab.add("Document 1", new JPanel());
Main.add(Tab);
Main.add(ComponentPanel);
JTextArea Area = new JTextArea(10,10);
JScrollPane Scroll = new JScrollPane(Area);
frame.add(Main);
frame.add(Scroll, BorderLayout.SOUTH);
frame.setSize(450,450);
frame.setVisible(true);
}
}
How can I align my JLabel to the right?
Thanks!
So, the place of that label is determined by the layout of ComponentPanel. Since you didn't specify any layout it is using the default FlowLayout with a CENTER alignment. Assuming that you are ok with a FlowLayout it is a mere question of setting the alignment of the LEFT since this is possible with this layout.
Here's the code with the fix, however I suspect that as you put more elements to the ComponentPanel you will want to use another layout since FlowLayout is more adequate for menus and the like and not for displaying the main content.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
class LabelProblem
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
initGUI();
}
});
}
public static void initGUI()
{
JFrame frame = new JFrame("Label Problem");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel main = new JPanel();
main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS));
JPanel componentPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JLabel label = new JLabel("Sample Text");
label.setBorder(BorderFactory.createLineBorder(Color.BLACK));
componentPanel.add(label);
JTabbedPane Tab = new JTabbedPane();
Tab.add("Document 1", new JPanel());
main.add(Tab);
main.add(componentPanel);
JTextArea area = new JTextArea(10, 10);
JScrollPane scroll = new JScrollPane(area);
frame.add(main);
frame.add(scroll, BorderLayout.SOUTH);
frame.setSize(450, 450);
frame.setVisible(true);
}
}
Result:
Note: I also changed the variable names to follow the java style convention: variable names should start with lower case to differenciate them from clases names, starting in upper case.
One simple approach is to set the label's horizontalAlignment to JLabel.RIGHT in the constructor.
import java.awt.*;
import javax.swing.*;
class LabelProblem {
public static void main(String[] args) {
JFrame frame = new JFrame("Label Problem");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(0, 1));
JTabbedPane tab = new JTabbedPane();
tab.add("Document 1", new JPanel());
frame.add(tab);
JLabel label = new JLabel("Sample Text", JLabel.RIGHT);
frame.add(label);
JTextArea area = new JTextArea(10, 10);
JScrollPane scroll = new JScrollPane(area);
frame.add(scroll);
frame.pack();
frame.setSize(450, 450);
frame.setVisible(true);
}
}
I think it may be a matter of you not actually setting layouts where you imagine you're setting layouts.
You have a JPanel with a vertically oriented BoxLayout (Main) enclosing another JPanel with default layout (ComponentPanel), finally enclosing your label. The reason why your label can't be pushed to the right is because is already is pushed to the right within it's enclosing container. If you set a colored border around ComponentPanel, you'll see what I mean -- it only occupies the same amount of space as the JLabel, giving the JLabel nowhere to move.
You need to set a layout and constraints for your intermediate ComponentPanel, allowing it to horizontally fill its parent container so that the label has someplace to go.
You haven't really specified how your layout is supposed to look, but if you change the layout on Main to X_AXIS, your label will pop over to the left (as will its parent container). Without knowing what you're really trying to do, I can't say much more.
I would however, suggest you throw your BoxLayout away entirely and look into using GridBagLayout, which gives you a high level control over your UI. GridBagLayout isn't the most concise construct, but that's the price of control.
I am creating a program which has three jpanels: one container, and inside the container is two jpanels, one that is going to hold buttons and one which will hold the content. Ive got them both showing so far and its looking good, the only problem i I was hoping to add some space or a border between or the two (or around the button menu if possible) however since both internal panels are set to null layouts and the external layout is set to a border layout I cannot seem to add a border between the two internal ones. Here is my code so far:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class internal_test extends JFrame {
int height = 480;
int width = 640;
public internal_test() {
initUI();
}
private void initUI() {
JPanel container = new JPanel();
container.setLayout(new BorderLayout());
container.setBackground(Color.black);
JPanel buttonMenu = new JPanel();
buttonMenu.setLayout(null);
buttonMenu.setBackground(Color.DARK_GRAY);
buttonMenu.setPreferredSize(new Dimension(150, height));
JPanel dragFrame = new JPanel();
dragFrame.setLayout(null);
dragFrame.setPreferredSize(new Dimension(200, 100));
dragFrame.setSize(new Dimension(490, height));
dragFrame.setBackground(Color.gray);
container.add(buttonMenu, BorderLayout.WEST);
container.add(dragFrame, BorderLayout.CENTER);
// container.setBorder(new EmptyBorder(new Insets(10, 10, 10, 10)));
add(container);
pack();
setTitle("internal_test V0.1");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(width, height);
setLocationRelativeTo(null);
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
internal_test c = new internal_test();
c.setVisible(true);
}
});
}
}
The dragFrame is going to be a DragLayout since that layout does what I need it to, but the button menu could technically be any layout, as long as it would allow me to place buttons/other items in a list with a label next to each.
Any help is greatly appreciated.
I would use the BoxLayout and for the spacing use
panel.add(Box.createRigidArea(new Dimension(x, y)));
Here are some decent examples.
I Suggest GridBagLayout
beacause it is more easy to intent spaces between the components
This should help you add the type of border you want:
http://docs.oracle.com/javase/tutorial/uiswing/components/border.html
You can start with a red line border like this:
buttonMenu.setBorder(BorderFactory.createLineBorder(Color.red));
I want to arrange components as shown in image. I can do this by using gridbaglayout but I want to do this using borderlayout. I tried it but could not achieve what I wanted. so please guide me here.
The black rectangles here are components like JPanel, Button etc.
If you want to do it only with BorderLayout, you need to use 2 BorderLayout. If you cannot use 2 layouts, then you are stuck with GridBagLayout.
This is a demonstration of what I am telling:
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
protected void initUI() {
JFrame frame = new JFrame("test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new BorderLayout());
JPanel panel2 = new JPanel(new BorderLayout());
panel2.add(new JButton("NORTH"), BorderLayout.NORTH);
panel2.add(new JButton("CENTER"));
panel.add(panel2);
panel.add(new JButton("SOUTH"), BorderLayout.SOUTH);
panel.add(new JButton("EAST"), BorderLayout.EAST);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test().initUI();
}
});
}
}
Border layout doesn't work that way. This is the border layout schematic:
You will not be able to place the EAST layout into the top right-hand corner - NORTH will always float to the right hand side.
Edit: shows how long it's been since I used Swing or AWT - back when I did, it was EAST, NORTH, WEST, SOUTH and CENTER.