Why doesn't frame.pack() work with a JTable? - java

I have a super basic JTable, which I'm adding to a JFrame, but for some reason frame.pack() doesn't work. I'm setting the row height to the column width so that the cells of the table are squares, but this doesn't seem to work when the window is resized. So, how can I make it so that the cells of the table are always squares, even when resized, and the frame is properly packed? Here's the code for the window:
package me.an.ar.window;
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
public class DayPlanner
{
private JFrame frame;
private void createAndShowGUI()
{
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container container = frame.getContentPane();
Object[][] data = new Object[5][7];
String[] columnNames = { "Day1", "Day2", "Day3", "Day4", "Day5", "Day6", "Day7" };
JTable table = new JTable(data, columnNames);
for (int col = 0; col < table.getColumnCount(); col++)
{
for (int row = 0; row < table.getRowCount(); row++)
{
int colWidth = table.getColumnModel().getColumn(col).getWidth();
table.setRowHeight(row, colWidth);
}
}
container.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void show()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
public DayPlanner()
{
show();
}
public static void main(String[] args)
{
new DayPlanner();
}
}

You did not set any LayoutManager. The default is not a FlowLayout but at least not null:
The default content pane will have a BorderLayout manager set on it.
(Java 17 API)
This would mean by resizing the window you would more have to fight with the scrollpane's resizing. What happens if you use
container.add(new JScrollPane(table), BorderLayout.CENTER);
That should let the scrollpane grow and shrink with the frame, and the amount of rows/columns inside the table might vary. This might also get impacted by JTable.setAutoResizeMode().

Option 1
You haven't really mentioned how you are about to use this table (what data will be held and how the user is expected to interact with); however, it sounds like a use case for GridLayout (regarding data cells), as well as GridBagLayout (regarding header cells). Example below:
public class DayPlanner {
private JFrame frame;
Dimension cellSize = new Dimension(75, 75);
private void createAndShowGUI() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Create header cells
JPanel p1 = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
String[] columnNames = { "Day 1", "Day 2", "Day 3", "Day 4", "Day 5", "Day 6", "Day 7" };
int count = 0;
for (int i = 0; i < columnNames.length; i++) {
JLabel l = new JLabel(columnNames[i], SwingConstants.CENTER);
l.setFont(new Font("Times New Roman", Font.BOLD, 14));
l.setBackground(new Color(238, 238, 238));
l.setOpaque(true);
l.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, new Color(122, 138, 153)));
c.fill = GridBagConstraints.HORIZONTAL;
c.ipady = 5; // adjusts cell's height
c.weightx = 0.5;
c.gridx = count++;
c.gridy = 0;
p1.add(l, c);
}
// Create data cells
JPanel p2 = new JPanel();
p2.setLayout(new GridLayout(5, 7));
for (int i = 0; i < 35; i++) {
JLabel l = new JLabel("<html> This is some sample text " + i + "</html>"); // html tags allow text-wrapping
l.setFont(new Font("Times New Roman", Font.PLAIN, 14));
l.setVerticalAlignment(SwingConstants.TOP);
l.setHorizontalAlignment(SwingConstants.LEFT);
l.setPreferredSize(cellSize);
l.setBackground(Color.WHITE);
l.setOpaque(true);
l.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 1, new Color(122, 138, 153)));
p2.add(l);
}
JScrollPane sp = new JScrollPane(p2);
sp.getVerticalScrollBar().setUnitIncrement((int) cellSize.getHeight() / 2); // adjusts scrolling speed
sp.getViewport().setBackground(Color.WHITE);
Container container = frame.getContentPane();
container.add(p1, BorderLayout.NORTH);
container.add(sp, BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new DayPlanner().createAndShowGUI();
}
});
}
}
Option 2
If you still want to use the JTable given in your question, you could add a ComponentListener to your JFrame, so that you can listen to "resize events", allowing you to readjust the cells' size and retain their square shape.
frame.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent evt) {
for (int row = 0; row < table.getRowCount(); row++) {
int colWidth = table.getColumnModel().getColumn(0).getWidth();
table.setRowHeight(row, colWidth);
}
}
});
As for the pack() issue, you could set the initial size of the JFrame according to the columns and rows in your table (remember to comment out frame.pack();). The below, although it would remove the empty space at the bottom of the frame, it wouldn't, however, stop the user from resizing the window and bringing that back.
int colWidth = table.getColumnModel().getColumn(0).getWidth();
frame.setSize(7 * colWidth, 5 * table.getRowHeight(0) + 25);
//frame.pack();

Related

Problem with refreshing GridLayout on Java Swing

i have a problem with refreshing the values of my gridlayout.
So, i have a JPanel in a JFrame and in that JPanel , once i entered two values(one for rows and one for columns) and then by clicking on validate, i get a GridLayout with the previous values of JButtons.
So for exemple if I enter (2,2) i get a GridLayout of 4 JButtons and in each JButton i have an image.
So my problem here is, every time i wanna refresh the GridLayout by changing the values, it doesn’t work, the GridLayout doesn’t change, or if it change, the JButtons are inclickable.
I feel like every time i click on Validate, a new GridLayout is created on my JPanel, but the first one is still there.
I will upload two pictures, one with the normal functioning (entering values first time), and the second with the bug (entering new values).
Thanks guys.
First values
Second values
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Color;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.awt.event.ActionEvent;
import javax.swing.*;
public class PagePrincipal extends JFrame implements ActionListener {
JButton Valider;
JTextField Columns;
JTextField Rows;
ArrayList<JButton> butt;
public PagePrincipal(){
getContentPane().setLayout(null); //this is not the panel that contains the GridLayout
Columns = new JTextField();
Columns.setBounds(219, 35, 197, 57);
getContentPane().add(Columns);
Columns.setColumns(10);
Rows = new JTextField();
Rows.setBounds(451, 35, 226, 57);
getContentPane().add(Rows);
Rows.setColumns(10);
Valider = new JButton();
Valider.setBackground(new Color(65, 179, 163));
Valider.setForeground(Color.WHITE);
Valider.setFont(new Font("Bookman Old Style", Font.BOLD, 20));
Valider.setBounds(704, 15, 268, 81);
Valider.setText("Validation");
Valider.addActionListener(this);
this.add(Valider);
this.setResizable(true);
this.setVisible(true);
this.setExtendedState(JFrame.MAXIMIZED_BOTH);
}
#Override
public void actionPerformed(ActionEvent event) {
if (event.getSource() == Valider) {
int NbRows= Integer.parseInt(Rows.getText());
int NbColumns=Integer.parseInt(Columns.getText());
JButton button[] = new JButton[NbRows*NbColumns];
butt = new ArrayList<>();
setDefaultCloseOperation(EXIT_ON_CLOSE);
JPanel botPanel = new JPanel(); //this is the panel that contains the GridLayout
botPanel.setBounds(100, 200, 1000, 400);
this.add(botPanel);
botPanel.setLayout(new GridLayout(NbRows,NbColumns));
for (int i=0; i<NbRows*NbColumns; i++){
button[i]=new JButton();
botPanel.add(button[i]);
butt.add(button[i]);
}
this.setVisible(true);
}
}
}
Again, avoid null layouts if at all possible, since they force you to create rigid, inflexible, hard to maintain GUI's that might work on one platform only. Instead, nest JPanels, each using its own layout to help create GUI's that look good, are flexible, extendable and that work.
Also, when changing components held within a container, call revalidate() and repaint() on the container after making the changes. For example, the following GUI:
Is created with the following code:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
public class PagePrincipal2 extends JPanel {
public static final int MAX_ROWS = 40;
public static final int MAX_COLS = 12;
private JButton validatorButton = new JButton("Validate");
private JSpinner columnsSpinner = new JSpinner(new SpinnerNumberModel(2, 1, MAX_COLS, 1));
private JSpinner rowsSpinner = new JSpinner(new SpinnerNumberModel(2, 1, MAX_ROWS, 1));
private List<JButton> buttonsList = new ArrayList<>();
private JPanel gridPanel = new JPanel();
public PagePrincipal2() {
JPanel topPanel = new JPanel();
topPanel.add(new JLabel("Columns:"));
topPanel.add(columnsSpinner);
topPanel.add(Box.createHorizontalStrut(10));
topPanel.add(new JLabel("Rows:"));
topPanel.add(rowsSpinner);
topPanel.add(Box.createHorizontalStrut(10));
topPanel.add(validatorButton);
JScrollPane scrollPane = new JScrollPane(gridPanel);
int gridWidth = 1000;
int gridHeight = 600;
scrollPane.setPreferredSize(new Dimension(gridWidth, gridHeight));
setLayout(new BorderLayout());
add(topPanel, BorderLayout.PAGE_START);
add(scrollPane, BorderLayout.CENTER);
validatorButton.addActionListener(e -> validateGrid());
}
private void validateGrid() {
int nbRows = (int) rowsSpinner.getValue();
int nbColumns = (int) columnsSpinner.getValue();
gridPanel.removeAll();
buttonsList.clear();
gridPanel.setLayout(new GridLayout(nbRows, nbColumns));
for (int i = 0; i < nbRows * nbColumns; i++) {
int column = i % nbColumns;
int row = i / nbColumns;
String text = String.format("[%02d, %02d]", column, row);
JButton button = new JButton(text);
button.addActionListener(e -> gridButtonAction(column, row));
buttonsList.add(button);
gridPanel.add(button);
}
gridPanel.revalidate();
gridPanel.repaint();
}
private void gridButtonAction(int column, int row) {
String message = String.format("Button pressed: [%02d, %02d]", column, row);
String title = "Grid Button Press";
int type = JOptionPane.INFORMATION_MESSAGE;
JOptionPane.showMessageDialog(this, message, title, type);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
PagePrincipal2 mainPanel = new PagePrincipal2();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
Note that the gridPanel, the one holding the buttons, is placed into a JScrollPane:
JScrollPane scrollPane = new JScrollPane(gridPanel);
Note that the main JPanel that holds everything is given a BorderLayout, and then 2 components are added, a topPanel JPanel that holds labels, buttons and fields for data input, added at the BorderLayout.PAGE_START, the top position, and the JScrollPane is added to the main JPanel at the BorderLayout.CENTER position:
setLayout(new BorderLayout());
add(topPanel, BorderLayout.PAGE_START);
add(scrollPane, BorderLayout.CENTER);
When the old buttons are removed from the gridPanel, and then new buttons are added, I will call revalidate() and repaint() on the gridPanel, the first method to get the layout managers to layout the new components, and the second method call to remove any dirty pixels that may be present:
private void validateGrid() {
int nbRows = (int) rowsSpinner.getValue();
int nbColumns = (int) columnsSpinner.getValue();
gridPanel.removeAll();
buttonsList.clear();
gridPanel.setLayout(new GridLayout(nbRows, nbColumns));
for (int i = 0; i < nbRows * nbColumns; i++) {
int column = i % nbColumns;
int row = i / nbColumns;
String text = String.format("[%02d, %02d]", column, row);
JButton button = new JButton(text);
button.addActionListener(e -> gridButtonAction(column, row));
buttonsList.add(button);
gridPanel.add(button);
}
gridPanel.revalidate();
gridPanel.repaint();
}

Anchor constraint in GridBagLayout not working

import java.awt.*;
import javax.swing.*;
public class GBLClumpingExample extends JFrame{
GBLClumpingExample(){
GridBagLayout g = new GridBagLayout();
GridBagConstraints gv = new GridBagConstraints();
GridBagLayout b = new GridBagLayout();
GridBagConstraints gc = new GridBagConstraints();
setVisible(true);
setSize(720,720);
setLayout(g);
JPanel p = new JPanel();
p.setLayout(b);
gv.fill = GridBagConstraints.BOTH;
add(p,gv);
Label l1 = new Label("Label 1");
Label l2 = new Label("Label 2");
Label l3 = new Label("Label 3");
Label l4 = new Label("Label 4");
Label l5 = new Label("Label 5");
gc.weightx =1.0;
gc.weighty = 1.0;
gc.gridx= 1;
gc.gridy= 1;
gc.anchor = GridBagConstraints.PAGE_START;
gc.gridx= -1;
gc.gridy= 0;
p.add(l1,gc);
gc.anchor = GridBagConstraints.SOUTH;
p.add(l2,gc);
gc.anchor = GridBagConstraints.EAST;
p.add(l3,gc);
gc.anchor = GridBagConstraints.WEST;
p.add(l4,gc);
gc.anchor = GridBagConstraints.CENTER;
p.add(l5,gc);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args){
GBLClumpingExample e = new GBLClumpingExample();
}
}
I was trying to use GridBagLayout but maybe it is not working correctly.
This is my code I don't know what is wrong but anchor constraints of GridBagConstraints are not working, they are all just clump together.
You are adding you components to a panel p which you are then adding to the frame (to its content pane) using add(p,gv);. The constraints in gv have been initialized with gv.fill = GridBagConstraints.BOTH;, but its weightx and weighty are left at their initial zero. As a result, this panel will stay at its preferred size and not receive additional space, so it has no additional space to distribute to its own content.
Since all labels have the same size, their anchors have no effect when there is no additional space.
When you change the line
gv.fill = GridBagConstraints.BOTH;
to
gv.fill = GridBagConstraints.BOTH;
gv.weightx = 1;
gv.weighty = 1;
you will see the effect of the anchors. Alternatively, you can get rid of the additional panel. There are other redundant operations too. You can simplify your code to:
import java.awt.*;
import javax.swing.*;
public class GBAnchorExample extends JFrame{
GBAnchorExample() {
Container c = super.getContentPane();
c.setLayout(new GridBagLayout());
GridBagConstraints gc = new GridBagConstraints();
gc.weightx = gc.weighty = 1.0;
JLabel l1 = new JLabel("Label 1");
JLabel l2 = new JLabel("Label 2");
JLabel l3 = new JLabel("Label 3");
JLabel l4 = new JLabel("Label 4");
JLabel l5 = new JLabel("Label 5");
gc.anchor = GridBagConstraints.PAGE_START;
c.add(l1,gc);
gc.anchor = GridBagConstraints.SOUTH;
c.add(l2,gc);
gc.anchor = GridBagConstraints.EAST;
c.add(l3,gc);
gc.anchor = GridBagConstraints.WEST;
c.add(l4,gc);
gc.anchor = GridBagConstraints.CENTER;
c.add(l5,gc);
super.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
GBAnchorExample e = new GBAnchorExample();
e.setSize(720,720);
e.setVisible(true);
}
}
To visualize the actual effect of the anchor you may change the main method to
public static void main(String[] args){
GBAnchorExample e = new GBAnchorExample();
Component grid = new JComponent() {
#Override
protected void paintComponent(Graphics g) {
g.setColor(Color.GREEN);
int w = getWidth(), h = getHeight();
for(int i = 1; i < 5; i++) {
int x = (int)(w/5.0*i);
g.drawLine(x, 0, x, h);
}
}
};
e.setGlassPane(grid);
grid.setVisible(true);
e.setSize(720,720);
e.setVisible(true);
}
This will paint a green grid to show the logical cells containing the labels, so it becomes apparent how the anchors affect the labels’ positions within their cells.
If I understand the intent of this layout correctly (I'm not sure I do) then this might be done using a BorderLayout.
Here it is with more width and height:
The titled borders are only there to provide a quick visual reference to the constraint used to place the component. The (spacer) is only added to the 3 labels in the center row to allow the full TitledBorder to appear!
Here is the code:
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
public class BLNotClumpingExample {
private JComponent ui = null;
BLNotClumpingExample() {
initUI();
}
public void initUI() {
if (ui!=null) return;
ui = new JPanel(new BorderLayout(10,10));
ui.setBorder(new EmptyBorder(4,4,4,4));
ui.add(getLabel("Label 1", "PAGE_START"), BorderLayout.PAGE_START);
ui.add(getLabel("Label 2", "PAGE_END"), BorderLayout.PAGE_END);
ui.add(getLabel("Label 3 (spacer)", "LINE_END"), BorderLayout.LINE_END);
ui.add(getLabel("Label 4 (spacer)", "LINE_START"), BorderLayout.LINE_START);
ui.add(getLabel("Label 5 (spacer)", "CENTER"), BorderLayout.CENTER);
}
private JLabel getLabel(String text, String constraint) {
JLabel l = new JLabel(text, SwingConstants.CENTER);
l.setBorder(new TitledBorder(constraint));
return l;
}
public JComponent getUI() {
return ui;
}
public static void main(String[] args) {
Runnable r = () -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception useDefault) {
}
BLNotClumpingExample o = new BLNotClumpingExample();
JFrame f = new JFrame(o.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o.getUI());
f.pack();
f.setMinimumSize(f.getSize());
f.setVisible(true);
};
SwingUtilities.invokeLater(r);
}
}

Spaces between elements in GridLayout and GridBagLayout

I am trying to create a grid of text fields which I envision would look like this:
I am trying to use Swing in order to do this but am having trouble creating the grid. I have tried both GridBagLayout and GridLayout in order to accomplish this but have had the same issue with both - I am unable to remove spaces between the text fields.
The above image is using grid bag layout. I have tried to change the insets as well as the weights of each text field but have not been able to get rid of the spaces between the fields.
The grid layout is slightly better:
But it has the same problem. I tried adding each text field to a JPanel and then created an empty border for each panel but this also did not work.
I have attached the code for both implementations. I am not committed to using a JTextField so if there is some other element that a user can type into I would be willing to try that out as well. Any help getting rid of the spaces between each text field would be greatly appreciated!
GridBagLayoutDemo
class GridBagLayoutDemo {
public static void addComponentsToPane(Container pane) {
GridBagLayout gbl = new GridBagLayout();
pane.setLayout(gbl);
GridBagConstraints c = new GridBagConstraints();
int rows = 2;
int cols = 2;
for(int i = 0; i < (rows + 1) * 3; i++){
JTextField textField = new JTextField(1);
textField.setFont( new Font("Serif", Font.PLAIN, 30) );
JPanel tempPanel = new JPanel();
tempPanel.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
tempPanel.add(textField);
c.gridx = i % (rows + 1);
c.gridy = i / (cols + 1);
c.gridheight = 1;
c.gridwidth = 1;
c.anchor = GridBagConstraints.FIRST_LINE_START;
c.fill = GridBagConstraints.HORIZONTAL;
pane.add(tempPanel, c);
}
gbl.setConstraints(pane, c);
c.insets = new Insets(0,0,0,0);
}
public void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("GridBagLayoutDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Set up the content pane.
addComponentsToPane(frame.getContentPane());
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
GridBagLayoutDemo demo = new GridBagLayoutDemo();
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
demo.createAndShowGUI();
}
});
}
}
GridLayoutDemo
class GridLayoutDemo {
public void createAndShowGUI() {
JFrame frame = new JFrame("GridLayout");
//frame.setOpacity(0L);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel parentPanel = new JPanel();
GridLayout layout = new GridLayout(3, 3, 0, 0);
layout.setHgap(0);
layout.setVgap(0);
parentPanel.setLayout(layout);
for(int i = 0 ; i < 9; i++){
JTextField textField = new JTextField();
textField.setHorizontalAlignment(JTextField.CENTER);
// JPanel tempPanel = new JPanel();
//textField.setBounds(0, 0, 10 , 10);
//textField.setFont( new Font("Serif", Font.PLAIN, 18));
//tempPanel.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
//tempPanel.add(textField);
// tempPanel.add(textField);
parentPanel.add(textField);
}
frame.add(parentPanel);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
GridLayoutDemo demo = new GridLayoutDemo();
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
demo.createAndShowGUI();
}
});
}
}
I think you will find that this is a issue with the MacOS look and feel, as it adds a empty border around the text fields to allow for the focus highlight
You can see it highlighted below
The simplest way to remove it, is to remove or replace the border, for example...
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
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.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.weighty = 1;
int rows = 3;
int cols = 3;
for (int index = 0; index < (rows * cols); index++) {
int row = index % rows;
int col = index / cols;
gbc.gridy = row;
gbc.gridx = col;
JTextField textField = new JTextField(4);
textField.setText(col + "x" + row);
textField.setBorder(new LineBorder(Color.DARK_GRAY));
add(textField, gbc);
}
}
}
}

Displaying JLabel matrix

Can somebody tell me why after calling method getContentPane().add(grid[i][j]) I am not able to display the matrix of JLabels. There's only one "e" label displayed.
public class SudokuFrame extends JFrame implements ActionListener {
JButton generateButton;
JLabel[][] grid;
public SudokuFrame(){
setSize(300, 300);
setTitle("Sudoku");
setLayout(null);
generateButton = new JButton("Generate");
generateButton.setBounds(90, 220, 100, 30);
add(generateButton);
generateButton.addActionListener(this);
grid = new JLabel[9][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
grid[i][j] = new JLabel("e");
grid[i][j].setBounds(100, 100, 30, 30);
getContentPane().add(grid[i][j]);
}
}
}
public static void main(String[] args){
SudokuFrame frame = new SudokuFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
You're giving every JLabel the exact same bounds -- same size and same position and so every new label is placed right smack dab on top of the previously added ones.
Solution: don't use null layout. Why use this when the problem is perfectly suited for a GridLayout? In general you want to avoid using null layouts and setBounds as the layout managers will make your coding and your GUI much easier to manage. Let the layouts do the heavy lifting for you.
e.g.,
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import javax.swing.*;
public class SimpleSudoku extends JPanel {
private static final int GAP = 1;
private static final Font LABEL_FONT = new Font(Font.DIALOG, Font.PLAIN, 24);
private JLabel[][] grid = new JLabel[9][9];
public SimpleSudoku() {
JPanel sudokuPanel = new JPanel(new GridLayout(9, 9, GAP, GAP));
sudokuPanel.setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
sudokuPanel.setBackground(Color.BLACK);
for (int row = 0; row < grid.length; row++) {
for (int col = 0; col < grid[row].length; col++) {
grid[row][col] = new JLabel(" ", SwingConstants.CENTER);
grid[row][col].setFont(LABEL_FONT); // make it big
grid[row][col].setOpaque(true);
grid[row][col].setBackground(Color.WHITE);
sudokuPanel.add(grid[row][col]);
}
}
JPanel bottomPanel = new JPanel();
bottomPanel.add(new JButton("Regenerate"));
setLayout(new BorderLayout());
add(sudokuPanel, BorderLayout.CENTER);
add(bottomPanel, BorderLayout.PAGE_END);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
createAndShowGui();
});
}
private static void createAndShowGui() {
SimpleSudoku mainPanel = new SimpleSudoku();
JFrame frame = new JFrame("SimpleSudoku");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
}

Providing white space in a Swing GUI

A GUI with no white space appears 'crowded'. How can I provide white space without resorting to explicitly setting the position or size of components?­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Using various LayoutManagers one can provide spacing between various components.
1.) BorderLayout :
Overloaded Constructor : BorderLayout(int horizontalGap, int verticalGap)
Getter and setter methods
For Horizontal Spacing : BorderLayout.getHgap() and BorderLayout.setHgap(int hgap)
For Vertical Spacing : BorderLayout.getVgap() and BorderLayout.setVgap()
2.) FlowLayout :
Overloaded Constructor : FlowLayout(int align, int hgap, int vgap)
Getter and setter methods
For Horizontal Spacing : FlowLayout.getHgap() and FlowLayout.setHgap(int hgap)
For Vertical Spacing : FlowLayout.getVgap() and FlowLayout.setVgap()
3.) GridLayout :
Overloaded Constructor : GridLayout(int rows, int columns, int hgap, int vgap)
Getter and setter methods
For Horizontal Spacing : GridLayout.getHgap() and GridLayout.setHgap(int hgap)
For Vertical Spacing : GridLayout.getVgap() and GridLayout.setVgap()
4.) GridBagLayout :
GridBagConstraints.insets
5.) CardLayout (example) :
CardLayout(int hGap, int vGap)
Example to display all constructors in action :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class LayoutExample {
private final int hGap = 5;
private final int vGap = 5;
private String[] borderConstraints = {
BorderLayout.PAGE_START,
BorderLayout.LINE_START,
BorderLayout.CENTER,
BorderLayout.LINE_END,
BorderLayout.PAGE_END
};
private JButton[] buttons;
private GridBagConstraints gbc;
private JPanel borderPanel;
private JPanel flowPanel;
private JPanel gridPanel;
private JPanel gridBagPanel;
private JPanel cardPanel;
public LayoutExample() {
buttons = new JButton[16];
gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.FIRST_LINE_START;
gbc.insets = new Insets(hGap, vGap, hGap, vGap);
}
private void displayGUI() {
JFrame frame = new JFrame("Layout Example");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JPanel contentPane = new JPanel(
new GridLayout(0, 1, hGap, vGap));
contentPane.setBorder(
BorderFactory.createEmptyBorder(hGap, vGap, hGap, vGap));
borderPanel = new JPanel(new BorderLayout(hGap, vGap));
borderPanel.setBorder(
BorderFactory.createTitledBorder("BorderLayout"));
borderPanel.setOpaque(true);
borderPanel.setBackground(Color.WHITE);
for (int i = 0; i < 5; i++) {
buttons[i] = new JButton(borderConstraints[i]);
borderPanel.add(buttons[i], borderConstraints[i]);
}
contentPane.add(borderPanel);
flowPanel = new JPanel(new FlowLayout(
FlowLayout.CENTER, hGap, vGap));
flowPanel.setBorder(
BorderFactory.createTitledBorder("FlowLayout"));
flowPanel.setOpaque(true);
flowPanel.setBackground(Color.WHITE);
for (int i = 5; i < 8; i++) {
buttons[i] = new JButton(Integer.toString(i));
flowPanel.add(buttons[i]);
}
contentPane.add(flowPanel);
gridPanel = new JPanel(new GridLayout(2, 2, hGap, vGap));
gridPanel.setBorder(
BorderFactory.createTitledBorder("GridLayout"));
gridPanel.setOpaque(true);
gridPanel.setBackground(Color.WHITE);
for (int i = 8; i < 12; i++) {
buttons[i] = new JButton(Integer.toString(i));
gridPanel.add(buttons[i]);
}
contentPane.add(gridPanel);
gridBagPanel = new JPanel(new GridBagLayout());
gridBagPanel.setBorder(
BorderFactory.createTitledBorder("GridBagLayout"));
gridBagPanel.setOpaque(true);
gridBagPanel.setBackground(Color.WHITE);
buttons[12] = new JButton(Integer.toString(12));
addComp(gridBagPanel, buttons[12], 0, 0, 1, 1
, GridBagConstraints.BOTH, 0.33, 0.5);
buttons[13] = new JButton(Integer.toString(13));
addComp(gridBagPanel, buttons[13], 1, 0, 1, 1
, GridBagConstraints.BOTH, 0.33, 0.5);
buttons[14] = new JButton(Integer.toString(14));
addComp(gridBagPanel, buttons[14], 0, 1, 2, 1
, GridBagConstraints.BOTH, 0.66, 0.5);
buttons[15] = new JButton(Integer.toString(15));
addComp(gridBagPanel, buttons[15], 2, 0, 1, 2
, GridBagConstraints.BOTH, 0.33, 1.0);
contentPane.add(gridBagPanel);
cardPanel = new JPanel(new CardLayout(hGap, vGap));
cardPanel.setBorder(
BorderFactory.createTitledBorder("CardLayout"));
cardPanel.setOpaque(true);
cardPanel.setBackground(Color.WHITE);
cardPanel.add(getPanel(Color.BLUE));
cardPanel.add(getPanel(Color.GREEN));
contentPane.add(cardPanel);
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel getPanel(Color bColor) {
JPanel panel = new JPanel(new FlowLayout(
FlowLayout.CENTER, hGap, vGap));
panel.setOpaque(true);
panel.setBackground(bColor.darker().darker());
JButton swapperButton = new JButton("Next");
swapperButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
CardLayout cardLayout = (CardLayout) cardPanel.getLayout();
cardLayout.next(cardPanel);
}
});
panel.add(swapperButton);
return panel;
}
private void addComp(JPanel panel, JComponent comp
, int x, int y, int gWidth
, int gHeight, int fill
, double weightx, double weighty) {
gbc.gridx = x;
gbc.gridy = y;
gbc.gridwidth = gWidth;
gbc.gridheight = gHeight;
gbc.fill = fill;
gbc.weightx = weightx;
gbc.weighty = weighty;
panel.add(comp, gbc);
}
public static void main(String[] args) {
Runnable runnable = new Runnable(){
#Override
public void run() {
new LayoutExample().displayGUI();
}
};
EventQueue.invokeLater(runnable);
}
}
OUTPUT :
There are a number of ways in a Swing GUI to provide a separation between components, and white space around components:
JToolBar has the methods addSeparator() & addSeparator(Dimension).
JMenu uses a spacing component better suited to menus, available through addSeparator().
But more generally, look to:
The spacing as can be defined in the layout constructors.
Borders.
Here is an example of using the layout separator hGap & vGap values & borders (specifically an EmptyBorder) to provide 'white' (actually shown as red to make it very obvious) space. Adjust the spinners to see the result.
import java.awt.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.*;
public class WhiteSpace {
private JPanel gui = null;
private BorderLayout mainLayout =
new BorderLayout(0, 0);
private final FlowLayout buttonLayout =
new FlowLayout(FlowLayout.CENTER, 0, 0);
private final JPanel buttonPanel = new JPanel(buttonLayout);
private final SpinnerNumberModel hModel =
new SpinnerNumberModel(0, 0, 15, 1);
private final SpinnerNumberModel vModel =
new SpinnerNumberModel(0, 0, 15, 1);
private final SpinnerNumberModel hBorderModel =
new SpinnerNumberModel(0, 0, 15, 1);
private final SpinnerNumberModel vBorderModel =
new SpinnerNumberModel(0, 0, 15, 1);
private ChangeListener changeListener;
public Container getGui() {
if (gui == null) {
gui = new JPanel(mainLayout);
gui.setBackground(Color.RED);
JTree tree = new JTree();
tree.setVisibleRowCount(10);
for (int ii = tree.getRowCount(); ii > -1; ii--) {
tree.expandRow(ii);
}
gui.add(new JScrollPane(
tree,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER),
BorderLayout.LINE_START);
gui.add(new JScrollPane(new JTextArea(10, 30)));
gui.add(buttonPanel, BorderLayout.PAGE_START);
changeListener = (ChangeEvent e) -> {
int hGap = hModel.getNumber().intValue();
int vGap = vModel.getNumber().intValue();
int hBorder = hBorderModel.getNumber().intValue();
int vBorder = vBorderModel.getNumber().intValue();
adjustWhiteSpace(hGap, vGap, hBorder, vBorder);
};
addModel("H Gap", hModel);
addModel("V Gap", vModel);
addModel("H Border", hBorderModel);
addModel("V Border", vBorderModel);
}
return gui;
}
private void addModel(String label, SpinnerNumberModel model) {
buttonPanel.add(new JLabel(label));
final JSpinner spinner = new JSpinner(model);
spinner.addChangeListener(changeListener);
buttonPanel.add(spinner);
}
private void adjustWhiteSpace(
int hGap, int vGap, int hBorder, int vBorder) {
mainLayout.setHgap(hGap);
mainLayout.setVgap(vGap);
buttonLayout.setHgap(hGap);
gui.setBorder(new EmptyBorder
(vBorder, hBorder, vBorder, hBorder));
Container c = gui.getTopLevelAncestor();
if (c instanceof Window) {
Window w = (Window) c;
w.pack();
}
}
public static void main(String[] args) {
Runnable r = () -> {
WhiteSpace ws = new WhiteSpace();
Container gui1 = ws.getGui();
JFrame f = new JFrame("White (OK Red) Space");
f.add(gui1);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);
f.setResizable(false);
f.pack();
f.setVisible(true);
};
SwingUtilities.invokeLater(r);
}
}
When you use BoxLayout, Box.createVerticalGlue() method can help you to make some white space.
Another method is BorderFactory.createEmptyBorder(int top, int left, int bottom, int right). It can help you to make some white space around component.
Thanks for Andrew Thompson's remind.I've revised BoxLayout in recent days and I find that Box.createVerticalGlue() can add some white space depend on the panel's size and you can not set the explicit pixel value of the length of white space.But Box.createVerticalStrut() can do that. Here is a MCTaRE and show the effect of those two methods.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
public class WhiteSpace extends JFrame{
static WhiteSpace whiteSpace;
DemoPanel demoPanel;
boolean withGlue;
JSpinner spinner;
public WhiteSpace(){
initialWindow();
demoPanel = new DemoPanel();
ActionPanel actionPanel = new ActionPanel();
setLayout(new BorderLayout());
getContentPane().add(actionPanel,BorderLayout.NORTH);
getContentPane().add(demoPanel,BorderLayout.CENTER);
setVisible(true);
}
public void initialWindow(){
setSize(220, 300);
setTitle("White Space");
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
//Show the window in the middle of the screen
}
/**
* #param args
*/
public static void main(String[] args) {
Runnable runnable = new Runnable() {
#Override
public void run() {
whiteSpace = new WhiteSpace();
}
};
SwingUtilities.invokeLater(runnable);
}
class DemoPanel extends JPanel{
//Show the vertical white space between label1 and label2
JLabel label1;
JLabel label2;
public void initialDemoPanel(){
setBorder(BorderFactory.createTitledBorder(getBorder(), "DemoPanel", TitledBorder.LEADING, TitledBorder.TOP, new Font("Default",Font.PLAIN,10), Color.gray));
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
label1 = new JLabel("This is first line");
label2 = new JLabel("This is second line");
}
public DemoPanel(){
initialDemoPanel();
add(label1);
if(withGlue){
add(Box.createVerticalGlue());
}
add(label2);
}
public DemoPanel(int strutValue){
initialDemoPanel();
add(label1);
add(Box.createVerticalStrut(strutValue));
add(label2);
}
}
class ActionPanel extends JPanel{
public ActionPanel(){
setBorder(BorderFactory.createTitledBorder(getBorder(), "ActionPanel", TitledBorder.LEADING, TitledBorder.TOP, new Font("Default",Font.PLAIN,10), Color.gray));
setLayout(new BoxLayout(this,BoxLayout.X_AXIS));
JRadioButton glueButton = new JRadioButton("With Glue");
glueButton.addActionListener(new glueButtonListener());
add(glueButton);
add(Box.createHorizontalStrut(10));
//To create horizontal white space
JLabel strutLabel = new JLabel("Strut Value");
add(strutLabel);
spinner = new JSpinner(new SpinnerNumberModel(0,0,50,1));
spinner.addChangeListener(new spinnerListener());
add(spinner);
//public SpinnerNumberModel(Number value,Comparable minimum,Comparable maximum,Number stepSize)
}
}
class glueButtonListener implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
spinner.setValue(new Integer(0));
withGlue = (withGlue == true ? false:true);
whiteSpace.getContentPane().remove(demoPanel);
demoPanel = new DemoPanel();
whiteSpace.getContentPane().add(demoPanel,BorderLayout.CENTER);
whiteSpace.getContentPane().validate();
}
}
class spinnerListener implements ChangeListener{
#Override
public void stateChanged(ChangeEvent e) {
int strutValue = (Integer) spinner.getValue();
whiteSpace.getContentPane().remove(demoPanel);
demoPanel = new DemoPanel(strutValue);
whiteSpace.getContentPane().add(demoPanel,BorderLayout.CENTER);
whiteSpace.getContentPane().validate();
}
}
}
Box.createHorizontalGlue() and Box.createHorizontalStrut(int height) can be used too. Besides, Box.createRigidArea(Dimension d) has the ability too create white space too.
MigLayout has multiple ways of creating space. (A space is called a gap in this layout.)
Gaps can be created at the highest level with layout constraints, it is possible to
create gaps between rows and column and gaps can be also set between individual
components with component constraints. There are also specific gaps around the borders
of a container called insets which have their own specific keyword to be set.
The following example creates all these kinds of gaps:
package com.zetcode;
import java.awt.EventQueue;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import net.miginfocom.swing.MigLayout;
public class MigLayoutGaps2 extends JFrame {
public MigLayoutGaps2() {
initUI();
setTitle("Gaps");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
private void initUI() {
JPanel base = new JPanel(new MigLayout("flowy, ins 30, gap 15"));
setContentPane(base);
JPanel pnl1 = new JPanel();
pnl1.setBorder(
BorderFactory.createTitledBorder("Grid gaps")
);
pnl1.setLayout(new MigLayout("gap 5 5, ins 10, wrap 3"));
pnl1.add(new JButton("1"));
pnl1.add(new JButton("2"));
pnl1.add(new JButton("3"));
pnl1.add(new JButton("4"));
pnl1.add(new JButton("5"));
pnl1.add(new JButton("6"));
JPanel pnl2 = new JPanel();
pnl2.setBorder(
BorderFactory.createTitledBorder("Column gaps")
);
pnl2.setLayout(new MigLayout("wrap 3", "[]10[]"));
JLabel lbl1 = new JLabel();
lbl1.setBorder(
BorderFactory.createEtchedBorder()
);
JLabel lbl2 = new JLabel();
lbl2.setBorder(
BorderFactory.createEtchedBorder()
);
JLabel lbl3 = new JLabel();
lbl3.setBorder(
BorderFactory.createEtchedBorder()
);
pnl2.add(lbl1, "w 40, h 110");
pnl2.add(lbl2, "w 40, h 110");
pnl2.add(lbl3, "w 40, h 110");
JPanel pnl3 = new JPanel();
pnl3.setBorder(
BorderFactory.createTitledBorder("Row gaps")
);
pnl3.setLayout(new MigLayout("wrap", "", "[]15[]"));
JLabel lbl4 = new JLabel();
lbl4.setBorder(
BorderFactory.createEtchedBorder()
);
JLabel lbl5 = new JLabel();
lbl5.setBorder(
BorderFactory.createEtchedBorder()
);
JLabel lbl6 = new JLabel();
lbl6.setBorder(
BorderFactory.createEtchedBorder()
);
pnl3.add(lbl4, "w 150, h 20");
pnl3.add(lbl5, "w 150, h 20");
pnl3.add(lbl6, "w 150, h 20");
JPanel pnl4 = new JPanel();
pnl4.setBorder(
BorderFactory.createTitledBorder("Component gaps")
);
pnl4.setLayout(new MigLayout());
pnl4.add(new JLabel("Name:"), "gapright 5");
pnl4.add(new JTextField(10), "gapbottom 20, gaptop 20");
base.add(pnl1);
base.add(pnl2);
base.add(pnl3);
base.add(pnl4);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
MigLayoutGaps2 ex = new MigLayoutGaps2();
ex.setVisible(true);
}
});
}
}
We have four panels in the layout. Each of this panels has a MigLayout manager.
JPanel base = new JPanel(new MigLayout("flowy, ins 30, gap 15"));
This line creates container insets and vertical gaps between panels.
pnl1.setLayout(new MigLayout("gap 5 5, ins 10, wrap 3"));
Here we apply gaps for the whole grid structure and also set container gaps.
pnl2.setLayout(new MigLayout("wrap 3", "[]10[]"));
This line creates gaps between columns.
pnl3.setLayout(new MigLayout("wrap", "", "[]15[]"));
Row gaps are defined with this code.
pnl4.add(new JLabel("Name:"), "gapright 5");
pnl4.add(new JTextField(10), "gapbottom 20, gaptop 20");
Finally, it is possible to create gaps between individual components.
JGoodies FormLayout.
Author Karsten Lentzsch has a collection of presentations on UI design. In particular this PDF speaks to the need for aesthetic whitespace. Adding meaningful space while also paying attention to clutter separates the wheat from the chaff.
Whenever I have this issue, I just use JPanels. For example in a GridLayout:
JFrame frame = new JFrame;
frame.setLayout(new GridLayout(2, 0));
//We want the bottom left to be blank
frame.add(new JLabel("Top Left"));
frame.add(new JLabel("Top Right"));
//This is the position we want empty
frame.add(new JPanel());
//Now we can continue with the rest of the script

Categories

Resources