Resizing of multiple JPanels to fit window - java

I'm pretty new to the Java Swing toolkit and have created a series of JPanel with JLabel components corresponding to each panel's index and value. However I can’t seem to make the panels correctly resize, when the window is too small to resize all the panels at once. Resulting in the an extra red margin to the right of all the panels (See. Window).
Is there a way to make the Sub Panels/Spacer Panels resize in such a way that does not allow for the extra margin? Or setup a better swing layout to handle such resizing?
Code demo:
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.BoxLayout;
public class Frame extends JFrame {
private int numBox = 20;
public class SubPanel extends JPanel {
public SubPanel(int i) {
setMinimumSize(new Dimension(20, 20));
setMaximumSize(new Dimension(1000, i*30));
setBackground(Color.BLACK);
setLayout(new BorderLayout());
setAlignmentY(Component.BOTTOM_ALIGNMENT);
JLabel labelValue = new JLabel(String.valueOf(30 * i), JLabel.CENTER);
labelValue.setForeground(Color.WHITE);
add(labelValue, BorderLayout.NORTH);
JLabel labelIndex = new JLabel(String.valueOf(i), JLabel.CENTER);
labelIndex.setForeground(Color.WHITE);
add(labelIndex, BorderLayout.SOUTH);
}
}
public class SpacerPanel extends JPanel {
public SpacerPanel() {
setBackground(Color.WHITE);
setMaximumSize(new Dimension(3, 10000));
setMinimumSize(new Dimension(3, 0));
}
}
public Frame(String title) {
super(title);
JPanel mainPanel = new JPanel();
mainPanel.setBackground(Color.RED);
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.LINE_AXIS));
for (int i = 1; i < numBox + 1; i++) {
mainPanel.add(new SpacerPanel());
mainPanel.add(new SubPanel(i));
}
mainPanel.add(new SpacerPanel());
add(mainPanel);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
new Frame("Frame").setSize(1200, 700);
}
}

If you have 40 components and you resize the frame by a few pixels, the BoxLayout is having problems allocating those pixels to each of the components.
Instead you can use the Relative Layout which allows you to specify how those pixels should be allocated.
The RelativeLayout also allows you to specify a gap between each component so you don't need to add the SpacerPanel.
However, you will need to modify the logic of the "subPanel". The height of the labels needs to be fixed so they can be painted black. So I created a "labelPanel" to hold the labels.
The restructured code using the RelativeLayout would be:
import java.awt.*;
import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.BoxLayout;
public class Frame extends JFrame {
private int numBox = 20;
public class SubPanel extends JPanel {
public SubPanel(int i) {
// setMinimumSize(new Dimension(20, 20));
// setMaximumSize(new Dimension(1000, i*30));
// setPreferredSize(new Dimension(20, i*30));
setBackground(Color.RED);
setLayout(new BorderLayout());
// setAlignmentY(Component.BOTTOM_ALIGNMENT);
JPanel labelPanel = new JPanel( new BorderLayout() );
labelPanel.setBackground( Color.BLACK );
labelPanel.setPreferredSize(new Dimension(20, i*30));
add(labelPanel, BorderLayout.PAGE_END);
JLabel labelValue = new JLabel(String.valueOf(30 * i), JLabel.CENTER);
labelValue.setForeground(Color.WHITE);
labelPanel.add(labelValue, BorderLayout.NORTH);
JLabel labelIndex = new JLabel(String.valueOf(i), JLabel.CENTER);
labelIndex.setForeground(Color.WHITE);
labelPanel.add(labelIndex, BorderLayout.SOUTH);
}
}
public class SpacerPanel extends JPanel {
public SpacerPanel() {
setBackground(Color.WHITE);
setMaximumSize(new Dimension(3, 10000));
setMinimumSize(new Dimension(3, 0));
}
}
public Frame(String title) {
super(title);
JPanel mainPanel = new JPanel();
// mainPanel.setBackground(Color.RED);
mainPanel.setBackground(Color.WHITE);
// mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.LINE_AXIS));
RelativeLayout rl = new RelativeLayout(RelativeLayout.X_AXIS, 5);
rl.setFill( true );
rl.setRoundingPolicy(RelativeLayout.EQUAL);
rl.setAlignment(RelativeLayout.TRAILING);
mainPanel.setLayout( rl );
Float constraint = new Float(1);
for (int i = 1; i < numBox + 1; i++) {
// mainPanel.add(new SpacerPanel());
// mainPanel.add(new SubPanel(i));
mainPanel.add(new SubPanel(i), constraint);
}
// mainPanel.add(new SpacerPanel());
add(mainPanel);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
new Frame("Frame").setSize(1200, 700);
}
}
Or this approach is closer to your original posting. It still uses the spacer and the height of the black columns will shrink as the frame height is decreased:
import java.awt.*;
import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.BoxLayout;
public class Frame2 extends JFrame {
private int numBox = 20;
public class SubPanel extends JPanel {
public SubPanel(int i) {
// setMinimumSize(new Dimension(20, 20));
// setMaximumSize(new Dimension(1000, i*30));
// setBackground(Color.BLACK);
setBackground(Color.RED);
// setLayout(new BorderLayout());
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
add(Box.createVerticalGlue());
// setAlignmentY(Component.BOTTOM_ALIGNMENT);
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.Y_AXIS));
labelPanel.setBackground( Color.BLACK );
labelPanel.setPreferredSize(new Dimension(50, i*30));
labelPanel.setMaximumSize(new Dimension(1000, labelPanel.getPreferredSize().height));
add(labelPanel, BorderLayout.CENTER);
JLabel labelValue = new JLabel(String.valueOf(30 * i), JLabel.CENTER);
labelValue.setForeground(Color.WHITE);
labelValue.setAlignmentX(JLabel.CENTER_ALIGNMENT);
// add(labelValue, BorderLayout.NORTH);
labelPanel.add(labelValue);
labelPanel.add(Box.createVerticalGlue());
JLabel labelIndex = new JLabel(String.valueOf(i), JLabel.CENTER);
labelIndex.setForeground(Color.WHITE);
labelIndex.setAlignmentX(JLabel.CENTER_ALIGNMENT);
// add(labelIndex, BorderLayout.SOUTH);
labelPanel.add(labelIndex);
}
}
public class SpacerPanel extends JPanel {
public SpacerPanel() {
setBackground(Color.WHITE);
setMaximumSize(new Dimension(3, 10000));
setMinimumSize(new Dimension(3, 0));
}
}
public Frame2(String title) {
super(title);
JPanel mainPanel = new JPanel();
mainPanel.setBackground(Color.RED);
// mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.LINE_AXIS));
RelativeLayout rl = new RelativeLayout(RelativeLayout.X_AXIS, 0);
rl.setFill( true );
rl.setRoundingPolicy(RelativeLayout.EQUAL);
rl.setAlignment(RelativeLayout.TRAILING);
mainPanel.setLayout( rl );
Float constraint = new Float(1);
for (int i = 1; i < numBox + 1; i++) {
mainPanel.add(new SpacerPanel());
// mainPanel.add(new SubPanel(i));
mainPanel.add(new SubPanel(i), constraint);
}
mainPanel.add(new SpacerPanel());
add(mainPanel);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
new Frame2("Frame").setSize(1200, 700);
}
}

Related

How to align vertically buttons of different sizes within a GridBagLayout?

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

How to get the TitledBorder's title to display properly in the GUI

I have the GUI displaying properly for the most part, except for one thing. The TitledBorder("Numeric Type") does not display the entire title for the right JPanel. I believe it has something to do with the BorderLayout Manager. Instead of displaying "Numeric Type" within the border, just "Numeric..." displays. Any help will be greatly appreciated.
public class P3GUI extends JFrame {
private JLabel originalList;
private JTextField originalSort;
private JLabel sortedList;
private JTextField newSort;
private JPanel panel;
private JButton performSort;
private JRadioButton ascending;
private JRadioButton descending;
private ButtonGroup sort;
private JRadioButton integer;
private JRadioButton fraction;
private ButtonGroup numType;
private JPanel inputPanel, outputPanel, calculatePanel, radioPanel;
private JPanel left, right;
public P3GUI () {
super("Binary Search Tree Sort");
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
originalList = new JLabel("Original List");
originalSort = new JTextField(20);
inputPanel = new JPanel();
inputPanel.add(originalList);
inputPanel.add(originalSort);
sortedList = new JLabel("Sorted List");
newSort = new JTextField(20);
newSort.setEditable(false);
outputPanel = new JPanel();
outputPanel.add(sortedList);
outputPanel.add(newSort);
panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(inputPanel);
panel.add(outputPanel);
add(panel, BorderLayout.NORTH);
performSort = new JButton("Perform Sort");
calculatePanel = new JPanel();
calculatePanel.add(performSort);
add(calculatePanel, BorderLayout.CENTER);
ascending = new JRadioButton("Ascending");
descending = new JRadioButton("Descending");
sort = new ButtonGroup();
sort.add(ascending);
sort.add(descending);
integer = new JRadioButton("Integer");
fraction = new JRadioButton("Fraction");
numType = new ButtonGroup();
numType.add(integer);
numType.add(fraction);
radioPanel = new JPanel();
radioPanel.setLayout(new FlowLayout());
left = new JPanel();
left.setLayout(new GridLayout(2,1));
left.setBorder(BorderFactory.createTitledBorder("Sort Order"));
left.add(ascending);
left.add(descending);
right = new JPanel();
right.setLayout(new GridLayout(2,1));
right.setBorder(BorderFactory.createTitledBorder("Numeric Type"));
right.add(integer);
right.add(fraction);
radioPanel.add(left);
radioPanel.add(right);
add(radioPanel, BorderLayout.SOUTH);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new P3GUI().setVisible(true);
}
});
}
}
The problem is that the right JPanel is too small to display the entire title, and so it gets truncated. I'd suggest placing the bottom two JPanels into another that uses GridLayout, and then place them in such a way that they expand to fit the bottom of your GUI. When spread out, the title has a much greater chance of being fully displayed (but not a guarantee, mind you!).
For example, if you make the main GUI use a BorderLayout, and add this GridLayout using JPanel into the BorderLayout.CENTER position, it will fill it completely. Then the top components, the TextFields and JButton can be added to another JPanel, say one that uses a GridBagLayout, and add that to the main JPanel's BorderLayout.PAGE_START position.
For example, the following code produces this GUI:
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.KeyEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class P3GUI2 extends JPanel {
private static final int COLS = 20;
private JTextField originalSort = new JTextField(COLS);
private JTextField newSort = new JTextField(COLS);
private JButton performSort = new JButton("Perform Sort");
private JRadioButton ascending = new JRadioButton("Ascending");
private JRadioButton descending = new JRadioButton("Descending");
private ButtonGroup sort = new ButtonGroup();
private JRadioButton integer = new JRadioButton("Integer");
private JRadioButton fraction = new JRadioButton("Fraction");
private ButtonGroup numType = new ButtonGroup();
public P3GUI2() {
JPanel topPanel = new JPanel(new GridBagLayout());
topPanel.add(new JLabel("Original List:"), createGbc(0, 0));
topPanel.add(originalSort, createGbc(1, 0));
topPanel.add(new JLabel("Sorted List:"), createGbc(0, 1));
topPanel.add(newSort, createGbc(1, 1));
performSort.setMnemonic(KeyEvent.VK_P);
JPanel btnPanel = new JPanel();
btnPanel.add(performSort);
JPanel sortOrderPanel = createTitlePanel("Sort Order");
JPanel numericPanel = createTitlePanel("Numeric Type");
sortOrderPanel.add(ascending);
sortOrderPanel.add(descending);
sort.add(ascending);
sort.add(descending);
numericPanel.add(integer);
numericPanel.add(fraction);
numType.add(integer);
numType.add(fraction);
JPanel radioPanels = new JPanel(new GridLayout(1, 0, 3, 3));
radioPanels.add(sortOrderPanel);
radioPanels.add(numericPanel);
setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
setLayout(new BorderLayout(3, 3));
add(topPanel, BorderLayout.PAGE_START);
add(btnPanel, BorderLayout.CENTER);
add(radioPanels, BorderLayout.PAGE_END);
}
private JPanel createTitlePanel(String title) {
JPanel panel = new JPanel(new GridLayout(0, 1, 3, 3));
panel.setBorder(BorderFactory.createTitledBorder(title));
return panel;
}
private GridBagConstraints createGbc(int x, int y) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = x;
gbc.gridy = y;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = x == 0 ? GridBagConstraints.WEST : GridBagConstraints.EAST;
gbc.insets = new Insets(3, 3, 3, 3);
gbc.weightx = 1.0;
gbc.weighty = 1.0;
return gbc;
}
private static void createAndShowGui() {
P3GUI2 mainPanel = new P3GUI2();
JFrame frame = new JFrame("Binary Search Tree Sort");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
Or you could place the above btnPanel into the main one BorderLayout.CENTER and then place the radioPanels into the main one BorderLayout.PAGE_END. This will display a GUI of the same appearance but it will expand differently if re-sized.
The preferred size of the panel (as determined by the layout manager) does not consider the size of the text in the TitledBorder so the title can get truncated.
Here is a custom JPanel that can be used with a TitleBorder. The getPreferredSize() method has been customized to use the maximum width of:
the default getPreferredSize() calculation
the width of the text in the TitledBorder
Here is a simple example:
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
public class TitledBorderPanel extends JPanel
{
/**
** The preferred width on the panel must consider the width of the text
** used on the TitledBorder
*/
#Override
public Dimension getPreferredSize()
{
Dimension preferredSize = super.getPreferredSize();
Border border = getBorder();
int borderWidth = 0;
if (border instanceof TitledBorder)
{
Insets insets = getInsets();
TitledBorder titledBorder = (TitledBorder)border;
borderWidth = titledBorder.getMinimumSize(this).width + insets.left + insets.right;
}
int preferredWidth = Math.max(preferredSize.width, borderWidth);
return new Dimension(preferredWidth, preferredSize.height);
}
private static void createAndShowGUI()
{
JPanel panel = new TitledBorderPanel();
panel.setBorder( BorderFactory.createTitledBorder("File Options Command List:") );
panel.setLayout( new BoxLayout(panel, BoxLayout.Y_AXIS) );
panel.add( new JLabel("Open") );
panel.add( new JLabel("Close") );
// panel.add( new JLabel("A really wierd file option longer than border title") );
JFrame frame = new JFrame("TitledBorderPanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( panel );
frame.setLocationByPlatform( true );
frame.pack();
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater( () -> createAndShowGUI() );
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}

Placing of JPanels on the JFrames is not correct

I wrote a program to compose a GUI using swing/awt framework for my assignment. So far, I am able to get the pieces working together, but when I put them all into a JFrame, they are not coming out as expected.
I have recently started working on Java GUI framework, and not sure what is missing in my code. How can I get this working properly?
I am also attaching the screen shots (see at the bottom) of the output I am getting.
public class MainFrame extends JFrame {
public MainFrame() {
addComponentsToPane(this.getContentPane());
}
private void addComponentsToPane(Container pane) {
// Set layout
GridBagConstraints gbc = new GridBagConstraints();
this.setTitle("Test tool");
this.setSize(600, 650);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLayout(new GridLayout(2, 1));
// Add video JComponent
mMainPanel = new MainPanel();
pane.add(mMainPanel, 0);
// Add conference screen panel
mFeedPanel = new FeedPanel();
pane.add(mFeedPanel, 1);
// Add a button panel
mButtonPanel = new ButtonPanel();
pane.add(mButtonPanel, 2);
this.setResizable(true);
this.setVisible(true);
this.pack();
}
}
// In actual output, there is 1 screen in this panel.
// mScreen1 is derived from JComponent object.
public class MainPanel() extends JPanel {
public MainPanel() {
addMainPanelComponents();
}
private void addMainPanelComponents() {
this.setSize(352, 240);
setBackground(Color.yellow);
setLayout(new GridLayout(1, 2));
add(mScreen);
setVisible(true);
}
}
// In actual output, there are 3 screens in this panel. I have shown code for 1 screen only
// mScreen1 is derived from JComponent object.
public class FeedPanel extends JPanel {
public FeedPanel() {
addFeedPanelComponents();
}
private void addFeedPanelComponents() {
String img1 = "images/screen1.png";
setSize(352, 150);
setBackground(Color.yellow);
setLayout(new GridLayout(1, 3));
Image image1 = ImageIO.read(new File(img1));
mScreen1.setImage(image1);
add(mScreen1);
setVisible(true);
}
}
public class ButtonPanel extends JPanel {
public ButtonPanel() {
addButtonPanelComponents();
}
private void addButtonPanelComponents() {
this.setSize(352, 150);
this.setBackground(Color.yellow);
this.setLayout(new GridLayout(1,
5));
// Add Button to panel
mStartButton = new JButton("Start");
this.add(mStartButton);
mStartButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
StartButtonActionListener(ae);
}
});
mStopButton = new JButton("Stop");
this.add(mStopButton);
mStopButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
StopButtonActionListener(ae);
}
});
setVisible(true);
}
}
This comes by default on running the code.
This comes after manually resizing the frame.
The combination of BorderLayout , GirdLayout and BoxLayout can do this for you(Actually it's not the only choice).
Here is the code:
import java.awt.BorderLayout;
import java.awt.GridLayout;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class GridLayoutTest {
public void createUI(){
JFrame frame = new JFrame();
JPanel topPanel = new TopPanel();
JPanel buttomPanel = new ButtomPanel();
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
mainPanel.add(topPanel,BorderLayout.CENTER);
mainPanel.add(buttomPanel,BorderLayout.SOUTH);
frame.add(mainPanel,BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
GridLayoutTest test = new GridLayoutTest();
test.createUI();
}
#SuppressWarnings("serial")
class TopPanel extends JPanel{
public TopPanel(){
setLayout(new GridLayout(2, 3));
ImageIcon icon = new ImageIcon("capture.png");
JLabel label1 = new JLabel(icon);
label1.setVisible(false);
JLabel label2 = new JLabel(icon);
JLabel label3 = new JLabel(icon);
label3.setVisible(false);
JLabel label4 = new JLabel(icon);
JLabel label5 = new JLabel(icon);
JLabel label6 = new JLabel(icon);
add(label1);
add(label2);
add(label3);
add(label4);
add(label5);
add(label6);
}
}
#SuppressWarnings("serial")
class ButtomPanel extends JPanel{
public ButtomPanel(){
JButton startButton = new JButton("start");
JButton stopButton = new JButton("stop");
JButton recordButton = new JButton("record");
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(Box.createHorizontalGlue());
add(startButton);
add(Box.createHorizontalStrut(10));
add(stopButton);
add(Box.createHorizontalStrut(10));
add(recordButton);
add(Box.createHorizontalGlue());
}
}
}
BoxLayout is so good too provide white space and help you to center the component.
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(Box.createHorizontalGlue());
add(startButton);
add(Box.createHorizontalStrut(10));
add(stopButton);
add(Box.createHorizontalStrut(10));
add(recordButton);
add(Box.createHorizontalGlue());
Add Glue before the first component and after the last component will help you too center the component and add strut can help you to provide white space you want. you can refer to https://stackoverflow.com/a/22525005/3378204 for more details.
Here is the effect:
The BoxLayout won't affect your component's size. Hope it can help you.
Try this :
public class Main{
private JFrame f;
private JLabel l1, l2, l3,l4;
private JPanel p1, p2, p3;
private JButton b1, b2, b3;
public Main(){
this.f = new JFrame();
this.f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.f.setLayout(new GridLayout(3,1));
this.p1 = new JPanel();
this.p1.setLayout(null)
this.p1.setSize(yoursize);
this.l1 = new JLabel();
this.l1.setBounds(x,y,xspan,yspan);
this.p1.add(l1);
this.p2 = new JPanel();
this.p2.setLayout(new GridLayout(1,3));
this.l2 = new JLabel();
this.l3 = new JLabel();
this.l4 = new JLabel();
this.p2.add(l2);
this.p2.add(l3);
this.p2.add(l4);
this.p3 = new JPanel();
this.p3.setLayout(new GridLayout(1,3));
this.b1 = new JButton();
this.b2 = new JButton();
this.b3 = new JButton();
this.p3.add(b1);
this.p3.add(b2);
this.p3.add(b3);
this.f.add(p1);
this.f.add(p2);
this.f.add(p3);
this.f.pack();
this.f.setResizeable(false)
}}
Add your video components instead of labels and you can change the color of the components as you wish.
Also if you want more control over the size and position of the components, use null layout and place them individually using setBounds() function as once shown in the program above. It is surely time consuming but makes the layout perfect.

What's the best way to organize JPanels in a list?

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(...) );

JScrollPane does not appear when using it on a JPanel

I have been trying for hours to find a way to solve the issue, but I had no luck with that. Here is a sample code:
import java.awt.BorderLayout;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class Example extends JFrame
{
private static final long serialVersionUID = 1L;
public Example()
{
JPanel contentPane = (JPanel) getContentPane();
contentPane.setLayout(new BorderLayout());
JPanel panTop = new JPanel(new BorderLayout());
//JPanel panBottom = new JPanel(new BorderLayout());
JPanel panTopCenter = new JPanel();
//JPanel panTopLeft = new JPanel();
//JPanel panTopRight = new JPanel();
panTop.add(panTopCenter, BorderLayout.CENTER);
//panTop.add(panTopLeft, BorderLayout.WEST);
//panTop.add(panTopRight, BorderLayout.EAST);
contentPane.add(panTop, BorderLayout.CENTER);
//contentPane.add(panBottom, BorderLayout.SOUTH);
JPanel pan = new JPanel();
pan.setLayout(new BoxLayout(pan, BoxLayout.Y_AXIS));
for(int i = 0; i < 50; i++) pan.add(new JButton("Button " + i));
JScrollPane scrollPane = new JScrollPane(pan);
panTopCenter.add(scrollPane);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new Example();
}
});
}
}
Snapshot:
I always have to set the viewport's preferred size like this.
import java.awt.*;
import javax.swing.*;
public class Example extends JFrame {
public Example() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
Box box = new Box(BoxLayout.Y_AXIS);
for (int i = 0; i < 50; i++) {
box.add(new JButton("Button " + i));
}
JScrollPane sp = new JScrollPane(box);
Dimension d = new Dimension(box.getComponent(0).getPreferredSize());
sp.getVerticalScrollBar().setUnitIncrement(d.height);
d.height *= 10; // Show at least 10 buttons
sp.getViewport().setPreferredSize(d);
add(sp, BorderLayout.CENTER);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Example e = new Example();
}
});
}
}
Don't set any sizes! The scroll-bar appears if this change is made.
JPanel panTopCenter = new JPanel(new GridLayout());
The basic problem is that FlowLayout will show components at the smallest size needed to display it, and for a scroll-pane, that is (decided to be) 0x0. By using a GridLayout with no arguments in the constructor and adding the scroll-pane as the only component, it will be forced to fill the available space.
You have to set the preferred-size, in the case that JScrollPane is single JComponent in the container or top-level container.
scrollPane.setPreferredSize(new Dimension(100,500));
Better would be to use GridLayout for the same type of JComponents.
The best solution, quick and easy, is using JXPanel from SwingX, which is quasi-standard and implements Scrollable.

Categories

Resources