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());
Related
At the moment I've got a 9x9 grid of text fields using a GridBayLayout. Because the grid makes each text field slighter longer than it is tall (even when I set a specific height for the text field), the grid is a rectangle, rather than a square.
How can I either make the entire grid a specific size, or each text field inside the grid a specific size?
EDIT: I need the 9 x 9 grid of text fields to be square, rather than rectangular.
The problem is with sizing hints, methods [get/set][Minimum/Maximum/Preferred]Size are telling the layout manager what size the component wants to be and layout managers sometimes ignore that and sometimes they don't. GridBagLayout respects that and your JTextFields are tellimg him that they want to be rectangular. How JTextFields figure out what size they want to be is all kinds of difficult so let's leave that out.
If you want to make sure that all your JTextFields are of the same size, you should override these methods. Subclass JTextField and make something like this:
final class SquareJTextField extends JTextField {
private static Dimension maxDimension = new Dimension(0, 0);
#Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
// take the larger value
int max = d.width > d.height ? d.width : d.height;
// compare it against our static dimension
// height je rovnaky ako width
if (max > maxDimension.width)
maxDimension = new Dimension(max, max);
// return copy so no one can change the private one
return new Dimension(maxDimension);
}
#Override
public Dimension getMinimumSize() {
Dimension d = super.getPreferredSize();
int max = d.width > d.height ? d.width : d.height;
if (max > maxDimension.width)
maxDimension = new Dimension(max, max);
return new Dimension(maxDimension);
}
#Override
public Dimension getMaximumSize() {
Dimension d = super.getPreferredSize();
int max = d.width > d.height ? d.width : d.height;
if (max > maxDimension.width)
maxDimension = new Dimension(max, max);
return new Dimension(maxDimension);
}
}
All objects of this class should be the same dimension and should be square.
Replace JtextFields in your grid with SquareJTextField
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.
In Java, is it possible to get the Width and Height of the JFrame without the title and other borders?
frame.getWidth() and frame.getHeight()1 seems to return the width including the border.
Thanks.
frame.getContentPane().getSize();
frame.pack();
System.out.println("frame width : "+getWidth());
System.out.println("frame height: "+getHeight());
System.out.println("content pane width : "+getContentPane().getWidth());
System.out.println("content pane height: "+getContentPane().getHeight());
System.out.println("width of left + right borders: "+(getWidth()-getContentPane ().getWidth()));
System.out.println("height of top + bottom borders: "+(getHeight()-getContentPane().getHeight()));
This works fine
frame.getContentPane().getSize();
But not if you haven't added content yet. In my case, I wanted to calculate the inner dimensions of the JFrame before adding content, so I could divide up the content accordingly. Here's what I came up with.
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
pack(); // Need this, otherwise insets() show as 0.
int scrW = (int)screenSize.getWidth();
int scrH = (int)screenSize.getHeight();
int innerW = scrW - getInsets().left - getInsets().right;
int innerH = scrH - getInsets().top - getInsets().bottom;
// Need to setSize(), otherwise pack() will collapse the empty JFrame
setSize(scrW, scrH);
Here is a code snippet which works on JFrame as well as AWT's Frame (which happens to be the super-type of JFrame):
public static Dimension getInnerSize(Frame frame) {
Dimension size = frame.getSize();
Insets insets = frame.getInsets();
if (insets != null) {
size.height -= insets.top + insets.bottom;
size.width -= insets.left + insets.right;
}
return size;
}
Beware: The insets are only valid once the frame has been shown.
Here is another code snippet to work around this problem:
private static Insets defaultInsets;
public static Insets getInsetsWithDefault(Frame frame) {
// insets only correct after pack() and setVisible(true) has been
// called, so we use some fallback strategies
Insets insets = frame.getInsets();
if (insets.top == 0) {
insets = defaultInsets;
if (insets == null) {
insets = new Insets(26, 3, 3, 3);
// usual values for windows as our last resort
// but only as long as we never saw any real insets
}
} else if (defaultInsets == null) {
defaultInsets = (Insets) insets.clone();
}
return insets;
}
This code needs to be called once with a visible Frame. After that it can correctly predict the insets even for invisible frames (due to caching of the defaultInsets), assuming they are always the same.
Of course this only works if all windows get the same window-decorations. But i am not aware of any case where they might differ from window to window.
This might be useful, too:
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowOpened(WindowEvent e) {
MyUtilClass.getInsetsWithDefault(frame); // init the defaultInsets
}
});
It will call the getInsetsWithDefault() method once the window is visible and initialize the correct defaultInsets.
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);
}
}
}
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.