Normally, FlowLayout uses more than one line if needed. Apparently this doesn't happen if the component with the FlowLayout is itself part of a GridBagLayout.
Consider this code:
import java.awt.*;
import javax.swing.*;
public class Xyzzy extends JFrame{
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
Xyzzy frame = new Xyzzy();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
JPanel top = new JPanel();
top.setLayout(new FlowLayout());
for (int i=1; i<=30; ++i)
top.add(new JLabel(String.format("Label #%d",i)));
GridBagConstraints c = new GridBagConstraints();
c.weightx = 1.0;
c.gridwidth = GridBagConstraints.REMAINDER;
c.anchor = GridBagConstraints.CENTER;
frame.add(top,c);
frame.add(new JLabel("Bottom"),c);
//top.setPreferredSize(new Dimension(500,300));
frame.setSize(600, 600);
frame.setVisible(true);
}
});
}
}
This code is intended to display the JLabels "Label #1", "Label #2" etc. on multiple lines, but in fact it only uses one line.
I can force it to use multiple lines by removing the '//' before the call to setPreferredSize in the above code, but this requires me to set both a width and a height, and I don't know what height to use. (I cannot use FontMetrics to calculate the height, because in my actual case the JLabels are in reality small JPanels of varying size.)
So is there a way to force FlowLayout to use multiple lines? (Or, alternatively, is there a way to calculate the required height of a component when its width is known?)
Try to add constraint parameters:
c.weighty = 1.0;
c.fill = GridBagConstraints.VERTICAL;
It should allow growing of panel with FlowLayout in vertical direction.
To the best of my knowledge, FlowLayout does require a preferred size in order to actually wrap anything. I've created a subclass which works out the preferred size as the parent container's width, and then whatever height is required:
public class WrappingFlowLayout extends FlowLayout {
#Override
public Dimension preferredLayoutSize(Container target) {
synchronized (target.getTreeLock()) {
Dimension result;
int w = target.getWidth();
if (w == 0) {
// The container hasn't been assigned any size yet; just behave like a regular flow layout.
result = super.preferredLayoutSize(target);
} else {
Insets insets = target.getInsets();
int wrapW = w - insets.left - insets.right;
int maxW = 0; // Width of the widest row.
int rowH = 0; // Current row height.
int x = 0;
int y = 0;
boolean firstVisibleComponent = true;
for (Component c : target.getComponents()) {
if (c.isVisible()) {
Dimension d = c.getPreferredSize();
if (firstVisibleComponent) {
x = d.width + (getHgap() * 2);
y = getVgap();
rowH = d.height;
firstVisibleComponent = false;
} else if (x + d.width + getHgap() <= wrapW) {
// Add to current row.
x += d.width + getHgap();
rowH = Math.max(rowH, d.height);
} else {
// New row.
x = d.width + (getHgap() * 2);
y += rowH + getVgap();
rowH = d.height;
}
maxW = Math.max(maxW, x);
}
}
y += rowH + getVgap();
result = new Dimension(maxW + insets.left + insets.right, y + insets.top + insets.bottom);
}
return result;
}
}
}
Note that you'll need to listen for resize events on the parent component (top in your code) and trigger an update of the preferred size. Your best bet for doing this is probably a ComponentListener.
Related
I have the weirdest bug ever.
I have this puzzle game that moves puzzle pieces (which really are buttons with images attached to them).
Everything worked fine until I tried to change the text of some label (to indicate how many steps the player has done).
Everytime I call someControl.setText("text");, the puzzle pieces that moved are set back to the their first position. I have no idea why, but they just do.
Here's my window:
It consists of two panels, each uses a GridBagLayout.
The main frame uses a gridBagLayout as well, which consists of the two panels.
I know it's weird as hell, but I can't figure out what may cause this GUI bug. Any idea?
The pieces of code:
increaseSteps which is called everytime I click a puzzle button
void increaseSteps() {
_steps++;
_lblSteps.setText("Steps: " + _steps);
}
Creation of the puzzle panel (the left panel)
private JPanel puzzlePanel() {
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
for (int i = 0; i < _splitImage.getSize(); i++)
for (int j = 0; j < _splitImage.getSize(); j++) {
int valueAtPos = _board.getMatrix()[i][j];
if (valueAtPos == 0)
continue;
int imageRow = _board.getImageRowFromValue(valueAtPos);
int imageCol = _board.getImageColFromValue(valueAtPos);
ImageIcon imageIcon = new ImageIcon(_splitImage.getImages()[imageRow][imageCol]);
JButton btn = new JButton(imageIcon);
_tileButtons[i][j] = new TileButton(btn, i, j);
btn.setPreferredSize(new Dimension(_splitImage.getImages()[i][j].getWidth(null),
_splitImage.getImages()[i][j].getHeight(null)));
// add action listener
btn.addActionListener(this);
btn.addKeyListener(this);
gbc.gridx = j;
gbc.gridy = i;
panel.add(_tileButtons[i][j].getButton(), gbc);
}
return panel;
}
actionPerformed:
#Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JButton))
return;
JButton btn = (JButton) e.getSource();
TileButton tile = getTileButtonFromBtn(btn);
if (tile == null)
return;
// check if we can move the tile
String moveDir = _board.canMoveTile(tile.getRow(), tile.getCol());
if (moveDir.equals("no"))
return;
increaseSteps();
int dirx = 0;
int diry = 0;
if (moveDir.equals("left")) {
dirx = -1;
_board.move("left", true);
tile.setCol(tile.getCol() - 1);
} else if (moveDir.equals("right")) {
dirx = 1;
_board.move("right", true);
tile.setCol(tile.getCol() + 1);
} else if (moveDir.equals("up")) {
diry = -1;
_board.move("up", true);
tile.setRow(tile.getRow() - 1);
} else { // down
diry = 1;
_board.move("down", true);
tile.setRow(tile.getRow() + 1);
}
moveButton(btn, dirx, diry, MOVE_SPEED);
if (_board.hasWon())
win();
}
moveButton: (moves the button in a seperate thread, calling btn.setLocation())
private void moveButton(JButton btn, int dirx, int diry, int speed) {
Point loc = btn.getLocation();
// get start ticks, calculate distance etc...
StopWatch stopper = new StopWatch();
int distance;
if (dirx != 0)
distance = _splitImage.getImages()[0][0].getWidth(null) * dirx;
else
distance = _splitImage.getImages()[0][0].getHeight(null) * diry;
if (speed > 0) {
// run the animation in a new thread
Thread thread = new Thread() {
public void run() {
int currentTicks;
int elapsed;
do {
int newX = loc.x;
int newY = loc.y;
elapsed = stopper.getElapsed();
int moved = (int) ((double) distance * (double) (elapsed / (double) speed));
if (dirx != 0)
newX += moved;
else
newY += moved;
btn.setLocation(newX, newY);
} while (elapsed <= MOVE_SPEED);
// make sure the last location is exact
btn.setLocation(loc.x + (dirx == 0 ? 0 : distance), loc.y + (diry == 0 ? 0 : distance));
}
};
thread.start();
}
else
btn.setLocation(loc.x + (dirx == 0 ? 0 : distance), loc.y + (diry == 0 ? 0 : distance));
}
You're trying to set the absolute position of a component via setLocation(...) or setBounds(...), one that is held by a container that uses a layout manager. This may work temporarily, but will fail if the container's layout manager is triggered to re-do the layout of its contained components. When that happens, the GridBagConstraints will take over and the components will move to their gridbag constraints assigned location.
The solution is to not do this, and instead to place the location of your components in concert with the layout managers used.
Another problem is that your current code is not Swing thread-safe since you're making Swing state changes from within a background thread. This won't always cause problems, but since it's a threading issue, risks causing intermittent hard to debug problems (ones that usually only occur when your boss or instructor are trying to run your code).
Possible solutions:
For a grid of images, you could use a grid of JLabels (or JButtons if you must) held in a container that uses GridLayout. When you need to reposition components, remove all components held by that JPanel, and then re-add, using the order of addition to help you position the components.
Easiest though would be to use a grid of non-moving JLabels, give them MouseListeners, and instead of moving the JLabels, remove and add Icons to them, including a blank Icon.
If you need to do Swing animation, use a Swing Timer to drive the animation. This will allow your code to make repetitive calls with delay between the calls, and with these calls being made on the Swing event thread, the EDT (event dispatch thread).
Demo proof of concept example code that shows swapping icons, but without animation, and without test of solution yet:
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class ImageShuffle extends JPanel {
private static final int SIDES = 3;
public static final String IMG_PATH = "https://upload.wikimedia.org/wikipedia/commons/"
+ "thumb/5/5a/Hurricane_Kiko_Sep_3_1983_1915Z.jpg/"
+ "600px-Hurricane_Kiko_Sep_3_1983_1915Z.jpg";
private List<Icon> iconList = new ArrayList<>(); // shuffled icons
private List<Icon> solutionList = new ArrayList<>(); // in order
private List<JLabel> labelList = new ArrayList<>(); // holds JLabel grid
private Icon blankIcon;
public ImageShuffle(BufferedImage img) {
setLayout(new GridLayout(SIDES, SIDES, 1, 1));
fillIconList(img); // fill array list with icons and one blank one
Collections.shuffle(iconList);
MyMouseListener myMouse = new MyMouseListener();
for (Icon icon : iconList) {
JLabel label = new JLabel(icon);
label.addMouseListener(myMouse);
add(label);
labelList.add(label);
}
}
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
JLabel selectedLabel = (JLabel) e.getSource();
if (selectedLabel.getIcon() == blankIcon) {
return; // don't want to move the blank icon
}
// index variables to hold selected and blank JLabel's index location
int selectedIndex = -1;
int blankIndex = -1;
for (int i = 0; i < labelList.size(); i++) {
if (selectedLabel == labelList.get(i)) {
selectedIndex = i;
} else if (labelList.get(i).getIcon() == blankIcon) {
blankIndex = i;
}
}
// get row and column of selected JLabel
int row = selectedIndex / SIDES;
int col = selectedIndex % SIDES;
// get row and column of blank JLabel
int blankRow = blankIndex / SIDES;
int blankCol = blankIndex % SIDES;
if (isMoveValid(row, col, blankRow, blankCol)) {
Icon selectedIcon = selectedLabel.getIcon();
labelList.get(selectedIndex).setIcon(blankIcon);
labelList.get(blankIndex).setIcon(selectedIcon);
// test for win here by comparing icons held by labelList
// with the solutionList
}
}
private boolean isMoveValid(int row, int col, int blankRow, int blankCol) {
// has to be on either same row or same column
if (row != blankRow && col != blankCol) {
return false;
}
// if same row
if (row == blankRow) {
// then columns must be off by 1 -- they're next to each other
return Math.abs(col - blankCol) == 1;
} else {
// or else rows off by 1 -- above or below each other
return Math.abs(row - blankRow) == 1;
}
}
public void shuffle() {
Collections.shuffle(iconList);
for (int i = 0; i < labelList.size(); i++) {
labelList.get(i).setIcon(iconList.get(i));
}
}
}
private void fillIconList(BufferedImage img) {
// get the width and height of each individual icon
// which is 1/3 the image width and height
int w = img.getWidth() / SIDES;
int h = img.getHeight() / SIDES;
for (int row = 0; row < SIDES; row++) {
int y = (row * img.getWidth()) / SIDES;
for (int col = 0; col < SIDES; col++) {
int x = (col * img.getHeight()) / SIDES;
// create a sub image
BufferedImage subImg = img.getSubimage(x, y, w, h);
// create icon from the image
Icon icon = new ImageIcon(subImg);
// add to both icon lists
iconList.add(icon);
solutionList.add(icon);
}
}
// create a blank image and corresponding icon as well.
BufferedImage blankImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
blankIcon = new ImageIcon(blankImg);
iconList.remove(iconList.size() - 1); // remove last icon from list
iconList.add(blankIcon); // and swap in the blank one
solutionList.remove(iconList.size() - 1); // same for the solution list
solutionList.add(blankIcon);
}
private static void createAndShowGui(BufferedImage img) {
JFrame frame = new JFrame("ImageShuffle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ImageShuffle(img));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
URL imgUrl = null;
BufferedImage img;
try {
imgUrl = new URL(IMG_PATH);
img = ImageIO.read(imgUrl);
SwingUtilities.invokeLater(() -> createAndShowGui(img));
} catch (IOException e) {
e.printStackTrace();
}
}
}
If I wanted animation, again, I'd raise the icon into the JFrame's glasspane, animate it to the new position using a Swing Timer, and then place the icon into the new JLabel. I'd also disable the MouseListener using a boolean field, a "flag", until the animation had completed its move.
I am writing a GUI with Swing. I'm using a GridBagLayout to display multiple JLabels in a grid (basically like a chess board). As soon as I use a self made label class derived from JLabel instead of JLabel, the GridBagLayout stacks every label on the top left corner of the JPanel.
Either my subclass TileLabel is incorrect or I don't use the layout and constraints the right way. I think the last one because I can't see what would be a problem in such a minimal subclass.
This is how it looks using JLabel (L represents a label):
(MenuBar)
L L L L L L L L L
L L L L L L L L L
L L L L L L L L L
This is how it looks using TileLabel (S represents all the labels stacked):
(MenuBar)
S
This is my simple subclass from JLabel:
import javax.swing.JLabel;
public class TileLabel extends JLabel {
private static final long serialVersionUID = 6718776819945522562L;
private int x;
private int y;
public TileLabel(int x, int y) {
super();
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
And this is the GUI class. I Marked the three lines where I used my custom label which lead to the layout problem.
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MainGUI extends JPanel {
private static final long serialVersionUID = -8750891542665009043L;
private JFrame frame;
private MainMenuBar menuBar;
private TileLabel[][] labelGrid; // <-- LINE 1
private GridBagConstraints constraints;
private int gridWidth;
private int gridHeight;
// Basic constructor.
public MainGUI(int frameWidth, int frameHeight) {
super(new GridBagLayout());
constraints = new GridBagConstraints();
buildFrame(frameWidth, frameHeight);
buildLabelGrid(frameWidth, frameHeight);
}
// Builds the frame.
private void buildFrame(int frameWidth, int frameHeight) {
menuBar = new MainMenuBar();
frame = new JFrame("Carcasonne");
frame.getContentPane().add(this);
frame.setJMenuBar(menuBar);
frame.setResizable(false);
frame.setVisible(true);
frame.setSize(frameWidth, frameHeight);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBackground(new Color(165, 200, 245));
}
// Creates the grid of labels.
private void buildLabelGrid(int frameWidth, int frameHeight) {
gridWidth = frameWidth / 100;
gridHeight = frameHeight / 100;
labelGrid = new TileLabel[gridWidth][gridHeight]; // <-- LINE 2
for (int x = 0; x < gridWidth; x++) {
for (int y = 0; y < gridHeight; y++) {
labelGrid[x][y] = new TileLabel(x, y); // <-- LINE 3
constraints.gridx = x;
constraints.gridy = y;
add(labelGrid[x][y], constraints); // add label with constraints
}
}
}
// sets the icon of a specific label
public void paint(Tile tile, int x, int y) {
if (x >= 0 && x < gridWidth && y >= 0 && y < gridHeight) {
labelGrid[x][y].setIcon(tile.getImage());
} else {
throw new IllegalArgumentException("Invalid label grid position (" + x + ", " + y + ")");
}
}
// Just to test this GUI:
public static void main(String[] args) {
MainGUI gui = new MainGUI(1280, 768);
Tile tile = TileFactory.createTile(TileType.Road);
for (int x = 0; x < 12; x++) {
for (int y = 0; y < 7; y++) {
gui.paint(tile, x, x);
}
}
}
}
Where is the problem?
There are quite a few things to fix in your code, but your problem originates from 3 things:
Your method definitions in your custom label:
public class TileLabel extends JLabel {
// #Override !!!!
public int getX() {
return x;
}
// #Override !!!!
public int getY() {
return y;
}
}
You are overriding JComponent's getX() and getY(), which are responsible for returning their coordinates. This messes up the layout completely.
Be careful with your paint method, a method with the same name exists in a superclass, though you are saved in this case since the arguments are different.
You have a typo at your loop: gui.paint(tile, x, x) should be gui.paint(tile, x, y).
The order in which you call your methods is wrong. First, you create the frame and display it, then you change its contents by adding the panel with the labels to it, then you change the text in the labels. You should do this the other way around.
My recommendations:
Make your paint method be made a member of your TileLabel class. It makes more sense.
Set the icons during creation of the labels, unless they are not known. If you can't, you might need to recalculate the space requirements.
Never make your layout dependent on the size of the screen or its resolution. It makes for a fragile GUI (as noted in the comments). Use pack() for the frame to calculate the correct size.
You have accidentally overridden JComponent#getX() and JComponent#getY(). The values returned by this method are not consistent with the values that the layout may set internally (via calls to setBounds or so). This messes up the layout.
(Admittedly, I did not really check whether this is the reason, but it likely is, and it is a problem in general!)
Here is my code:
import java.awt.Component;
import java.awt.Container;
import javax.swing.*;
public class GUIBuilder {
/**
* Create the GUI and show it. For thread safety, this method should be
* invoked from the event-dispatching thread.
*/
private static void createAndShowGUI() {
String[] labels = { "Name: ", "Fax: ", "Email: ", "Address: " };
int numPairs = labels.length;
JPanel p = new JPanel();
BoxLayout b = new BoxLayout(p, BoxLayout.Y_AXIS);
p.setLayout(b);
// Create and populate the panel.
JPanel p2 = new JPanel(new SpringLayout());
for (int i = 0; i < numPairs; i++) {
JLabel l = new JLabel(labels[i], JLabel.TRAILING);
p2.add(l);
JTextField textField = new JTextField(10);
l.setLabelFor(textField);
p2.add(textField);
}
p.add(p2);
// Lay out the panel.
makeCompactGrid(p2, numPairs, 2, // rows, cols
6, 6, // initX, initY
6, 6); // xPad, yPad
// Create and set up the window.
JFrame frame = new JFrame("SpringForm");
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Set up the content pane.
p.setOpaque(true); // content panes must be opaque
frame.setContentPane(p);
JButton enter = new JButton("Enter");
p.add(enter);
// Display the window.
frame.pack();
frame.setVisible(true);
}
/* Used by makeCompactGrid. */
private static SpringLayout.Constraints getConstraintsForCell(
int row, int col,
Container parent,
int cols) {
SpringLayout layout = (SpringLayout) parent.getLayout();
Component c = parent.getComponent(row * cols + col);
return layout.getConstraints(c);
}
/**
* Aligns the first <code>rows</code> * <code>cols</code>
* components of <code>parent</code> in
* a grid. Each component in a column is as wide as the maximum
* preferred width of the components in that column;
* height is similarly determined for each row.
* The parent is made just big enough to fit them all.
*
* #param rows number of rows
* #param cols number of columns
* #param initialX x location to start the grid at
* #param initialY y location to start the grid at
* #param xPad x padding between cells
* #param yPad y padding between cells
*/
public static void makeCompactGrid(Container parent,
int rows, int cols,
int initialX, int initialY,
int xPad, int yPad) {
SpringLayout layout;
try {
layout = (SpringLayout)parent.getLayout();
} catch (ClassCastException exc) {
System.err.println("The first argument to makeCompactGrid must use SpringLayout.");
return;
}
//Align all cells in each column and make them the same width.
Spring x = Spring.constant(initialX);
for (int c = 0; c < cols; c++) {
Spring width = Spring.constant(0);
for (int r = 0; r < rows; r++) {
width = Spring.max(width,
getConstraintsForCell(r, c, parent, cols).
getWidth());
}
for (int r = 0; r < rows; r++) {
SpringLayout.Constraints constraints =
getConstraintsForCell(r, c, parent, cols);
constraints.setX(x);
constraints.setWidth(width);
}
x = Spring.sum(x, Spring.sum(width, Spring.constant(xPad)));
}
//Align all cells in each row and make them the same height.
Spring y = Spring.constant(initialY);
for (int r = 0; r < rows; r++) {
Spring height = Spring.constant(0);
for (int c = 0; c < cols; c++) {
height = Spring.max(height,
getConstraintsForCell(r, c, parent, cols).
getHeight());
}
for (int c = 0; c < cols; c++) {
SpringLayout.Constraints constraints =
getConstraintsForCell(r, c, parent, cols);
constraints.setY(y);
constraints.setHeight(height);
}
y = Spring.sum(y, Spring.sum(height, Spring.constant(yPad)));
}
//Set the parent's size.
SpringLayout.Constraints pCons = layout.getConstraints(parent);
pCons.setConstraint(SpringLayout.SOUTH, y);
pCons.setConstraint(SpringLayout.EAST, x);
}
public static void main(String[] args) {
// Schedule a job for the event-dispatching thread:
// creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
For some reason, my JFrame frame isn't centering properly. Could anyone know what could be wrong here? This is my first time using SpringLayout, so I wonder if it could have anything to do with this. I used part of Oracle's examples on their website, so I would like to know how could I get my frame properly centered.
It's nothing to do with SpringLayout. The JFrame is being packed after the window has been centered which changes its size. Simply reverse the order of these 2 calls
frame.setLocationRelativeTo(null);
frame.pack()
I have a custom layout where the principal behavior is to grow and shrink a child JTextArea when the JScrollPane it's in changes in width. The scroll pane has the horizontal scroll bar disabled and the text area is supposed to expand and contract so as to avoid needing a horizontal scroll bar. For a number of months, I worked around this using one of the standard layout managers, but now I need some different functionality.
What's happening is that when the user expands horizontally the scroll pane, the layout manager layoutContainer method is called. It resizes the text area and the text reflows properly. However, when you shrink the scroll pane, layoutContainer is not called and the text area stays fixed. I've put some printlns in the layoutContainer method to make it obvious when it's working and not.
The essential thing to note is that the problem happens when JTextArea.setColumns() is called. I can comment it out and the layoutContainer gets called during resizing (of course, then the text area doesn't get resized.) I've tried also using JTextArea.setSize(), with the same results.
Here's the code:
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
#SuppressWarnings("serial")
class XTextArea extends JTextArea
{
XTextArea (String text)
{
super (text);
}
public int getColumnWidth()
{
return super.getColumnWidth();
}
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
class PackLeftLayout implements LayoutManager
{
Component viewPort;
Component flexWidthComponent;
int preferredWidth = 0;
int preferredHeight = 0;
//----------------------------------------------------------------------------
// viewPort - if null, compute width as sum of component's preferred width;
// otherwise width will be the viewPort's width.
// flexWidthComponent - if not null, this component width will be sized to right
// justify rightmost component.
public PackLeftLayout (Component viewPort, Component flexWidthComponent)
{
super ();
this.viewPort = viewPort;
this.flexWidthComponent = flexWidthComponent;
}
//----------------------------------------------------------------------------
public void addLayoutComponent(String name, Component comp)
{
}
//----------------------------------------------------------------------------
public void removeLayoutComponent(Component comp)
{
}
//----------------------------------------------------------------------------
// Calculates the preferred size dimensions for the specified container, given the
// components it contains.
// parent - the container to be laid out
// Components layed out left-to-right with no additional spacing.
public Dimension preferredLayoutSize (Container parent)
{
Insets insets = parent.getInsets();
int width = 0;
int height = 0; // will become max of all component's preferred height
// calculate sum of fixed width components - skip the flexwidth component
width = insets.left + insets.right;
for (int i = 0, limit = parent.getComponentCount(); i < limit; i++)
{
Component c = parent.getComponent(i);
if (c.isVisible())
{
if (c != flexWidthComponent)
{
Dimension size = c.getPreferredSize();
if (size.height > height)
height = size.height;
width += size.width;
}
}
}
// determine width of flex width component
if (flexWidthComponent != null)
{
int flexWidth = viewPort.getWidth() - width;
if (flexWidth < 1)
flexWidth = 1;
if (flexWidthComponent instanceof XTextArea)
{
// some trickery here to get the xtextarea to tell us its preferred height
// given a specific width.
int colWidth = ((XTextArea)flexWidthComponent).getColumnWidth();
// the following line causes the failure:
((XTextArea)flexWidthComponent).setColumns (flexWidth / colWidth);
Dimension taSize = flexWidthComponent.getPreferredSize();
width += taSize.width;
if (taSize.height > height)
height = taSize.height;
}
else
{
Dimension size = flexWidthComponent.getPreferredSize();
width += flexWidth;
if (size.height > height)
height = size.height;
}
}
preferredWidth = width; // already include insets
preferredHeight = height + insets.top + insets.bottom;
return new Dimension (preferredWidth, preferredHeight);
}
//----------------------------------------------------------------------------
// Calculates the minimum size dimensions for the specified container, given the
// components it contains.
// parent - the component to be laid out
public Dimension minimumLayoutSize(Container parent)
{
return new Dimension (10, 10); //???
}
static int k = 0;
//----------------------------------------------------------------------------
public void layoutContainer(Container parent)
{
System.out.println ("layout" + (k++));
Insets insets = parent.getInsets();
int left = insets.left;
if (preferredWidth == 0 || preferredHeight == 0)
preferredLayoutSize (parent);
for (int i = 0, limit = parent.getComponentCount(); i < limit; i++)
{
Component c = parent.getComponent(i);
Dimension size = c.getPreferredSize();
c.setBounds (left, insets.top, size.width, preferredHeight);
left += size.width;
}
// force another layout calc
preferredWidth = 0;
}
}
public class ResizablePane extends JFrame
{
public static void main(String[] args)
{
javax.swing.SwingUtilities.invokeLater(
new Runnable() {public void run()
{
new ResizablePane();
}});
}
ResizablePane ()
{
super ("ResizableDemo");
// put a button and text area into a panel, then into a scroll pane
JButton button = new JButton ("button");
XTextArea text = new XTextArea (
"For three years I ran as fast as I could, trying to live and love and learn at " +
"double speed to make up for what Anne-Marie lost. Trying to anesthetize myself " +
"from what Id lost. When I decided to read a book a day and write about it, Id " +
"finally stopped running away.");
text.setLineWrap (true);
text.setWrapStyleWord (true);
JScrollPane scroll = new JScrollPane();
JPanel panel = new JPanel();
panel.setLayout (new PackLeftLayout(scroll.getViewport(), text));
panel.add (button);
panel.add (text);
scroll.setHorizontalScrollBarPolicy (ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scroll.setViewportView (panel);
getContentPane().add(scroll);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setVisible(true);
}
}
JTextArea wont shrink inside JPanel
You need to set the minimum size of the text area:
textArea.SetMinimumSize(new Dimension(100,100));
ps. I'm Using GridLayout in my panel with just the one component.
This is old question, but i hope that it will be usefull to someone.
For me worked:
jScrollPane = new JScrollPane(textArea);
jScrollPane.setPreferredSize(jScrollPane.getPreferredSize());
I have a JPanel and I create, dynamically, JCheckBoxes inside.
These have to be added JCheckBoxes always a side by side. In case there is more space to be inserted in the side, a new line of JCheckBoxes is created, as in a simple text editor.
This is happening perfectly. But ...
I set the layout on this JPanel to FlowLayout, exactly what I want.
The obvious problem is that a window has limited space. So a good solution to this is: Insertion of this JPanel in a JScrollPane,l and making that happen only in the vertical scrolling.
But I have problems. Although you can make only a vertical scroll bar to appear, the items are always added "forever" side by side. And the vertical scroll simply does not work, only horizontally.
I've tried many ways to make the scroll only vertically, but nothing worked (if it had worked I would not be here:]).
So, has anyone had any similar problem, and can help me?
I shall be very grateful to those who help me.
No more.
I dealt with the same issue with ScrollPanes and FlowLayouts. I found the best solution is to use a modified version of FlowLayout that takes into account vertical changes. Here is the code for such a layout. You can include it in your project and call it just like a FlowLayout, however it will actually work nice with a scrollpane.
import java.awt.*;
/**
* A modified version of FlowLayout that allows containers using this
* Layout to behave in a reasonable manner when placed inside a
* JScrollPane
* #author Babu Kalakrishnan
* Modifications by greearb and jzd
*/
public class ModifiedFlowLayout extends FlowLayout {
public ModifiedFlowLayout() {
super();
}
public ModifiedFlowLayout(int align) {
super(align);
}
public ModifiedFlowLayout(int align, int hgap, int vgap) {
super(align, hgap, vgap);
}
public Dimension minimumLayoutSize(Container target) {
// Size of largest component, so we can resize it in
// either direction with something like a split-pane.
return computeMinSize(target);
}
public Dimension preferredLayoutSize(Container target) {
return computeSize(target);
}
private Dimension computeSize(Container target) {
synchronized (target.getTreeLock()) {
int hgap = getHgap();
int vgap = getVgap();
int w = target.getWidth();
// Let this behave like a regular FlowLayout (single row)
// if the container hasn't been assigned any size yet
if (w == 0) {
w = Integer.MAX_VALUE;
}
Insets insets = target.getInsets();
if (insets == null){
insets = new Insets(0, 0, 0, 0);
}
int reqdWidth = 0;
int maxwidth = w - (insets.left + insets.right + hgap * 2);
int n = target.getComponentCount();
int x = 0;
int y = insets.top + vgap; // FlowLayout starts by adding vgap, so do that here too.
int rowHeight = 0;
for (int i = 0; i < n; i++) {
Component c = target.getComponent(i);
if (c.isVisible()) {
Dimension d = c.getPreferredSize();
if ((x == 0) || ((x + d.width) <= maxwidth)) {
// fits in current row.
if (x > 0) {
x += hgap;
}
x += d.width;
rowHeight = Math.max(rowHeight, d.height);
}
else {
// Start of new row
x = d.width;
y += vgap + rowHeight;
rowHeight = d.height;
}
reqdWidth = Math.max(reqdWidth, x);
}
}
y += rowHeight;
y += insets.bottom;
return new Dimension(reqdWidth+insets.left+insets.right, y);
}
}
private Dimension computeMinSize(Container target) {
synchronized (target.getTreeLock()) {
int minx = Integer.MAX_VALUE;
int miny = Integer.MIN_VALUE;
boolean found_one = false;
int n = target.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = target.getComponent(i);
if (c.isVisible()) {
found_one = true;
Dimension d = c.getPreferredSize();
minx = Math.min(minx, d.width);
miny = Math.min(miny, d.height);
}
}
if (found_one) {
return new Dimension(minx, miny);
}
return new Dimension(0, 0);
}
}
}