In the first tab of the sample program: In the first row the components are r and l justified in their cells. In the second row the cell is spanned and pushed to the right.
All of the excess space in the first row is given to cell 0,0 which contains the component labelled "20".
Tab "Two" improves the situation by using pushx. The widget looked centered but that is not what I want.
But how do I give all the excess space to cell 0, 1 (the cell containing the component labelled "30")? I tried posting an image of what I want to accomplish but the system disallowed that. Hopefully, my verbal explanation is clear enough.
package test1;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
public class Main extends JTabbedPane {
public Main() {
addTab("One", createPanel1());
addTab("Two", createPanel2());
}
public static void main(String[] args) {
final JFrame frame = new JFrame("Test MigLayout");
Main main = new Main();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(main);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
frame.pack();
frame.setVisible(true);
frame.setBounds(200, 200, 800, 500);
}
});
}
private JPanel createPanel1() {
JPanel panel = new JPanel(new MigLayout("hidemode 3, debug", "[r][l]"));
JTextField textField1 = new JTextField("20", 20);
JTextField textField2 = new JTextField("30", 30);
JButton button = new JButton("Button");
panel.add(textField1);
panel.add(textField2, "wrap");
panel.add(button, "span 2, pushx");
return (panel);
}
private JPanel createPanel2() {
JPanel panel = new JPanel(new MigLayout("hidemode 3, debug", "[r][l]"));
JTextField textField1 = new JTextField("20", 20);
JTextField textField2 = new JTextField("30", 30);
JButton button = new JButton("Button");
panel.add(textField1);
panel.add(textField2, "pushx, wrap");
panel.add(button, "span 2, pushx");
return (panel);
}
}
Was astonished that any cell was growing horizontally, because the default is not to. Until re-reading the doc for push:
Makes the row and/or column that the component is residing in grow
with "weight". This can be used instead of having a "grow" keyword in
the column/row constraints
Using it in the spanning cell of the button effectively applies a grow constraint to the first column of the span. Without knowing your exact goal, it's hard to advise anything except saying: don't :-) One option might be to explicitly grow the second column:
JPanel panel = new JPanel(new MigLayout("hidemode 3, debug, wrap 2", "[r][l, grow]"));
JTextField textField1 = new JTextField("20", 20);
JTextField textField2 = new JTextField("30", 30);
JButton button = new JButton("Button");
panel.add(textField1);
panel.add(textField2);
panel.add(button, "span");
As an aside: I always recommend to define constraints as high up in the hierarchy (layout > row/column > component) as possible - following DRY :-)
Related
In the class that extends JFrame, I have the following code:
public Frame() {
setTitle("WORDLE");
// creates all containers
Container mainContainer = getContentPane();
JPanel gridContainer = new JPanel();
JPanel optionsContainer = new JPanel();
// sets size of main frame
setSize(WIDTH, 900);
// sets background size and background colors of all containers
gridContainer.setBackground(GameTheme.BACKGROUND); // TODO: add message label inside of gridContainer below grid JPanel
gridContainer.setPreferredSize(new Dimension(WIDTH - 150, 500));
gridContainer.setMaximumSize(new Dimension(WIDTH - 150, 500));
gridContainer.setLayout(new BorderLayout(10, 0));
optionsContainer.setBackground(GameTheme.BACKGROUND);
optionsContainer.setPreferredSize(new Dimension(WIDTH, 100));
// creates grid with boxes for letter
grid = new JPanel();
grid.setLayout(new GridLayout(6, 5, 5, 5));
grid.setPreferredSize(new Dimension(WIDTH - 150, 400));
grid.setMaximumSize(new Dimension(WIDTH - 150, 400));
grid.setBackground(GameTheme.BACKGROUND);
createBoxes();
// JLabel for messages
JLabel alertLabel = new JLabel("test");
alertLabel.setOpaque(true);
alertLabel.setForeground(GameTheme.WHITE);
alertLabel.setBackground(GameTheme.BACKGROUND);
// listens for presses in the JFrame, with the Game class implementing the method for the keypress event
addKeyListener(new Game(boxes));
// lays out all containers and their sub-containers
// title label at top, grid container at center, options container at bottom
mainContainer.setLayout(new BorderLayout());
mainContainer.add(createTitle(), BorderLayout.NORTH); // createTitle returns a JLabel
mainContainer.add(gridContainer, BorderLayout.CENTER);
mainContainer.add(optionsContainer, BorderLayout.SOUTH);
gridContainer.add(grid, BorderLayout.NORTH);
gridContainer.add(alertLabel, BorderLayout.SOUTH);
// boilerplate frame configuration
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
setLocationRelativeTo(null);
pack();
setVisible(true);
}
public void createBoxes() {
// initializes 2d array for letter boxes
boxes = new LetterBox[6][5];
// populates 2d array and adds boxes to the grid container
for (int r = 0; r < 6; r++) {
for (int c = 0; c < 5; c++) {
LetterBox letterBox = new LetterBox(r, c);
boxes[r][c] = letterBox;
grid.add(letterBox.getBox());
}
}
}
I have set a maximum size on all of the components within gridContainer, and even on the gridContainer itself, yet the JLabels are still stretching to take up the size of the GUI. Can someone help me figure out why this is happening, and how I can fix it?
To make the problem clear, here are some images that show what I want vs what I am getting:
Expected:
Actual:
I tried to describe as clearly as possible please comment and let me know if there is anything I need to clarify.
Don't keep playing with the preferred/minimum/maximum sizes of parent panels and the frame. Each panel should be able to calculate its preferred size based on the preferred size of the components added to the panel. So you add components to a panel and the panels to the frame, then you pack the frame and everything will be displayed at the proper size.
So in your case you can use a GridLayout for your "gridContainer". Then you add your JLabels to the gridContainer. For your LetterBox component you should use a "monospaced" Font so all characters will take the same space. Then you can initialize your label with a default value of " " to give the label a size.
This size will be too small so you will then also want to add an EmptyBorder to the LetterBox. So in the constructor you can add something like:
setBorder( new EmptyBorder(10, 10, 10, 10) );
Now the LeterBox can calculates its preferred size correctly and the "gridContainer" will be able to calculate its preferred size correctly.
However, when you add the panel the the CENTER of the BorderLayout, the BorderLayout will attempt to resize each component to fill the space available in the frame. To prevent this you can use a "wrapper" panel:
JPanel wrapper = new JPanel(); // uses FlowLayout by default
wrapper.add( gridContainer );
mainContainer.add(wrapper, BorderLayout.CENTER);
//mainContainer.add(gridContainer, BorderLayout.CENTER);
Now if the frame is resized the extra space will go to the wrapper panel NOT the gridContainer.
In case you would like to relax a little the requirement of the frame being unresizable, here follow some implementations which restrict the maximum size of the grid to the preferred, but do not restrict the minimum. As a result in both implementations the grid will not get bigger than its preferred size, making it possible for the user to enlarge the frame and empty spaces to appear by the LayoutManagers used to fill the remaining space.
The minimum size of the grid is not restricted however, just in case the user wants to resize the frame to a smaller size. This is supposed to be an extra feature, although if you want to disable it the easiest way would be to uncomment the frame.setMinimumSize(frame.getSize()); line in each implementation.
Both implementations take into account the sizes of the grid.
BoxLayout implementation:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class BoxMain {
private static void createAndShowGUI() {
final int rows = 6, cols = 15;
final JPanel grid = new JPanel(new GridLayout(rows, 0, 5, 5));
for (int row = 0; row < rows; ++row)
for (int col = 0; col < cols; ++col)
grid.add(new JLabel(Integer.toString(row * cols + col), JLabel.CENTER));
final Dimension sz = grid.getPreferredSize();
grid.setMaximumSize(sz);
grid.setMinimumSize(new Dimension(0, sz.height));
final JPanel box = new JPanel();
box.setLayout(new BoxLayout(box, BoxLayout.X_AXIS));
box.add(Box.createGlue());
box.add(grid);
box.add(Box.createGlue());
final JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(box, BorderLayout.CENTER);
mainPanel.add(new JLabel("Alert label", JLabel.CENTER), BorderLayout.PAGE_END);
final JFrame frame = new JFrame("Wordle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
//frame.setMinimumSize(frame.getSize());
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(BoxMain::createAndShowGUI);
}
}
SpringLayout implementation:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Spring;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;
public class SpringMain {
private static void createAndShowGUI() {
final int rows = 6, cols = 15;
final JPanel grid = new JPanel(new GridLayout(rows, 0, 5, 5));
for (int row = 0; row < rows; ++row)
for (int col = 0; col < cols; ++col)
grid.add(new JLabel(Integer.toString(row * cols + col), JLabel.CENTER));
grid.setMaximumSize(grid.getPreferredSize());
grid.setMinimumSize(new Dimension());
final SpringLayout layout = new SpringLayout();
final JPanel spring = new JPanel(layout);
spring.add(grid);
final Spring flexible = Spring.constant(0, 0, Short.MAX_VALUE);
layout.putConstraint(SpringLayout.WEST, grid, flexible, SpringLayout.WEST, spring);
layout.putConstraint(SpringLayout.EAST, spring, flexible, SpringLayout.EAST, grid);
layout.putConstraint(SpringLayout.NORTH, grid, flexible, SpringLayout.NORTH, spring);
layout.putConstraint(SpringLayout.SOUTH, spring, flexible, SpringLayout.SOUTH, grid);
final JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(spring, BorderLayout.CENTER);
mainPanel.add(new JLabel("Alert label", JLabel.CENTER), BorderLayout.PAGE_END);
final JFrame frame = new JFrame("Wordle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
//frame.setMinimumSize(frame.getSize());
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(SpringMain::createAndShowGUI);
}
}
package swingtraining;
import javax.swing.*;
import java.awt.*;
import static java.awt.Color.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import static javafx.scene.paint.Color.TEAL;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
public class MyFrame extends JFrame{
public MyFrame(){
setSize(500,500);
setVisible(true);
setResizable(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
};
public static class MyPanel extends JPanel{
public MyPanel(){
setLayout(new GridBagLayout());
setBackground(BLACK);
setOpaque(true);
GridBagConstraints gbc1 = new GridBagConstraints();
GridBagConstraints gbc2 = new GridBagConstraints();
gbc1.insets = new Insets(200,0,0,200);
gbc1.ipadx = 100;
gbc1.ipady = 100;
gbc1.gridx = 1;
gbc1.gridy = 1;
gbc2.insets = new Insets(0,200,200,0);
gbc2.ipadx = 150;
gbc2.ipady = 10 ;
gbc2.gridx = 1;
gbc2.gridy = 1;
JTextArea jta1 = new JTextArea();
jta1.setLineWrap(true);
jta1.setWrapStyleWord(true);
JButton jb1 = new JButton("Have a banana!");
jb1.setToolTipText("Button prints Banana.");
ActionListener action1 = new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
System.out.println("\nBananas!");
}
};
jb1.addActionListener(action1);
add(jta1,gbc2);
add(jb1,gbc1);
};
};
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
MyFrame jf1 = new MyFrame();
MyPanel jp1 = new MyPanel();
jf1.add(jp1);
}
});
}
}
The code is just a simple JFrame, Panel, with a Button that prints bananas, and a JTextArea. When typed into without the LineWrap etc, it simply extends itself depending on which direction input is being applied. (pressing enter to go down in the area pulls it upwards/downwards, and typing into it pulls it to the left and to the right, making it bigger.) This makes sense, I didn't add a LineWrap or anything. However, adding those;
jta1.setLineWrap(true);
jta1.setWrapStyleWord(true);
I get the following result;
Just a picture to show what I'm talking about:
What I'm aiming for is a JTextArea that has limits, when those are exceeded creates a scrollbar, and doesn't change size at all.
Suggestions?
What I'm aiming for is a JTextArea that has limits, when those are exceeded creates a scrollbar, and doesn't change size at all.
JTextArea jta1 = new JTextArea();
Your text area doesn't have preferred size so it keeps growing. You need to create the text area with a preferred size. This is done by using:
JTextArea jta1 = new JTextArea(5, 30);
Now the layout manager can use the preferred size of the text area and the scrollpane will display the scrollbars when the preferred size of the text area is greater than the size of the scroll pane.
when those are exceeded creates a scrollbar, and doesn't change size at all.
And as pointed out by MadProgrammer if you want a scrollbar then you also need to actually add your text area to a JScrollPane and then add the scrollpane to the frame. So you would also need to use code like:
//add(jta1,gbc2);
add(new JScrollPane(jta1), gbc);
Read the section from the Swing tutorial on How to Use Text Areas for more information and examples.
I'm trying to build a simple panel which I will throw some fields onto and capture some user data. I typically use a combination of GridBagLayouts (thanks to trashgod) and BoxLayouts to achieve the layout I want. I normally don't have any issues with using them and they just do what makes intuitive sense 99% of the time, but I can't seem to make this rather simple panel function properly. Can anyone tell me why?
The panel class:
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class EmailPanel extends JPanel {
private JButton m_OkButton;
private JPanel m_MainPanel;
private JTextField m_ServerIPTF;
private JTextField m_ServerPortTF;
private JTextField m_DomainNameTF;
private JTextField m_UnitNameTF;
private JTextField m_Recipient1TF;
private JTextField m_Recipient2TF;
private final Dimension LARGE_TEXTFIELD_SIZE = new Dimension(125, 25);
public EmailPanel() {
init();
}
private void init() {
this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
JPanel tPanel;
JLabel tLabel;
Header: {
tPanel = new JPanel();
tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.X_AXIS));
tPanel.add(Box.createHorizontalGlue());
tLabel = new JLabel("Email Settings");
tPanel.add(tLabel);
tPanel.add(Box.createHorizontalGlue());
tPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 3, 0, Color.red));
this.add(tPanel);
}
MainPanel: {
m_MainPanel = new JPanel();
m_MainPanel.setLayout(new BoxLayout(m_MainPanel, BoxLayout.Y_AXIS));
m_MainPanel.add(Box.createVerticalStrut(5));
tPanel = new JPanel();
tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.X_AXIS));
tPanel.add(Box.createHorizontalStrut(3));
tLabel = new JLabel("Server IP Address:");
tPanel.add(tLabel);
tPanel.add(Box.createHorizontalStrut(3));
m_ServerIPTF = new JTextField();
m_ServerIPTF.setMinimumSize(LARGE_TEXTFIELD_SIZE);
m_ServerIPTF.setMaximumSize(LARGE_TEXTFIELD_SIZE);
m_ServerIPTF.setPreferredSize(LARGE_TEXTFIELD_SIZE);
tPanel.add(m_ServerIPTF);
tPanel.add(Box.createHorizontalStrut(25));
tLabel = new JLabel("Server Port");
tPanel.add(tLabel);
tPanel.add(Box.createHorizontalStrut(3));
m_ServerPortTF = new JTextField();
m_ServerPortTF.setMinimumSize(LARGE_TEXTFIELD_SIZE);
m_ServerPortTF.setMaximumSize(LARGE_TEXTFIELD_SIZE);
m_ServerPortTF.setPreferredSize(LARGE_TEXTFIELD_SIZE);
tPanel.add(m_ServerPortTF);
tPanel.add(Box.createHorizontalGlue());
m_MainPanel.add(tPanel);
m_MainPanel.add(Box.createVerticalStrut(5));
tPanel = new JPanel();
tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.X_AXIS));
tPanel.add(Box.createHorizontalStrut(6));
tLabel = new JLabel("Domain Name:");
tPanel.add(tLabel);
tPanel.add(Box.createHorizontalStrut(3));
m_DomainNameTF = new JTextField();
m_DomainNameTF.setMinimumSize(LARGE_TEXTFIELD_SIZE);
m_DomainNameTF.setMaximumSize(LARGE_TEXTFIELD_SIZE);
m_DomainNameTF.setPreferredSize(LARGE_TEXTFIELD_SIZE);
tPanel.add(m_DomainNameTF);
tPanel.add(Box.createHorizontalGlue());
m_MainPanel.add(tPanel);
this.add(m_MainPanel);
}
OKButton: {
m_OkButton = new JButton("Ok");
tPanel = new JPanel();
tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.X_AXIS));
tPanel.add(Box.createHorizontalGlue());
tPanel.add(m_OkButton);
tPanel.add(Box.createHorizontalGlue());
this.add(tPanel);
}
this.add(Box.createVerticalGlue());
}
}
If you add this to / use this as a content pane, you will see that there are large gaps on the Y axis between the various controls. I'm under the impression that the vertical glue that I add at the end of the init method should grow to consume all the space below the OK button, and the controls would be pushed together as a consequence. What I'm seeing is that it seems to be splitting up the space evenly between the various instances of my temporary JPanel object tPanel and the vertical glue at the bottom. How do I make it stop doing that?
Edit: It seems that the behavior is the same both with and without the somewhat superfluous m_MainPanel object.
This is what I see when it renders and the form is made larger than needed for the controls. I would expect the vertical glue to fill the space below the OK button to keep the controls on the top of the form.
I copy-pasted your code and added the main method:
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.getContentPane().add(new EmailPanel());
frame.pack();
frame.setVisible(true);
}
This is the result:
with or without the line this.add(Box.createVerticalGlue());
Is this what you wanted or not?
Edit: Solution
I edited your code to achieve the desired result:
public class EmailPanel extends JPanel {
private JButton okButton;
private JTextField serverIPTF;
private JTextField serverPortTF;
private JTextField domainNameTF;
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.getContentPane().add(new EmailPanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setMinimumSize(frame.getPreferredSize());
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public EmailPanel() {
init();
}
private void init() {
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
JPanel tPanel;
JLabel tLabel;
// Header
tLabel = new JLabel("Email Settings", JLabel.CENTER);
tLabel.setAlignmentX(CENTER_ALIGNMENT);
tLabel.setMaximumSize(new Dimension(Integer.MAX_VALUE, tLabel.getPreferredSize().height));
tLabel.setBorder(BorderFactory.createMatteBorder(0, 0, 3, 0, Color.red));
add(tLabel);
// Fields
JPanel fieldsPanel = new JPanel();
fieldsPanel.setLayout(new BoxLayout(fieldsPanel, BoxLayout.Y_AXIS));
fieldsPanel.setBorder(BorderFactory.createMatteBorder(5, 3, 5, 3, new Color(0, 0, 255, 255)));
// Top fields
serverIPTF = new JTextField(10);
serverIPTF.setMaximumSize(serverIPTF.getPreferredSize());
serverPortTF = new JTextField(10);
serverPortTF.setMaximumSize(serverPortTF.getPreferredSize());
tPanel = new JPanel();
tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.X_AXIS));
tPanel.add(new JLabel("Server IP Address:"));
tPanel.add(Box.createRigidArea(new Dimension(3, 0)));
tPanel.add(serverIPTF);
tPanel.add(Box.createRigidArea(new Dimension(25, 0)));
tPanel.add(new JLabel("Server Port"));
tPanel.add(Box.createRigidArea(new Dimension(3, 0)));
tPanel.add(serverPortTF);
tPanel.add(Box.createHorizontalGlue());
fieldsPanel.add(tPanel);
fieldsPanel.add(Box.createRigidArea(new Dimension(0, 5)));
// Lower field
domainNameTF = new JTextField(10);
domainNameTF.setMaximumSize(domainNameTF.getPreferredSize());
tPanel = new JPanel();
tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.X_AXIS));
tPanel.add(new JLabel("Domain Name:"));
tPanel.add(Box.createRigidArea(new Dimension(3, 0)));
tPanel.add(domainNameTF);
tPanel.add(Box.createHorizontalGlue());
fieldsPanel.add(tPanel);
add(fieldsPanel);
// OK Button
okButton = new JButton("OK");
okButton.setAlignmentX(CENTER_ALIGNMENT);
add(okButton);
}
}
Explanation:
BoxLayout says:
When a BoxLayout lays out components from top to bottom, it tries to size each component at the component's preferred height. If the vertical space of the layout does not match the sum of the preferred heights, then BoxLayout tries to resize the components to fill the space. The components either grow or shrink to fill the space, with BoxLayout honoring the minimum and maximum sizes of each of the components. Any extra space appears at the bottom of the container.
(emphasis mine)
Which tells us that if we restrict the components' maximum height to their preferred height, all the extra vertical space will go to the bottom, just as you want. Hence, we added for all the text fields (the labels do not grow vertically) the line:
nameTF.setMaximumSize(nameTF.getPreferredSize());
and we don't need any vertical glue.
Notes:
I created the text fields with 10 columns, you can change this value.
The top label does not need horizontal glue to stretch it, just relax the maximum width constraint and set the alignment (similarly to the bottom button).
Instead of creating a lot of rigid areas (you used struts), I created a border with the appropriate widths. It is blue for visual purposes, but you should set its alpha to 0 to make is transparent.
Use createRigidArea instead of createXXXStrut (see the note in the above link).
I used frame.setMinimumSize(frame.getPreferredSize()) to not let the window resize to a smaller size than its contents. This is optional.
Non-final fields and variables should not use underscore (_) in the name according to Java naming conventions.
You did not specify horizontal stretching behavior, so it does whatever it does.
I still think that box layout is not the best approach here, or at least do not allow resizing of the window at all (so to not deal with extra space).
I am thoroughly confused. I have a pretty decent understanding of how each layout manger works and what each one is used for, but I'm not understanding what combination of layout managers and JPanels are necessary to make what I need work.
What I am trying to accomplish
I have a top bar, and a bottom bar of a container panel NORTH and SOUTH of a BorderLayout.
Within the Center panel, I want an unknown number of buttons 1 or more. Regardless of how many buttons there are they all need to be the same size, if there are dozens then scrolling should start happening once the buttons pass the window size limit.
What I am getting
Depending on the combination of layout mangers and how many nested JPanels I use and all sorts of trouble shooting, I get one massive button filling the entire CENTER element. I get 2 buttons that are the right size, but spread way apart (gap filling the CENTER space), or I get a dozen buttons that are the right size with no scroll.
I can solve any one of these, but then the other breaks. IE if I get a bunch of correctly sized buttons that properly scroll, then when I replace them with a single button its one massive button. Or if I get a single properly sized button then the larger quantity won't scroll etc.
My Code
import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.io.*;
public class TestCode extends JFrame {
private final JFrame frame;
public TestCode(){
frame = new JFrame();
JLabel title = new JLabel("Test Title");
JPanel windowContainer = new JPanel();
JPanel topPanel = new JPanel();
final JPanel middlePanel = new JPanel();
JPanel bottomPanel = new JPanel();
JButton searchButton = new JButton("Search");
JButton browseButton = new JButton("Browse...");
JButton testButton = new JButton("Button 1");
JButton exitButton = new JButton("Exit");
final JTextField searchBar = new JTextField("Search database...");
topPanel.setLayout(new GridLayout(2,0));
topPanel.add(title);
title.setHorizontalAlignment(JLabel.CENTER);
topPanel.setPreferredSize(new Dimension(getWidth(), 100));
// This is a subset of the top section. Top part is two panels, bottom panel is two cells (grid)
JPanel topPanelSearch = new JPanel();
topPanelSearch.setLayout(new GridLayout(0,2));
topPanelSearch.add(searchBar);
topPanelSearch.add(searchButton);
topPanel.add(topPanelSearch);
// PROBLEM AREA STARTS
// middlePanel.setLayout(new FlowLayout());
middlePanel.setLayout(new GridLayout(0, 1, 10, 10));
// middlePanel.setLayout(new BoxLayout(middlePanel, BoxLayout.PAGE_AXIS));
JPanel innerContainer = new JPanel();
innerContainer.setLayout(new BoxLayout(innerContainer, BoxLayout.PAGE_AXIS));
// innerContainer.setLayout(new FlowLayout());
// innerContainer.setLayout(new GridLayout(0, 1, 10, 10));
for(int i = 0; i < 2; i++){
JButton button = new JButton("Button ");
button.setPreferredSize(new Dimension(400, 100));
JPanel test = new JPanel();
test.add(button);
innerContainer.add(test);
}
JScrollPane midScroll = new JScrollPane(innerContainer);
middlePanel.add(midScroll);
// PROBLEM AREA ENDS
bottomPanel.setLayout(new GridLayout(0, 3));
bottomPanel.setPreferredSize(new Dimension(getWidth(), 100));
bottomPanel.add(testButton);
bottomPanel.add(browseButton);
bottomPanel.add(exitButton);
windowContainer.setLayout(new BorderLayout());
windowContainer.add(topPanel, BorderLayout.NORTH);
windowContainer.add(middlePanel, BorderLayout.CENTER);
windowContainer.add(bottomPanel, BorderLayout.SOUTH);
frame.add(windowContainer);
frame.setTitle("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(480, 800);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args){
TestCode test = new TestCode();
}
}
Visual of some of the fail results
I want the leftmost picture, but buttons should be stacked neatly (like the middle picture) when there are only a few results, and scrollable when there are lots.
What am I doing wrong?
Try with GridBagLayout and a filler component that takes the remaining vertical space and therefore forces the buttons upwards.
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.Box.Filler;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class GridbagButtons extends JFrame {
private final JScrollPane jscrpButtons;
private final JPanel jpButtons;
public GridbagButtons() {
setLayout(new BorderLayout());
jpButtons = new JPanel(new GridBagLayout());
jscrpButtons = new JScrollPane(jpButtons);
add(jscrpButtons, BorderLayout.CENTER);
// add a custom number of buttons
int numButtons = 10;
for (int i = 0; i < numButtons; i++) {
JButton jbButton = new JButton("Button");
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = i;
jpButtons.add(jbButton, gbc);
}
// add a vertical filler as last component to "push" the buttons up
GridBagConstraints gbc = new GridBagConstraints();
Filler verticalFiller = new Filler(
new java.awt.Dimension(0, 0),
new java.awt.Dimension(0, 0),
new java.awt.Dimension(0, Integer.MAX_VALUE));
gbc.gridx = 0;
gbc.gridy = numButtons;
gbc.fill = java.awt.GridBagConstraints.VERTICAL;
gbc.weighty = 1.0;
jpButtons.add(verticalFiller, gbc);
setSize(300, 200);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new GridbagButtons().setVisible(true);
}
});
}
}
If I add components like JButtons on the East or West side, how do I prevent it from hugging the side of the screen? I want some space between the JButtons and the edge of the screen.
call setBorder on your JButton like this:
setBorder( new EmptyBorder( 3, 3, 3, 3 ) )
From the JavaDoc, an EmptyBorder is a "transparent border which takes up space but does no drawing". In my example it shall take 3 pixels respectively top, left, bottom and right.
Complete code whose purpose is only to show how to use EmptyBorder:
import java.awt.BorderLayout;
import java.awt.Container;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.border.EmptyBorder;
public class ALineBorder {
public static void main(String args[]) {
JFrame frame = new JFrame("Line Borders");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton button1 = new JButton("Button1");
button1.setBorder( new EmptyBorder( 8, 8, 8, 8 ) );
JButton button2 = new JButton("Button2");
JButton button3 = new JButton("Button3");
button3.setBorder( new EmptyBorder( 16, 16, 16, 16 ) );
Container contentPane = frame.getContentPane();
contentPane.add(button1, BorderLayout.WEST);
contentPane.add(button2, BorderLayout.CENTER);
contentPane.add(button3, BorderLayout.EAST);
frame.pack();
frame.setSize(300, frame.getHeight());
frame.setVisible(true);
}
}
Most likely you have (or soon will have) more than one button in the container, and want to align them horizontally. So consider putting the buttons within in a nested JPanel with a GridBagLayout:
class ButtonPanel extends JPanel {
ButtonPanel() {
setLayout(new GridBagLayout());
}
#Override
public Component add(Component button) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridy = nextGridY++;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(3, 3, 3, 3);
super.add(button, gbc);
return button;
}
int nextGridY;
}
Then add this panel to the parent frame or panel (with a BorderLayout.)
BorderLayout goes as the name says to the border. You can however nest things inside, so you could insert a JPanel with a border and then put your button in that.
You may also want to experiment with the GUI designer in Netbeans. It is really quite nice, and give a lot of help to things you usually want to do (like have a margin to the border, etc).