I want to place some elements inside a JFrame by setLocation(x, y) but my frame is not the size I expect.
The size of my JFrame is 600 x 400 but when I run the app it looks like a JFrame of about 550 x 350.
I would like to understand why this happens, even if there are better ways to implement it.
Thanks in advance for any assistance.
My code:
public static void main(String[] args) {
JFrame mainFrame = new JFrame("Example");
mainFrame.setVisible(true);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.setSize(600, 400);
mainFrame.setLayout(null);
mainFrame.setResizable(false);
JPanel p1 = new JPanel();
p1.setSize(100, 100);
p1.setLocation(0,0);
p1.setBackground(Color.BLUE);
mainFrame.add(p1);
JPanel p2 = new JPanel();
p2.setSize(100, 100);
p2.setLocation(500,300);
p2.setBackground(Color.BLUE);
mainFrame.add(p2);
}
Output:
A frame's visible content size is the frame size minus the frame's decorations insets.
So your viewable area will be 600x400 - frameDecorationInsets, and this will be variable (even across the same platform).
Instead, you should be looking at the size of the parent container, for example
Note, I'm running on MacOS, which has a different frame decoration then Windows, but this concept will still work on both ... and you get resizability
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
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 {
private JPanel p1 = new JPanel();
private JPanel p2 = new JPanel();
public TestPane() {
setLayout(null);
p1.setBackground(Color.BLUE);
add(p1);
p2.setBackground(Color.BLUE);
add(p2);
}
#Override
public void doLayout() {
super.doLayout();
p1.setSize(100, 100);
p1.setLocation(0, 0);
p2.setSize(100, 100);
p2.setLocation(getWidth() - p2.getWidth(), getHeight() - p2.getHeight());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(600, 400);
}
}
}
But...
Arguably, you shouldn't be using null layouts, you should be relying on things like JComponent#getPreferredSize and JFrame#pack to provide suitable hints about how best you want the window to be sized by default, for example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
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() {
JPanel p1 = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
};
p1.setBackground(Color.BLUE);
JPanel p2 = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
};
p2.setBackground(Color.BLUE);
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.anchor = GridBagConstraints.NORTHWEST;
add(p1, gbc);
gbc.gridx = 1;
gbc.gridy = 1;
gbc.anchor = GridBagConstraints.SOUTHEAST;
add(p2, gbc);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(600, 400);
}
}
}
Related
I'm starting with swing, I have some questions about how to align elements within a GridBagLayout, I'm not sure either whether this is the correct approach, please advice.
I have the below code
import javax.swing.*;
import java.awt.*;
public class App {
public void start() {
JPanel mainPanel = new JPanel(new FlowLayout());
mainPanel.setBorder(BorderFactory.createLineBorder(Color.CYAN, 20));
//buttons for initial options
JButton button1 = new JButton("This is option A");
JButton button2 = new JButton("option B");
JButton button3 = new JButton("Another text");
JPanel second = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.CENTER;
second.setBackground(Color.GREEN);
second.add(button1, gbc);
second.add(button2, gbc);
second.add(button3, gbc);
mainPanel.add(second, BorderLayout.CENTER);
//frame configuration
JFrame frame = new JFrame();
frame.setContentPane(mainPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setResizable(false);
}
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
SwingUtilities.invokeLater(() -> new App().start());
}
}
My goal is to produce the following output:
So far I have tried with BoxLayout with vertical alignment and it works but the problem is that it overwrites the preferred sized of the buttons and I want them all to be the same width.
Also, I tried with GridLayout and BorderLayout adding the elements to NORTH, CENTER, and SOUTH but the sizes of the buttons change.
What is the recommended way to center the elements but keeping their dimensions?
I would nest layouts:
A JPanel that holds the buttons and uses a new GridLayout(0, 1, 0, vGap) -- a grid that holds one column and variable number of rows, with a vGap gap between buttons.
Add that JPanel into another JPanel that uses GridBagLayout, and add it in a default way (no GridBagConstraints) which will center the first JPanel into the second. This would obviously have to somehow be the size desired. This can be achieved by either
overriding getPreferredSize() in a sensible way
Calling setPreferredSize(new Dimension(someWidth, someHeight)) -- this isn't quite as "clean"
Giving this a border, specifically a BorderFactor.EmptyBorder(gap, gap, gap, gap) where gap is the size of the border around the JPanel...
Done.
Test code that uses the GridBagLayout:
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import javax.swing.*;
public class ButtonLayout extends JPanel {
public static final int MY_WIDTH = 750;
public static final int MY_HEIGHT = 500;
private static final float BTN_SIZE = 24f;
private String[] buttonTexts = {"This is Option A", "Option B",
"Something Else Entirely"};
public ButtonLayout() {
int colGap = 20;
JPanel buttonPanel = new JPanel(new GridLayout(0, 1, 0, colGap));
for (String btnText : buttonTexts) {
JButton button = new JButton(btnText);
// set first letter of text as mnemonic (alt-char shortcut)
int mnemonic = (int) btnText.charAt(0);
button.setMnemonic(mnemonic);
// make button bigger by increasing its font
button.setFont(button.getFont().deriveFont(BTN_SIZE));
// add to the GridLayout-using JPanel
buttonPanel.add(button);
}
// set layout of main panel to GridBag
setLayout(new GridBagLayout());
// add the button panel in a "default" manner (no constraints)
// which centers this panel
add(buttonPanel);
}
#Override
public Dimension getPreferredSize() {
Dimension superSize = super.getPreferredSize();
int width = Math.max(MY_WIDTH, superSize.width);
int height = Math.max(MY_HEIGHT, superSize.height);
return new Dimension(width, height);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
ButtonLayout mainPanel = new ButtonLayout();
JFrame frame = new JFrame("ButtonLayout");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Example 2 that uses EmptyBorder:
import java.awt.GridLayout;
import javax.swing.*;
#SuppressWarnings("serial")
public class ButtonLayout extends JPanel {
public static final int MY_WIDTH = 750;
public static final int MY_HEIGHT = 500;
private static final float BTN_SIZE = 24f;
private String[] buttonTexts = {"This is Option A", "Option B",
"Something Else Entirely"};
public ButtonLayout() {
int colGap = 20;
JPanel buttonPanel = new JPanel(new GridLayout(0, 1, 0, colGap));
for (String btnText : buttonTexts) {
JButton button = new JButton(btnText);
// set first letter of text as mnemonic (alt-char shortcut)
int mnemonic = (int) btnText.charAt(0);
button.setMnemonic(mnemonic);
// make button bigger by increasing its font
button.setFont(button.getFont().deriveFont(BTN_SIZE));
// add to the GridLayout-using JPanel
buttonPanel.add(button);
}
add(buttonPanel);
int top = 60;
int left = top;
int bottom = 2 * top;
int right = left;
setBorder(BorderFactory.createEmptyBorder(top, left, bottom, right));
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
ButtonLayout mainPanel = new ButtonLayout();
JFrame frame = new JFrame("ButtonLayout");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
I'm not sure I completely understand the issue, but if you want to vertically align the buttons, BUT allow them to keep their preferred size, just don't provide any kind of fill constraint, for example
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SoTest {
public static void main(String[] args) {
new SoTest();
}
public SoTest() {
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.gridwidth = GridBagConstraints.REMAINDER;
add(new JButton("This is option A"), gbc);
add(new JButton("Option B"), gbc);
add(new JButton("Another button"), gbc);
}
}
}
Or, if you want them to have the same width, use a fill constraint
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SoTest {
public static void main(String[] args) {
new SoTest();
}
public SoTest() {
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.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.BOTH;
add(new JButton("This is option A"), gbc);
add(new JButton("Option B"), gbc);
add(new JButton("Another button"), gbc);
}
}
}
If you want to mix a more complex layout, then you should consider making use of compound layouts
But wait, there's no outline...
So, a number of ways you "might" be able to do this, for example, you could use a CompoundBorder....
setBorder(new CompoundBorder(new LineBorder(Color.CYAN, 16), new EmptyBorder(32, 32, 32, 32)));
But the devil is in the detail
I am trying to draw graphics on panels, but I am not sure how I would go about doing that.
I have tried creating a class that extends the JPanel and overrided paintComponent, and some other methods, but nothing gets rendered at all.
Here is my code:
edit:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Lesson1 extends JFrame {
private static final long serialVersionUID = -198253288329146091L;
private JPanel contentPane;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Lesson1 frame = new Lesson1();
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Lesson1() {
contentPane = new JPanel();
setContentPane(contentPane);
JPanel panel = new JPanel() {
private static final long serialVersionUID = -5974584127539186578L;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillRect(0, 0, 500, 500);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
};
contentPane.add(panel);
JPanel panel_1 = new JPanel() {
private static final long serialVersionUID = 123456789L;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.PINK);
g.fillRect(0, 0, 200, 200);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
};
panel.setLayout(new BorderLayout());
panel_1.setLayout(new BorderLayout());
panel.add(panel_1);
}
}
I am not sure what I am doing wrong here, but I have tried everything I could. Thanks!
Mistake #1
contentPane.setLayout(null);
and
panel.setLayout(null);
Before you do anything else, you need to learn and understand what the layout managers actually do and why null layouts are naive and not recommended (especially when you're just learning).
Start by taking the time to go through Laying Out Components Within a Container
Remove contentPane.setLayout(null); and change panel.setLayout(null); to panel.setLayout(new BorderLayout()); and you will see an immediate change.
In fact, you really don't need panel, as the default contentPane of a JFrame is already setup to use BorderLayout.
This means you can get rid of panel.setBounds(73, 52, 231, 143);
Mistake #2
Okay, this is more of an oversight, but, when you start customising components, you need to provide sizing hints, which allows the layout managers to make decisions about how best to layout your component in relationship to other components and based on it's own internal rules.
To this end, you should, at the very least, override the getPreferredSize method (avoid the setXxxSize methods as these can lead to unexpected results if used incorrectly)
Add...
#Override
public Dimension getPreferredSize() {
return new Dimension(1001, 1001);
}
to panel_1
This now means you can do something more like...
Lesson1 frame = new Lesson1();
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
and the window will pack it self around the component (and be centred on the screen) and you can get rid of setBounds(100, 100, 450, 300);
Alright, anyways I tried but have no luck making it so I can set panels in a specific position :/
Layout management in a GUI environment is part science, part voodoo, part black magic
Pixel perfect positioning is an illusion in modern UI development. There are so many factors which go into determining "how" a component should be positioned on a single platform, let along across multiple platforms, you'd spend the rest of your live constantly adding "hacks" to account for the infinite number of possible edge case.
This is why any decent UI framework abstracts the concept, Swing uses the layout manager API for this purpose, iOS (and MacOS) use Auto layout, etc...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Lesson1 {
/**
* Launch the application.
*/
public static void main(String[] args) {
new Lesson1();
}
public Lesson1() {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.NORTHWEST;
add(new RectPane(), gbc);
gbc.gridx = 2;
gbc.anchor = GridBagConstraints.NORTHEAST;
add(new RectPane(), gbc);
gbc.gridx = 1;
gbc.gridy = 1;
gbc.anchor = GridBagConstraints.CENTER;
gbc.weightx = 1;
gbc.weighty = 1;
add(new RectPane(), gbc);
gbc.weightx = 0;
gbc.weighty = 0;
gbc.gridx = 0;
gbc.gridy = 2;
gbc.anchor = GridBagConstraints.SOUTHWEST;
add(new RectPane(), gbc);
gbc.gridx = 2;
gbc.anchor = GridBagConstraints.SOUTHEAST;
add(new RectPane(), gbc);
}
// This is just to demonstrate how the layout works when the avaliable
// space is larger then the desired space, you don't need this
// and should get rid of it and just let the layout manager calculate
// it's desired size based on it's content
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
}
public class RectPane extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillRect(0, 0, 100, 100);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
}
}
Understand, GridBagLayout IS the most complex layout manager in the standard API, but it is the most flexible. You're also not confined to a single layout manager, you can use compound layouts to create very advanced interfaces
I've got a probelm with my swing ui lately. Everything works fine,untill i trigger a tooltip from a JButton.After that moving the mouse over the rest of the ui is causing weird artifacts and glitching.
Bugged:
I can't show the whole code because its too much but here im initialising the button :
GridBagConstraints bottompane_gbc = new GridBagConstraints();
toggleTorConnectionButton = new JButton();
toggleTorConnectionButton.setToolTipText("Toggles Tor Connection.");
toggleTorConnectionButton.setIcon(new ImageIcon(ResourceHandler.Menueicon3_1));
toggleTorConnectionButton.setMinimumSize(new Dimension(removeFinishedDownloads.getMinimumSize().width, toggleTorConnectionButton.getIcon().getIconHeight()+5));
toggleTorConnectionButton.addActionListener(); // unimportant
bottompane_gbc.gridy = 1;
bottompane_gbc.fill = GridBagConstraints.BOTH;
bottompane_gbc.insets = new Insets(0,15,10,5);
bottompane.add(ToggleTorConnectionButton,bottompane_gbc);
this.add(bottompane,BorderLayout.PAGE_END);
If anybody needs more information to help me pls feel free to ask.Im kind of desperated. XD
EDIT:
After some tinkering im guessing that the problem is related to swing and my use of it.Currently im using alot of Eventlisteners (is this bad?), that might slow down the awt thread ?
Here is a brief extract from HPROF:
http://www.pastebucket.com/96444
EDIT 2:
I was able to recreate the error in a handy and simple example. When you move over the button,wait for the tooltip and then over the ui.You will see ghosting :(.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
public class Main_frame {
public static void main(String[] args) {
new Main_frame();
}
public Main_frame() {
JFrame frame = new JFrame("LOL");
frame.setFocusable(true);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(400, 500));
frame.setLocationRelativeTo(null);
Download_window download_window = new Download_window();
JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.addTab("Download", null, download_window, "Main Download Window.");
for (int i = 0; i < 5; i++) {
JPanel pane = new JPanel();
Dimension dim = new Dimension(370, 60);
pane.setPreferredSize(dim);
pane.setMaximumSize(dim);
pane.setBackground(Color.blue);
pane.setMinimumSize(dim);
download_window.jobpanel.add(pane);
}
download_window.jobpanel.repaint();
download_window.jobpanel.revalidate();
frame.add(tabbedPane);
frame.setVisible(true);
}
public class Download_window extends JPanel {
JPanel jobpanel;
public Download_window() {
this.setLayout(new BorderLayout());
jobpanel = new JPanel();
jobpanel.setLayout(new BoxLayout(jobpanel, BoxLayout.Y_AXIS));
JPanel bottompane = new JPanel();
bottompane.setPreferredSize(new Dimension(385, 40));
JButton toggleTorConnectionButton = new JButton();
toggleTorConnectionButton.setPreferredSize(new Dimension(100, 50));
toggleTorConnectionButton.setToolTipText("Toggles Tor Connection.");
bottompane.add(toggleTorConnectionButton);
this.add(bottompane, BorderLayout.PAGE_END);
JScrollPane jobScrollPane = new JScrollPane(jobpanel);
jobScrollPane.getVerticalScrollBar().setUnitIncrement(16);
this.add(jobScrollPane, BorderLayout.CENTER);
}
}
}
Edit 3: Concerning trashgods ideas, I used the EventDispatchThread, I modified the setter to override the getter for size and i crossed out incompatibility by using trashgods code and it was working fine.... So where is the actual difference?
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
public class Main_frame {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Main_frame();
}
});
}
public Main_frame() {
JFrame frame = new JFrame("LOL");
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(400, 500));
Download_window download_window = new Download_window();
JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.addTab("Download", null, download_window, "Main Download Window.");
frame.add(tabbedPane);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public class Download_window extends JPanel {
JPanel jobpanel;
public Download_window() {
this.setLayout(new BorderLayout());
jobpanel = new JPanel();
jobpanel.setLayout(new BoxLayout(jobpanel, BoxLayout.Y_AXIS));
for (int i = 0; i < 5; i++) {
JPanel pane = new JPanel(){
#Override
public Dimension getPreferredSize() {
return new Dimension(370, 60);
}
#Override
public Dimension getMaximumSize() {
return new Dimension(370, 60);
}
#Override
public Dimension getMinimumSize() {
return new Dimension(370, 60);
}
};
pane.setBackground(Color.blue);
jobpanel.add(pane);
}
JPanel bottompane = new JPanel(){
#Override
public Dimension getPreferredSize() {
return new Dimension(385, 40);
}
};
JButton toggleTorConnectionButton = new JButton("Button"){
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 30);
}
};
toggleTorConnectionButton.setToolTipText("Toggles Tor Connection.");
bottompane.add(toggleTorConnectionButton);
this.add(bottompane, BorderLayout.PAGE_END);
JScrollPane jobScrollPane = new JScrollPane(jobpanel);
jobScrollPane.getVerticalScrollBar().setUnitIncrement(16);
this.add(jobScrollPane, BorderLayout.CENTER);
}
}
}
Could anyone please verify that strange behavior himself? You just need to copy&paste the code from above in Edit3.
Your code exhibits none of the glitches shown above when run on my platform.
Verify that you have no painting problems e.g. neglecting super.paintComponent() as discussed here.
Verify that you have no driver incompatibilities, as discussed here.
Construct and modify all GUI objects on the event dispatch thread.
Don't use set[Preferred|Maximum|Minimum]Size() when you really mean to override get[Preferred|Maximum|Minimum]Size(), as discussed here. The example below overrides getPreferredSize() on the scroll pane, but you can implement Scrollable, as discussed here.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
/** #see https://stackoverflow.com/a/34319260/230513 */
public class MainFrame {
private static final int H = 64;
public static void main(String[] args) {
EventQueue.invokeLater(() -> new MainFrame());
}
public MainFrame() {
JFrame frame = new JFrame("LOL");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTabbedPane tabbedPane = new JTabbedPane();
JPanel panel = new JPanel(new GridLayout(0, 1, 5, 5));
for (int i = 0; i < 8; i++) {
panel.add(new DownloadPanel());
}
JScrollPane jsp = new JScrollPane(panel) {
#Override
public Dimension getPreferredSize() {
return new Dimension(6 * H, 4 * H);
}
};
tabbedPane.addTab("Download", null, jsp, "Main Download Window.");
tabbedPane.addTab("Options", null, null, "Options");
frame.add(tabbedPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static class DownloadPanel extends JPanel {
JPanel jobPanel = new JPanel();
public DownloadPanel() {
this.setLayout(new BorderLayout());
this.setBackground(Color.lightGray);
JProgressBar jpb = new JProgressBar();
jpb.setIndeterminate(true);
this.add(jpb);
JPanel buttonPane = new JPanel();
JButton toggleTorConnectionButton = new JButton("Button");
toggleTorConnectionButton.setToolTipText("Toggles Tor Connection.");
buttonPane.add(toggleTorConnectionButton);
this.add(buttonPane, BorderLayout.WEST);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(4 * H, H);
}
}
}
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;
}
}
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...
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(...) );
I want to have a JFrame, where on the left and the right there is a border that has the color black and a width of withfOfJFrame/10.
Now, my try at it looks like this:
JFrame f = new JFrame();
f.setSize(800, 600);
f.setLayout(new BorderLayout());
JPanel leftBorder = new JPanel();
JPanel rightBorder = new JPanel();
leftBorder.setBackground(Color.black);
rightBorder.setBackground(Color.black);
leftBorder.setSize(f.getWidth()/10, f.getHeight());
rightBorder.setSize(f.getWidth()/10, f.getHeight());
JPanel center = new JPanel();
center.setBackground(Color.red);
f.add(leftBorder, BorderLayout.WEST);
f.add(center, BorderLayout.CENTER);
f.add(rightBorder, BorderLayout.EAST);
f.setVisible(true);
This adds a black border on the left and the right, but that border has a fixed size and doesn't recalculate when resizing the window. The size isn't even 1/10 of 800 (the beginning width of the JFrame).
What am I doing wrong? Or is there even a better way to do this?
You may achieve the desired result with a GridBagLayout and appropriate weights:
public class Snippet {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JPanel leftBorder = new JPanel();
JPanel rightBorder = new JPanel();
leftBorder.setBackground(Color.black);
rightBorder.setBackground(Color.black);
JPanel center = new JPanel();
center.setBackground(Color.red);
f.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.weighty = 1.0;
gbc.gridy = 0;
gbc.gridwidth = 1;
gbc.gridheight = 1;
gbc.gridx = 0;
gbc.weightx = 0.1;
f.add(leftBorder, gbc);
gbc.gridx = 1;
gbc.weightx = 0.8;
f.add(center, gbc);
gbc.gridx = 2;
gbc.weightx = 0.1;
f.add(rightBorder, gbc);
f.pack();
f.setVisible(true);
}
});
}
}
A ComponentListener works well for this.
Addendum: The Flank panel needs to revalidate() itself explicitly in response to any size change.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
/** #see http://stackoverflow.com/a/14588506/230513 */
public class Test {
private static final int W = 320;
private static final Center center = new Center();
private static class Center extends JPanel {
public Center() {
setBackground(Color.red);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(W, 3 * W / 4);
}
};
private static class Flank extends JPanel {
private Dimension d = new Dimension(W / 10, 0);
public Flank() {
setBackground(Color.black);
Test.center.addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
d = new Dimension(e.getComponent().getWidth() / 10, 0);
revalidate();
}
});
}
#Override
public Dimension getPreferredSize() {
return d;
}
}
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new Flank(), BorderLayout.WEST);
f.add(center, BorderLayout.CENTER);
f.add(new Flank(), BorderLayout.EAST);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test().display();
}
});
}
}