PDA

View Full Version : Java / Swing / SpringLayout bug - not for the faint of heart.




toddburch
Mar 25, 2007, 12:56 AM
This is eating my lunch. There is a lot of code here, and the problem lies somewhere with not being able to convince the SpringLayout layout manager that he needs to recheck his constraints.

First, the file name should be MySLO.java.

The problem lies with the JTextArea in the middle of the application window. If the window is resized, the layout goes awry.

When the window is resized to the right:

all buttons and the label stay put as desired, and
the JTextArea expands to the right as desired.


So far, so good.

However, when the window is resized to the left:

all buttons and the label will move to the right (evensomuch as off-screen) and
the JTextArea is not resized smaller to fix how the constraints are defined.

All this occurs when JTextArea.setLineWrap(true) is set.

If setLineWrap(false) is set, the behavior changes:

When the window is resized left or right, all buttons and the label stay where they are supposed to, and the JTextArea is resized properly.

However, when you type, you get no line wrapping, and the box is now worthless for long phrases. I guess, this scenario is working as designed. But, I need the line wrap, so it's not really applicable to my needs.

Here's the code. If you can figure this out, I owe you a beer (or a kool-aid! ;) )

Todd

p.s.
newbie warning with Java...
Code in question is RED
I've used adapter classes for the buttons



// My SpringLayOut bug.
public class MySLO extends javax.swing.JFrame {

static javax.swing.JTextArea myText ;

// Create a window with all the GUI stuff, and display it.
public static void main(String[] args) {
javax.swing.JFrame win = new MySLO() ;
win.setVisible(true) ; // make window appear
}


// This is the GUI window maker. It builds the buttons, does the layout,
// registers for events, etc...
public MySLO() {
super("My SpringLayout Bug - Stretch Window to the Right, then Left, to see the bug") ; // This is the window title

// Get the JFrame's container.
java.awt.Container content = this.getContentPane() ;

// Set the Layout Manager
javax.swing.SpringLayout myLayout = new javax.swing.SpringLayout() ;
content.setLayout(myLayout) ;


// Connect and Disconnnect Buttons.
javax.swing.JButton buttonConnect = new javax.swing.JButton("Connect") ;
buttonConnect.addActionListener(new ButtonConnect(this)) ;
content.add(buttonConnect) ;

javax.swing.JButton buttonDisconnect = new javax.swing.JButton("Disconnect") ;
buttonDisconnect.addActionListener(new ButtonDisconnect(this)) ;
content.add(buttonDisconnect) ;


// This is the label for the box that is screwing up
javax.swing.JLabel enterLabel = new javax.swing.JLabel() ;
enterLabel.setText("Enter Text:") ;
content.add(enterLabel) ;

// This is the JTextArea box that is screwing up
myText = new javax.swing.JTextArea(10,50) ;

// If this setLineWrap is set to false, the problem is that the TextArea grows to the right
// while the window stays the same size. Pretty soon, you can't see what you type.

// If this setLineWrap is set to true, there is an issue with resizing the whole window - all
// the other widgets move incorrectly, and this TextArea can grow but will never shrink.

myText.setLineWrap(true) ; // Cause it to wrap lines. Change to false for no line wrap.

// Add a listener so I can force a validation of the layout - but it has no effect... :(

myText.addComponentListener(new java.awt.event.ComponentListener() {
public void componentResized(java.awt.event.ComponentEvent e) {
System.out.println("Validating...") ;
validate() ; // In theory, this should validate the layout!!
}
public void componentShown(java.awt.event.ComponentEvent e ) { }
public void componentMoved(java.awt.event.ComponentEvent e ) { }
public void componentHidden(java.awt.event.ComponentEvent e) { }
} ) ;
content.add(myText) ;

javax.swing.JButton buttonClear = new javax.swing.JButton("Clear") ;
buttonClear.addActionListener(new ButtonClear(this)) ;
content.add(buttonClear) ;

javax.swing.JButton buttonSubmit = new javax.swing.JButton("Submit") ;
buttonSubmit.addActionListener(new ButtonSubmit(this)) ;
content.add(buttonSubmit);

this.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE) ; // Close app when RED X is clicked.

// The following code sets up the Spring Layout Constraints - it positions everything
// in the window and influences object placement when the window is resized.

// Row 1 - Constrain to the Top & Left Edge
myLayout.putConstraint(javax.swing.SpringLayout.WEST, buttonConnect, 135, javax.swing.SpringLayout.WEST, content);
myLayout.putConstraint(javax.swing.SpringLayout.NORTH, buttonConnect, 15, javax.swing.SpringLayout.NORTH, content);

myLayout.putConstraint(javax.swing.SpringLayout.WEST, buttonDisconnect, 15, javax.swing.SpringLayout.EAST, buttonConnect);
myLayout.putConstraint(javax.swing.SpringLayout.NORTH, buttonDisconnect, 15, javax.swing.SpringLayout.NORTH, content);

// Row 2 - Constrain to the Top and Left Edge.
myLayout.putConstraint(javax.swing.SpringLayout.EAST, enterLabel, 120, javax.swing.SpringLayout.WEST, content);
myLayout.putConstraint(javax.swing.SpringLayout.NORTH, enterLabel, 50, javax.swing.SpringLayout.NORTH, content);

myLayout.putConstraint(javax.swing.SpringLayout.WEST, myText, 135, javax.swing.SpringLayout.WEST, content);
myLayout.putConstraint(javax.swing.SpringLayout.NORTH, myText, 50, javax.swing.SpringLayout.NORTH, content);

// Row 3 - Constrain to the Top and Left Edge
myLayout.putConstraint(javax.swing.SpringLayout.WEST, buttonClear, 135, javax.swing.SpringLayout.WEST, content);
myLayout.putConstraint(javax.swing.SpringLayout.NORTH, buttonClear, 15, javax.swing.SpringLayout.SOUTH, myText);

myLayout.putConstraint(javax.swing.SpringLayout.WEST, buttonSubmit, 15, javax.swing.SpringLayout.EAST, buttonClear);
myLayout.putConstraint(javax.swing.SpringLayout.NORTH, buttonSubmit, 15, javax.swing.SpringLayout.SOUTH, myText);

// Constrain to the Bottom.
myLayout.putConstraint(javax.swing.SpringLayout.SOUTH, content, 15, javax.swing.SpringLayout.SOUTH, buttonSubmit);

// Constrain to the right Edge
myLayout.putConstraint(javax.swing.SpringLayout.EAST, content, 20, javax.swing.SpringLayout.EAST, myText);

this.pack() ; // Resize the window as needed.

// Center the Dialog on the screen.
java.awt.Dimension screen = java.awt.Toolkit.getDefaultToolkit().getScreenSize() ;
int x = (screen.width - this.getWidth() ) / 2 ;
int y = (screen.height - this.getHeight() ) / 2 ;
setBounds(x,y,this.getWidth(),this.getHeight()) ;
}

// Adapter Classes - to separate the GUI from the application logic (not that there IS any appl. logic yet...

class ButtonClear implements java.awt.event.ActionListener {
MySLO data ;
ButtonClear (MySLO data ) {
this.data = data ;
}
public void actionPerformed( java.awt.event.ActionEvent ae) {
data.myText.setText("") ;
System.out.println("Input data has been cleared") ;
}
}

class ButtonSubmit implements java.awt.event.ActionListener {
MySLO data ;
ButtonSubmit (MySLO data ) {
this.data = data ;
}
public void actionPerformed( java.awt.event.ActionEvent ae) {
System.out.println("Submit Button") ;
}
}

class ButtonConnect implements java.awt.event.ActionListener {
MySLO data ;
ButtonConnect (MySLO data ) {
this.data = data ;
}
public void actionPerformed( java.awt.event.ActionEvent ae) {
System.out.println("Connect Button") ;
}
}

class ButtonDisconnect implements java.awt.event.ActionListener {
MySLO data ;
ButtonDisconnect (MySLO data ) {
this.data = data ;
}
public void actionPerformed( java.awt.event.ActionEvent ae) {
System.out.println("Disconnect Button") ;
}
}
}



toddburch
Mar 26, 2007, 09:46 AM
After countless hours of trying to get this to work, I've decided to can this layout manager and find a more user friendly one. There are too many aspects of this layout manager that are not working as expected, or, it's not documented good enough for me to grasp it.

Todd

jeremy.king
Mar 26, 2007, 01:26 PM
Todd, you may want to consider a BorderLayout based on your GUI so far.

Also, consider putting your JTextArea inside a JScrollPane so scrollbars can be used.

toddburch
Mar 26, 2007, 01:32 PM
OK, thanks for the tip. My to-do tonight is to read up on all the layout managers. Will do the scrollbar too. Got to read up on that as well.

Todd

hsvmoon
Mar 26, 2007, 01:45 PM
I have seen this issue before. The usual fix is to add the text area to either a scroll pane or a JPanel then to the top level componet. Also note that you are adding the resize listener to the text area itself. This will not catch all changes to the top level components.

toddburch
Mar 26, 2007, 01:59 PM
I have seen this issue before. The usual fix is to add the text area to either a scroll pane or a JPanel then to the top level componet. Also note that you are adding the resize listener to the text area itself. This will not catch all changes to the top level components.

Yes, I did notice that last night. "Validate...'" would print out once for the initial display, and then again only if I typed so much data that the box itself changed size.

Perhaps I'll add the JScrollPane to see the effects before leaving for greener pastures.

I've read about adding top-level component listeners (parent objects) versus adding component-specific listeners, and now as you've pointed out - this point really sinks it in. :)

Todd

TeppefallGuy
Mar 26, 2007, 06:11 PM
Here is a quick prototype I did in Fabric (http://us.teppefall.com/products/fabric.jsp). Not pretty, but I guess it would work.


<jfc>
<layout class="java.awt.BorderLayout">
<layout class="java.awt.BorderLayout" constraint="North">
<layout class="java.awt.FlowLayout" constraint="West">
<component class="javax.swing.JButton" text="Connect"/>
<component class="javax.swing.JButton" text="Disconnect"/>
</layout>
</layout>
<layout class="java.awt.BorderLayout" constraint="Center">
<component class="javax.swing.JLabel" text="Enter text:" constraint="North"/>
<component class="javax.swing.JScrollPane" constraint="Center">
<property name="viewportView" type="Component">
<component class="javax.swing.JTextPane"/>
</property>
</component>
</layout>
<layout class="java.awt.BorderLayout" constraint="South">
<layout class="java.awt.FlowLayout" constraint="West">
<component class="javax.swing.JButton" text="Clear"/>
<component class="javax.swing.JButton" text="Submit"/>
</layout>
</layout>
</layout>
</jfc>


Which looks like this on Windows and OS X:

http://us.teppefall.com/2007/test.png

http://us.teppefall.com/2007/testMac.png

toddburch
Mar 26, 2007, 07:20 PM
Tepppefalguy, that seems pretty powerful. Looks like I would still need to be well versed in layout managers - you're even combing two different ones there.

I read the PDF on your website. How would you attach events to this? Would it work with Adapter classes? Is this something you sell?

Todd