tags:

views:

876

answers:

5

I always have trouble with Java layouts, but the main thing bugging me now is that when the content changes, in particular changes it sizes, it's not laid out again properly. Take the example below:

package layouttest;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;

public class LayoutTestStart extends JFrame implements ActionListener
{
    static JButton button= new JButton("Expand");
    static JTextArea f = new JTextArea("A medium sized text");
    static LayoutTestStart lst;

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

    public static void createAndShowGUI()
    {
      lst  = new LayoutTestStart();
     lst.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     JPanel all = new JPanel();

     button.addActionListener(lst);

     all.add(button);
     all.add(f);
     lst.getContentPane().add(all);

     lst.setVisible(true);
     lst.pack();
    }
    @Override
    public void actionPerformed(ActionEvent e)
    {
     f.setText(f.getText()+"\n"+f.getText());

     // this doesn't work
     f.invalidate();

     // this does but it's cheating
//   lst.pack();
    }
}

The only way I get this to work is to call lst.pack(), but that's cheating since then each component should have a reference to it's JFrame, which gets messy when a component is a seperate class. What's the preferred way to let this example work?

A: 

First in regards to your assumption, you could call pack by getting the top level anscestor (there is such a method on components) and work out calling pack. You could make a static method and call that from everywhere this is needed. That would work.

Another option (perhaps the really prefered option) is to use a layout manager that accomidates this. GridBagLayout of course does it all, but there are those who claim it is not intended for mortals to use. MigLayout is an open source layout manager that is a bit more sane.

Yishai
I used hacking similar to your first suggestion, but it doesn't feel very nice.In regards to the second option: usually the field whose size changes is layered into a series of layout managers, so I'd have to change them all to sane layout managers.
kresjer
I had a look at MigLayout and it looks great (except for the name), but I don't see how I can change my example to use MigLayout and work automatically.
kresjer
Dynamic layout managers (unlike the default) are made to change the position of components as they grow and shrink, and as the frame grows and shrinks, so that you don't have to explicitly code for it.
Yishai
I tried doing that, but it doesn't work. I made it into JPanel all = new JPanel(new MigLayout());but the example still doesn't work. Could you post a complete example?
kresjer
I don't have MigLayout setup in my current environment to test an example, but what you need to do is pass string parameters to the MigLayout constructor to define that the grid the components are on can expand, and how much, and then in the add method on the panel, you pass a string as second parameter to specify how much the component can grow. This is documented (including the content of the string) in the quick start guide. http://www.miglayout.com/QuickStart.pdf
Yishai
I've tried for some time but couldn't get it working (but I loved MigLayout from the start). If you have some time to show how to fix my example it'd be great.
kresjer
+1  A: 

revalidate instead of invalidate. invalidate just marks the container as needing a layout. revalidate does that and then schedules a validate.

BTW: I suggest: avoid extending from JFrame and other components; avoid multiple inheritance of interface and avoid (mutable) statics.

Tom Hawtin - tackline
Can you go into more detail into the last paragraph Tom? In particular, why should you avoid extending components?
Pool
If I change the invalidate() to revalidate() in my source code it still doensn't work -- or are you suggesting something else?I know my code is ugly (although I might not know all the suggestions you gave -- I'll have a look) as I wanted to fit my example in one class :).
kresjer
A: 

updateUI() never fails! :D

Rodrigo Asensio
updateUI() is used for LAF changes. This is not a LAF change and should not be used.
camickr
+1  A: 

Well, generally, users don't like the size of the frame changing every time they hit enter. The frame should be designed to accomodate growth. So you would define the text area to have a given number of row and columns. Then you add the text area to a scroll pane and add the scrollpane to the frame. Then as data is changes scrollbars will appear or disappear as required.

If however you truly need to have a dynamically changing frame then you should use pack(). You can use:

SwingUtilities.windowForComponent(...)

where the Component is the source component of the ActionEvent, to find the Window to pack().

camickr
So far this looks like my best option
kresjer
A: 

The invalidate or validate on JTextArea alone doesn't work, you have to change the size of the top frame, as it doesn't update it's size automatically. For example you can do it like this (changed the code a little, to remove static variables):

package layouttest;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;    

public class LayoutTestStart extends JFrame implements ActionListener{

private JTextArea f = new JTextArea("A medium sized text");

public LayoutTestStart(){
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    JPanel all = new JPanel();
    JButton button = new JButton("Expand");
    button.addActionListener(this);

    all.add(button);
    all.add(f);
    getContentPane().add(all);
}

@Override
public void actionPerformed(ActionEvent e){
    javax.swing.SwingUtilities.invokeLater(new Runnable(){           
        @Override
        public void run(){
            f.setText(f.getText() + "\n" + f.getText());
            setSize(getPreferredSize());                
        }
    });

}

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(){
            LayoutTestStart lst = new LayoutTestStart();
            lst.setVisible(true);
            lst.pack();
        }
    });
}
}
Taisin