XY Layout JAVA - java

is there any sort of XY-Layout to Java?
So I can set a Button at the X and Y cordinate and that it is suppose to be that big etc.... Because this border layout and grid and panel thing is driven me crazy. :)
They are flowing every were and getting strecht up. And to make them small you have to put panel in panel in panel in panel ^^,

When setting the container's layout to null (no LayoutManager), you can set the component's bounds individually with component.setBounds(x,y,w,h).
Fixed layouts are in 99% of all cases bad UI design (if your labels, for example, don't get their preferred size you run into mayor problems when your application supports multiple languages), so my advice for you is to rather write a specialized layout manager for your specific needs.
Writing your custom layout manager is quite easy, all you have to do is to be able to calculate the preferred size for a container with given components and your layout, and to do the layout by setting the (calculated) bounds of your components.
I got rid of the GridBagLayout and started coding my own layouts long ago, and layouting has never been easier.
Here's an example of a custom layout, that layouts pairs of key and value components:
public class KeyValueLayout implements LayoutManager {
public static enum KeyAlignment {
LEFT, RIGHT;
}
private KeyAlignment keyAlignment = KeyAlignment.LEFT;
private int hgap;
private int vgap;
public KeyValueLayout () {
this(KeyAlignment.LEFT);
}
public KeyValueLayout (KeyAlignment keyAlignment) {
this(keyAlignment, 5, 5);
}
public KeyValueLayout (int hgap, int vgap) {
this(KeyAlignment.LEFT, hgap, vgap);
}
public KeyValueLayout (KeyAlignment keyAlignment, int hgap, int vgap) {
this.keyAlignment = keyAlignment != null ? keyAlignment : KeyAlignment.LEFT;
this.hgap = hgap;
this.vgap = vgap;
}
public void addLayoutComponent (String name, Component comp) {
}
public void addLayoutComponent (Component comp, Object constraints) {
}
public void removeLayoutComponent (Component comp) {
}
public void layoutContainer (Container parent) {
Rectangle canvas = getLayoutCanvas(parent);
int ypos = canvas.y;
int preferredKeyWidth = getPreferredKeyWidth(parent);
for (Iterator<Component> iter = new ComponentIterator(parent); iter.hasNext();) {
Component key = (Component) iter.next();
Component value = iter.hasNext() ? (Component) iter.next() : null;
int xpos = canvas.x;
int preferredHeight = Math.max(key.getPreferredSize().height, value != null ? value.getPreferredSize().height : 0);
if (keyAlignment == KeyAlignment.LEFT)
key.setBounds(xpos, ypos, key.getPreferredSize().width, key.getPreferredSize().height);
else
key.setBounds(xpos + preferredKeyWidth - key.getPreferredSize().width, ypos, key.getPreferredSize().width,
key.getPreferredSize().height);
xpos += preferredKeyWidth + hgap;
if (value != null)
value.setBounds(xpos, ypos, canvas.x + canvas.width - xpos, preferredHeight);
ypos += preferredHeight + vgap;
}
}
public Dimension minimumLayoutSize (Container parent) {
int preferredKeyWidth = getPreferredKeyWidth(parent);
int minimumValueWidth = 0;
int minimumHeight = 0;
int lines = 0;
for (Iterator<Component> iter = new ComponentIterator(parent); iter.hasNext();) {
lines++;
Component key = (Component) iter.next();
Component value = iter.hasNext() ? (Component) iter.next() : null;
minimumHeight += Math.max(key.getPreferredSize().height, value != null ? value.getMinimumSize().height : 0);
minimumValueWidth = Math.max(minimumValueWidth, value != null ? value.getMinimumSize().width : 0);
}
Insets insets = parent.getInsets();
int minimumWidth = insets.left + preferredKeyWidth + hgap + minimumValueWidth + insets.right;
minimumHeight += insets.top + insets.bottom;
if (lines > 0)
minimumHeight += (lines - 1) * vgap;
return new Dimension(minimumWidth, minimumHeight);
}
public Dimension preferredLayoutSize (Container parent) {
int preferredKeyWidth = getPreferredKeyWidth(parent);
int preferredValueWidth = 0;
int preferredHeight = 0;
int lines = 0;
for (Iterator<Component> iter = new ComponentIterator(parent); iter.hasNext();) {
lines++;
Component key = (Component) iter.next();
Component value = iter.hasNext() ? (Component) iter.next() : null;
preferredHeight += Math.max(key.getPreferredSize().height, value != null ? value.getPreferredSize().height : 0);
preferredValueWidth = Math.max(preferredValueWidth, value != null ? value.getPreferredSize().width : 0);
}
Insets insets = parent.getInsets();
int preferredWidth = insets.left + preferredKeyWidth + hgap + preferredValueWidth + insets.right;
preferredHeight += insets.top + insets.bottom;
if (lines > 0)
preferredHeight += (lines - 1) * vgap;
return new Dimension(preferredWidth, preferredHeight);
}
public Dimension maximumLayoutSize (Container target) {
return preferredLayoutSize(target);
}
private int getPreferredKeyWidth (Container parent) {
int preferredWidth = 0;
for (Iterator<Component> iter = new ComponentIterator(parent); iter.hasNext();) {
Component key = (Component) iter.next();
if (iter.hasNext())
iter.next();
preferredWidth = Math.max(preferredWidth, key.getPreferredSize().width);
}
return preferredWidth;
}
private Rectangle getLayoutCanvas (Container parent) {
Insets insets = parent.getInsets();
int x = insets.left;
int y = insets.top;
int width = parent.getSize().width - insets.left - insets.right;
int height = parent.getSize().height - insets.top - insets.bottom;
return new Rectangle(x, y, width, height);
}
private class ComponentIterator implements Iterator<Component> {
private Container container;
private int index = 0;
public ComponentIterator (Container container) {
this.container = container;
}
public boolean hasNext () {
return index < container.getComponentCount();
}
public Component next () {
return container.getComponent(index++);
}
public void remove () {
}
}
}
Just set the layout and add alternatingly labels and value components. It's easy to use, especially compared to GridBagLayout or nested panels with custom layouts.

The reason components resize is so stuff looks nice whatever size the window is, so Swing discourages straight X-Y position. You might want to have a look at GroupLayout http://java.sun.com/docs/books/tutorial/uiswing/layout/group.html which is designed for GUI builders, and the page mentioned above describes using invisible components to absorb the stretches. eg:layout.setAutoCreateGaps(true);
SpringLayout might also be useful - see the visual guide
If you really want X and Y then set the layout manager to null, and use setLocation() or setBounds(). I REALLY REALLY wouldn't recommend this, but it does work. Have a read of this tutorial

If you really want to do this, use
setLayout(null);
on the component you're putting things into, then use
setBounds(x, y, width, height);
to set the absolute coordinates of the elements. Example:
setLayout(null);
JButton myButton = new JButton("Do Stuff");
add(myButton);
myButton.setBounds(30, 30, 100, 30);
However, the resulting GUI will look non-standard, won't be resizable, and if it's of any complexity, will be a pain to maintain.
I know that layouting is frustrating, but in the end you will be better off using a combination of BorderLayouts and FlowLayouts, orGridBagLayout - or an IDE interface builder like Eclipse's or Netbeans'.

This doesn't answer your specific question, but as well as agreeing with the other comments, have a look at MiG Layout. I have been equally frustrated with layouts as a Swing newbie, but this has helped a lot.

Related

Setting a label text in Swing undos all button location movements

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.

Zoom on a JScrollPane with headers

I hava a JFrame containing a table with row and column headers.
My table is a custom component made of 3 panels (row header, column header and grid).
The panels are regular JPanels, containing either JButton or JLabel, in a MigLayout.
I display this component inside a JScrollPane in order to scroll simultaneously my grid and my headers.
This part works fine.
Now, the user should be able to zoom on my component.
I tried to use the pbjar JXLayer but if I put my whole JScrollPane inside the layer, everything is zoomed, event the scrollbars.
I tried to use 3 JXLayers, one for each viewPort of my JScrollPane. But this solution just mess up with my layout as the panels inside the viewPorts get centered instead of being top-left aligned.
import org.jdesktop.jxlayer.JXLayer;
import org.pbjar.jxlayer.demo.TransformUtils;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;
public class Matrix extends JScrollPane {
private Grid grid;
private Header rowHeader;
private Header columnHeader;
private DefaultTransformModel zoomTransformModel;
private double zoom = 1;
public Matrix() {
super(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
this.zoomTransformModel1 = new DefaultTransformModel();
this.zoomTransformModel1.setScaleToPreferredSize(true);
this.zoomTransformModel1.setScale(1);
this.zoomTransformModel2 = new DefaultTransformModel();
this.zoomTransformModel2.setScaleToPreferredSize(true);
this.zoomTransformModel2.setScale(1);
this.zoomTransformModel3 = new DefaultTransformModel();
this.zoomTransformModel3.setScaleToPreferredSize(true);
this.zoomTransformModel3.setScale(1);
this.grid = new Grid();
this.setViewportView(TransformUtils.createTransformJXLayer(this.grid,
zoomTransformModel1););
this.matrixRowHeader = new Header(Orientation.VERTICAL);
this.setRowHeader(new JViewport(
TransformUtils.createTransformJXLayer(
this.rowHeader, zoomTransformModel2)));
this.matrixColumnHeader = new Header(Orientation.HORIZONTAL);
this.setColumnHeader(new JViewport(
TransformUtils.createTransformJXLayer(
this.columnHeader, zoomTransformModel2)));
}
public void setScale(double scale) {
this.zoomTransformModel1.setScale(scale);
this.zoomTransformModel2.setScale(scale);
this.zoomTransformModel3.setScale(scale);
}
}
How could I handle the zoom on my JScrollPane without zooming on the scrollBars and without messing up my layout?
First, MigLayout seems to be incompatible with the JXLayer. When using both, the components in the panel using the MigLayout have a unpredictable behaviour.
Then, the original pbjar JXLayer only allows you to put your component in the center of the Layer pane.
Pbjar sources can be download on github. Note this is not the official Piet Blok repository.
The solution I found is to modify the TransformLayout, TransformUI, and the TranformModel classes:
Alignment enum give the possible alignment for the component in the layer.
public enum Alignment {
TOP,
BOTTOM,
LEFT,
RIGHT,
CENTER
}
In TransformLayout :
#Override
public void layoutContainer(Container parent) {
JXLayer<?> layer = (JXLayer<?>) parent;
LayerUI<?> layerUI = layer.getUI();
if (layerUI instanceof CustomTransformUI) {
JComponent view = (JComponent) layer.getView();
JComponent glassPane = layer.getGlassPane();
if (view != null) {
Rectangle innerArea = new Rectangle();
SwingUtilities.calculateInnerArea(layer, innerArea);
view.setSize(view.getPreferredSize());
Rectangle viewRect = new Rectangle(0, 0, view.getWidth(), view
.getHeight());
int x;
int y;
Alignment alignX = ((CustomTransformUI) layerUI).getAlignX();
Alignment alignY = ((CustomTransformUI) layerUI).getAlignY();
if(alignX == Alignment.LEFT) {
x = (int) (innerArea.getX() - viewRect.getX());
} else if(alignX == Alignment.RIGHT) {
x = (int) (innerArea.getX()+innerArea.getWidth()-viewRect.getWidth()-viewRect.getX());
} else {
x = (int) Math.round(innerArea.getCenterX()
- viewRect.getCenterX());
}
if(alignY == Alignment.TOP) {
y = (int) (innerArea.getY() - viewRect.getY());
} else if(alignY == Alignment.BOTTOM) {
y = (int) (innerArea.getY()+innerArea.getHeight()-viewRect.getHeight()-viewRect.getY());
} else {
y = (int) Math.round(innerArea.getCenterY()
- viewRect.getCenterY());
}
viewRect.translate(x, y);
view.setBounds(viewRect);
}
if (glassPane != null) {
glassPane.setLocation(0, 0);
glassPane.setSize(layer.getWidth(), layer.getHeight());
}
return;
}
super.layoutContainer(parent);
}
In TransformUI :
private Alignment alignX; // horizontal alignment
private Alignment alignY; // verticalalignment
public TransformUI(TransformModel model, Alignment alignX, Alignment alignY) {
super();
this.setModel(model);
this.alignX = alignX;
this.alignY = alignY;
}
public Alignment getAlignX() {
return alignX;
}
public Alignment getAlignY() {
return alignY;
}
In TransformModel:
private Alignment alignX = Alignment.CENTER;
private Alignment alignY = Alignment.CENTER;
public CustomTransformModel(Alignment alignX, Alignment alignY) {
super();
this.alignX = alignX;
this.alignY = alignY;
}
#Override
public AffineTransform getTransform(JXLayer<? extends JComponent> layer) {
JComponent view = (JComponent)layer.getView();
/*
* Set the current actual program values in addition to the user
* options.
*/
this.setValue(Type.LayerWidth, layer == null ? 0 : layer.getWidth());
this.setValue(Type.LayerHeight, layer == null ? 0 : layer.getHeight());
this.setValue(Type.ViewWidth, view == null ? 0 : view.getWidth());
this.setValue(Type.ViewHeight, view == null ? 0 : view.getHeight());
/*
* If any change to previous values, recompute the transform.
*/
if (!Arrays.equals(this.prevValues, this.values)) {
System.arraycopy(this.values, 0, this.prevValues, 0, this.values.length);
this.transform.setToIdentity();
if (view != null) {
double scaleX;
double scaleY;
double centerX;
if(this.alignX == Alignment.LEFT) {
centerX = 0.0;
} else if (this.alignX == Alignment.RIGHT){
centerX = layer == null ? 0.0 : (double)layer.getWidth();
} else {
centerX = layer == null ? 0.0 : (double)layer.getWidth() / 2.0;
}
double centerY;
if(this.alignY == Alignment.TOP) {
centerY = 0.0;
} else if(this.alignY == Alignment.BOTTOM){
centerY = layer == null ? 0.0 : (double)layer.getHeight();
} else {
centerY = layer == null ? 0.0 : (double)layer.getHeight() / 2.0;
}
AffineTransform nonScaledTransform = this.transformNoScale(centerX, centerY);
if (((Boolean)this.getValue(Type.ScaleToPreferredSize)).booleanValue()) {
scaleY = scaleX = ((Double)this.getValue(Type.PreferredScale)).doubleValue();
} else {
Area area = new Area(new Rectangle2D.Double(0.0, 0.0, view.getWidth(), view.getHeight()));
area.transform(nonScaledTransform);
Rectangle2D bounds = area.getBounds2D();
scaleX = layer == null ? 0.0 : (double)layer.getWidth() / bounds.getWidth();
scaleY = layer == null ? 0.0 : (double)layer.getHeight() / bounds.getHeight();
if (((Boolean)this.getValue(Type.PreserveAspectRatio)).booleanValue()) {
scaleY = scaleX = Math.min(scaleX, scaleY);
}
}
this.transform.translate(centerX, centerY);
this.transform.scale((Boolean)this.getValue(Type.Mirror) != false ? - scaleX : scaleX, scaleY);
this.transform.translate(- centerX, - centerY);
this.transform.concatenate(nonScaledTransform);
}
}
return this.transform;
}
You can now create a zoomable panel with configurable alignment using:
TransformModel model = new TransformModel(Alignment.LEFT, Alignment.TOP);
TransformUI ui = new TransformUI(model, Alignment.LEFT, Alignment.TOP);
new JXLayer((Component)component, (LayerUI)ui)
Note that's a quick fix. It can probably be improved.

FlowLayout does not use multiple lines within GridBagLayout

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.

JTextArea wont shrink inside JPanel with layout manager

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

JPanel inside a JScrollPane

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);
}
}
}

Categories

Resources