Why does my JTextArea overflow the frame in GroupLayout?
I'm observing some strange behavior using GroupLayout. I've got a JTextArea that is contained inside of a JScrollPane that is resizing and pushing other components out of the JFrame. Oddly, if I rearrange the layout so that the JTextArea has nothing above or below it (no gaps, either), it works fine. It is as if the text area is asking the container how much space is in the container, and then taking 100% of it, regardless of other components. The other strange thing is that it appears to happen only when the JTextArea (not JScrollPane) size plus the other component heights within the container reach Short.MAX_VALUE.
If I specify the max size in the vertical group for the scroll pane (when adding the component to the layout) to a value less than Short.MAX_VALUE, it seems to fix the problem (as long as the difference between the value and Short.MAX_VALUE is greater than the heights of all the other components). e.g.
.addComponent(textArea, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE - 500)
Also, if I set the preferred size to a small positive value and instead of GroupLayout.PREFERRED_SIZE or GroupLayout.DEFAULT_SIZE, it also seems to make this behavior go away. e.g.
.addComponent(textArea, 0, 1, Short.MAX_VALUE)
The Java Tutorials on GroupLayout don't seem to mention anything about this and tend to use Short.MAX_VALUE all over the place. I tried Googling to find an answer, but I found this problem very difficult to describe in search terms.
Have I found a bug, or do I just not understand GroupLayout? The latter certainly seems more likely.
This example will create a simple text area. Push the lower button to populate it with text (and resize the JTextArea inside the JScrollPane). You can then click inside the text area and add or remove lines. After adding some extra lines, click the redraw button (or resize the frame) to see the odd behavior.
public class GroupLayoutTest {
public GroupLayoutTest() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
final JFrame frame = new JFrame("GroupLayout test");
Container panel = frame.getContentPane();
GroupLayout layout = new GroupLayout(panel);
panel.setLayout(layout);
JButton addBtn = new JButton("Add Lines");
JButton redrawBtn = new JButton("Redraw");
final JTextArea textArea = new JTextArea();
final JScrollPane textPane = new JScrollPane(textArea);
layout.setHorizontalGroup(layout.createParallelGroup()
.addComponent(redrawBtn)
.addComponent(textPane)
.addComponent(addBtn));
layout.setVerticalGroup(layout.createSequentialGroup()
.addComponent(redrawBtn)
.addComponent(textPane)
.addComponent(addBtn));
开发者_高级运维 addBtn.addActionListener(new ActionListener() {
int m = 0;
@Override
public void actionPerformed(ActionEvent e) {
for (int i = m; m < i + 2044; ++m) {
textArea.append("Line " + m + "\n");
}
// redraw the frame
frame.validate();
}
});
redrawBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
frame.validate();
}
});
frame.setPreferredSize(new Dimension(640, 480));
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static void main(String[] args) {
new GroupLayoutTest();
}
}
I am not that familiar with GroupLayout, this is just what I observed from looking at the docs and thinking about it. Hopefully it's helpful. My answer got kind of long, so I'll summarize up top:
Why does this happen? A combination of 3 things.
- GroupLayout respects the preferred size of your components. (that's why you chose it, judging by your comments).
- Your vertical group is sequential. It lay out one component at a time - in their preferred size, or larger if there is room.
- Your JScrollPane has an unbounded preferred size. It is growing to whatever the preferred size of the JTextArea is...leaving no room for the component below it once its preferred size becomes larger than that of your frame.
Combining a layout manager that respects preferred size with a component with a very large preferred size will generally always cause this problem. I haven't tested extensively, but I know it also happens in FormLayout too (if you tell it to use preferred size).
Longer attempt at explanation / thought process:
It is as if the text area is asking the container how much space is in the container, and then taking 100% of it, regardless of other components.
The text area isn't asking the container how big it can get - it's the opposite - it's telling the container how big it wants to be. In this case, the textArea is directly contained in a scroll pane. The text area's preferred size is going to grow with the size of the text in the text area. The scroll pane's preferred size is going to grow with the text area's preferred size, unless you set a preferred size on the scroll pane. From there, the layout manager is going to decide how to lay things out.
In this case, you are using a GroupLayout with a SequentialGroup for the vertical layout. This is going to lay out the components in order, based on their min/pref/max sizes. If you reposition the textPane as the last item in the group, it isn't going to steal space from your other components...so my guess is that for very large sizes, GroupLayout doesn't care if all components get displayed - if there's no room left after the last component, they are not displayed until the container is enlarged. For small sizes, users can adjust the size of the frame...but for big sizes like in your example, it isn't feasible.
Separate from the omission - it looks like merely the combination of a huge scroll pane and GroupLayout doesn't know when to stop...so the bottom of the scroll pane bar gets cut off. This can all be avoided by just setting a reasonable preferred size on the JScrollPane, though, as @camickr suggested.
The other strange thing is that it appears to happen only when the JTextArea (not JScrollPane) size plus the other component heights within the container reach Short.MAX_VALUE.
If you log the sizes of each components out, you'll see that unless you specify a preferred size for the JScrollPane, it will always have a preferred size greater than that of the textArea, so I don't think the above statement is true. The size of the scroll pane plus the other components in the container will still surpass Short.MAX_VALUE, and does appear to cause issues in GroupLayout, that much is true.
The point of a JScrollPane as I see it is to house a component with a large (or potentially large) preferred size in a contained, smaller area (with scroll bars as appropriate). You can then always let the scrollpane grow to a size greater than its preferred size...but it starts with telling a scroll pane how big it should be. In fact, setting a preferred size is effectively telling the JScrollPane when it needs scroll bars. E.g., if the preferred size of the scroll pane is 400,300, then whenever the preferred width of the contained component exceeds 400 (or the size of the scroll pane, if it's in a container that is letting it grow to more than its preferred size), show horizontal scroll bars. Similarly, for height as well. Otherwise it's always going to grow to the size of what you have in it, and it won't need scroll bars, eliminating the point, or in some cases, preventing other components from showing.
The docs for GroupLayout mention it is typically used by other layout builder tools, and not usually by developers (although you still can). I would consider using a different layout over one that routinely requires you to use special max values in order to work properly. My personal favorite is FormLayout, a free, BSD-license thirdparty layout manager. I don't think you should ever need to specify fixed sizes for normal components, but in the case of scroll panes you do want to give it a preferred size in layouts that respect preferred size.
I want all of my layouts to respect DPI, grow where appropriate, and work fine with internationalization (where text lengths may be very different)...so I would not want to use anything that requires specifying fixed sizes for everything. I think that in most layouts, setting a fixed preferred size on a scroll pane is not a bad thing. Doing so on a button, text field, or other component that clearly has an obvious preferred size, not so much.
Here is how it would look in FormLayout (it also has builders, but in this case I am using cell constraints to span a column easily):
FormLayout layout = new FormLayout(
"pref,fill:pref:grow", // cols
"pref,3dlu,fill:pref:grow,3dlu,pref" // rows
);
JPanel panel = new JPanel(layout);
CellConstraints cc = new CellConstraints();
panel.add(redrawBtn, cc.xy(1, 1));
panel.add(textPane, cc.xyw(1, 3, 2)); // span 2 columns
panel.add(addBtn, cc.xy(1, 5));
frame.setContentPane(panel);
I don't know the internals of the GroupLayout, but generally you need to specify a preferred size of the textArea/scrollpane combo to give the layout manager some information to work with. This is done by using either:
final JTextArea textArea = new JTextArea(10, 30);
or
textPane.setPreferredSize( new Dimension(400, 200) );
Otherwise I believe the preferred size is basically the size of all the text added to the text area.
精彩评论