Reputation: 493
I've found myself writing up quite a few programs recently which all need to display some collection of data. So far the best looking approach I've thought of is make small JPanels which contain data on each item in the collection and put them all in a big JPanel which I then put in a JScrollPane. It works and looks just as intended but there's one issue: I can't seem to get the smaller JPanels to start at the top of the bigger JPanel.
The problem is only apparent when I've got a small number of small JPanels (green) added into the bigger JPanel (red).
Described below is the method I used to produce the above and I'd like to know if there's a better way I could do it (where the list starts at the top like it should):
I created a class which extends JPanel and in it add all data I want to display. We'll call it "SmallPanel.java". I don't set the size of it (that comes later).
In my main window's class (which extends JFrame):
private JScrollPane scrollPane;
private JPanel panel;
...
scrollPane = new JScrollPane();
getContentPane().add(scrollPane);
panel = new JPanel();
panel.setLayout(new GridBagLayout());
scrollPane.setViewportView(panel);
...
private void addPanel()
{
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = panel.getComponentCount(); //The new JPanel's place in the list
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.PAGE_START; //I thought this would do it
gbc.ipady = 130; //Set the panel's height, the width will get set to that of the container JPanel (which is what I want since I'd like my JFrames to be resizable)
gbc.insets = new Insets(2, 0, 2, 0); //Separation between JPanels in the list
gbc.weightx = 1.0;
SmallPanel smallPanel = new SmallPanel();
panel.add(smallPanel, gbc);
panel.revalidate();
panel.invalidate();
panel.repaint(); //Better safe than peeved
}
Call the addPanel() method every time I want to add a panel.
EDIT
Final solution (based on MadProgrammer's answer below):
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.BevelBorder;
public class ListPanel extends JPanel
{
private static final long serialVersionUID = 1L;
private JPanel fillerPanel;
private ArrayList<JPanel> panels;
public ListPanel(List<JPanel> panels, int height)
{
this(panels, height, new Insets(2, 0, 2, 0));
}
public ListPanel(List<JPanel> panels, int height, Insets insets)
{
this();
for (JPanel panel : panels)
addPanel(panel, height, insets);
}
public ListPanel()
{
super();
this.fillerPanel = new JPanel();
this.fillerPanel.setMinimumSize(new Dimension(0, 0));
this.panels = new ArrayList<JPanel>();
setLayout(new GridBagLayout());
}
public void addPanel(JPanel p, int height)
{
addPanel(p, height, new Insets(2, 0, 2, 0));
}
public void addPanel(JPanel p, int height, Insets insets)
{
super.remove(fillerPanel);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = getComponentCount();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.PAGE_START;
gbc.ipady = height;
gbc.insets = insets;
gbc.weightx = 1.0;
panels.add(p);
add(p, gbc);
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = getComponentCount();
gbc.fill = GridBagConstraints.VERTICAL;
gbc.weighty = 1.0;
add(fillerPanel, gbc);
revalidate();
invalidate();
repaint();
}
public void removePanel(JPanel p)
{
removePanel(panels.indexOf(p));
}
public void removePanel(int i)
{
super.remove(i);
panels.remove(i);
revalidate();
invalidate();
repaint();
}
public ArrayList<JPanel> getPanels()
{
return this.panels;
}
public static void main(String[] args)
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setMinimumSize(new Dimension(500, 500));
f.setLocationRelativeTo(null);
f.getContentPane().setLayout(new BorderLayout());
final ListPanel listPanel = new ListPanel();
for (int i = 1; i <= 10; i++)
listPanel.addPanel(getRandomJPanel(), new Random().nextInt(50) + 50);
JButton btnAdd = new JButton("Add");
btnAdd.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent paramActionEvent)
{
listPanel.addPanel(getRandomJPanel(), new Random().nextInt(50) + 50);
}
});
JButton btnRemove = new JButton("Remove");
btnRemove.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent paramActionEvent)
{
listPanel.removePanel(0);
}
});
f.getContentPane().add(btnRemove, BorderLayout.NORTH);
f.getContentPane().add(btnAdd, BorderLayout.SOUTH);
JScrollPane scrollPane = new JScrollPane();
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setViewportView(listPanel);
f.getContentPane().add(scrollPane, BorderLayout.CENTER);
f.setVisible(true);
}
public static JPanel getRandomJPanel()
{
JPanel panel = new JPanel();
panel.setBorder(new BevelBorder(BevelBorder.LOWERED, null, null, null, null));
panel.add(new JLabel("This is a randomly sized JPanel"));
panel.setBackground(new Color(new Random().nextFloat(), new Random().nextFloat(), new Random().nextFloat()));
return panel;
}
}
Upvotes: 2
Views: 2780
Reputation: 324147
You can use a vertical BoxLayout.
Just make sure the maximum size of the panel is equal to the preferred size so the panel doesn't grow.
Edit:
Since your class already has a custom panel all you need to do is override the getMaximumSize() method to return an appropriate value. Something like:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class VerticalLayoutExample2 {
public static void main(String[] args) {
new VerticalLayoutExample2();
}
public VerticalLayoutExample2() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
final TestPane pane = new TestPane();
JButton add = new JButton("Add");
add.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
pane.addAnotherPane();
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(pane));
frame.add(add, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JPanel filler;
private int y = 0;
public TestPane() {
setBackground(Color.RED);
setLayout(new GridBagLayout());
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setBorder( new EmptyBorder(4, 4, 4, 4) );
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 400);
}
public void addAnotherPane() {
SmallPanel panel = new SmallPanel();
panel.setLayout( new GridBagLayout() );
panel.add(new JLabel("Hello"));
add(panel);
add(Box.createVerticalStrut(4));
revalidate();
repaint();
}
}
static class SmallPanel extends JPanel
{
@Override
public Dimension getMaximumSize()
{
Dimension preferred = super.getPreferredSize();
Dimension maximum = super.getMaximumSize();
maximum.height = preferred.height;
return maximum;
}
}
}
I know you mentioned you don't want to use a lib, but you can also look at Relative Layout. It is only a single class. It can easily mimic a BoxLayout but is easier to use because you don't need to override the getMaximumSize() method or add a Box component to the panel to give the vertical spacing.
You would set it as the layout of your panel as follow:
RelativeLayout rl = new RelativeLayout(RelativeLayout.Y_AXIS);
rl.setFill( true ); // fills components horizontally
rl.setGap(4); // vertical gap between panels
yourPanel.setLayout(rl);
yourPanel.add( new SmallPanel(...) );
yourPanel.add( new SmallPanel(...) );
Upvotes: 2
Reputation: 347314
The best solution I've found is to use VerticalLayout
from the SwingLabs SwingX (which can be downloaded from here) libraries.
You "could" use a GridBagLayout
with an invisible component positioned at the end, whose weighty
property is set to 1
, but this is a lot more additional work to manage, as you need to keep updating the x/y positions of all the components to keep it in place...
Updated with GridBagLayout
example
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class VerticalLayoutExample {
public static void main(String[] args) {
new VerticalLayoutExample();
}
public VerticalLayoutExample() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
final TestPane pane = new TestPane();
JButton add = new JButton("Add");
add.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
pane.addAnotherPane();
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(pane));
frame.add(add, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JPanel filler;
private int y = 0;
public TestPane() {
setBackground(Color.RED);
setLayout(new GridBagLayout());
filler = new JPanel();
filler.setOpaque(false);
GridBagConstraints gbc = new GridBagConstraints();
gbc.weighty = 1;
gbc.gridy = 0;
add(filler, gbc);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 400);
}
public void addAnotherPane() {
JPanel panel = new JPanel(new GridBagLayout());
panel.add(new JLabel("Hello"));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridy = y++;
gbc.weightx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(4, 4, 4, 4);
add(panel, gbc);
GridBagLayout gbl = ((GridBagLayout)getLayout());
gbc = gbl.getConstraints(filler);
gbc.gridy = y++;
gbl.setConstraints(filler, gbc);
revalidate();
repaint();
}
}
}
This is just a concept. As camickr has pointed out, so long as you know the last component, you can adjust the GridBagConstraints
of the component so that the last component which is in the list has the weighty
of 1
instead...
As you can, you can override some of the things GridBagLayout
does, for example, instead of using the preferred size of the panel, I've asked GridBagLayout
to make it fill the HORIZONTAL
width of the parent container...
Upvotes: 2