Java question: resizing components when the window resizes

I’m driving myself CRAZY with this. I’ve been searched the Sun site for two days, I’ve tried different techniques… none of it has been of any help. I know there are a few Java folks around so I’m hoping they can help.

Here’s the application:

JFrame, border layout. BorderLayout.NORTH has a JScrollPane that holds a JTable. BorderLayout.SOUTH has a JLabel that I’m using as a status bar. What I want to do is resize the JScrollPane when the window is resized so that the status label is always visible.

Everything I’ve tried is always a paint process behind. What happens is: I resize the window, and nothing happens; I resize the window again, and the scroll pane is resized to where it was supposed to have been last time; etc. It is always a step behind.

My first attempt was to implement ComponentListener on the main window which listened for resizing events and set the scroll pane size. This resulted in the one-resizing-behind above. So I said, huh, maybe I should put the listener on the scroll pane. This had no effect at all. So then I said, huh, maybe I should subclass the JScrollPane and override the paint method. This resulted in the one-step-behind resizing again. Additional calls to repaint in various places didn’t seem to help.

This has to be a simple thing to do but for Eris’s sake I cannot get it done.

Please help!

Not seeing your code, it’s hard to tell what’s missing. In all the Swing GUIs I’ve done, the resize just works without having to add listeners or modify the paint methods. Are you calling validate() before repaint()?

If you can post a bare-bones version of the GUI code, I’ll be happy to take a more detailed look.

You’re doing too much work – the GUI should automatically handle everything.

In general, one adds the JTable to BorderLayout.CENTER since that one allows growth in both directions.
Make sure that you are adding your JTable to the viewport of the scroll pane (myScrollPane.getViewPort).
Add the scroll pane to the JFrame.

You may wish to use a JPanel to contain the JLabel in BorderLayout.SOUTH. This gives you more flexibility.

In addition, you might look at your minimum, maximum, and preferred sizes for your components. Some wacky things happen if your minimum size is bigger than your gui can display.

Again, it should work fine without any event handlers or any other fussy code.

Well, I’ve dodged the issue by changing the layout. I’ve put the JScrollPane in the BorderLayout.CENTER panel which resizes both directions by default (north and south only resize horizontally, east and west only vertically). So I’ve gotten it to work the way I want; the issue is more academic: surely you can explicitly resize a component when the window resizes!

The snippet probably wouldn’t help, but here’s a good outline:


// some previous variable initialization, menu bar, etc...
// ...

	table = new JTable(model);
	table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

	statusLabel = new JLabel(SessionMonitor.STATUS);
	resultPane = new JScrollPane(table);
		
	resultPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
	resultPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
	getContentPane().add(resultPane,BorderLayout.NORTH);
	statusLabel.setBorder(BorderFactory.createLoweredBevelBorder());
	getContentPane().add(statusLabel,BorderLayout.SOUTH);
	logo = Toolkit.getDefaultToolkit().getImage("logo.jpg");
	setIconImage(logo);
                
	pack();
	setVisible(true);


That’s it. I mean it is a simple window to draw with only two components. The issue is where to catch a resizing event for the main window so that I can resize components before they get drawn. I thought simply overriding the paint method for the components might work, but it didn’t, or I didn’t do it right.

As the code stands above, it won’t resize properly in any way. The component sizes are pretty much fixed, vertically (they resize horizontally fine because of the default behavior of the layout manager).

If we add:



// in constructor
this.addComponentListener(this);

// ...somewhere else
public void ComponentResized(etc)
{
	// get new window size
	// get component sizes
	// use some math to resize the scrollpane so that the status label has room
	// set the scrollpane's new size
}

This code is always a step behind when the main application window implements a ComponentListener. Adding a repaint or update call at the end didn’t help, though I used some text output to the console window and all the numbers were right: components were sized properly. They just didn’t paint at the right time, and I couldn’t get them to repaint themselves for some ungodly reason.

Actually, yes, I’ve done this because depending on which arguments are passed to main additional components need to be added. I’m just trying to simplify the explanation. :slight_smile:

You can see the code above, I put the table in the constructor’s argument list. What trouble might I run into?

Right now I’m not setting anything’s minimum, maximum, or preferred etc size. Very simple construction.

If you try to do any kind of resizing whatsoever, you will be fighting the layout manager. If you wish to handle your sizing yourself, use a null layout manager or write an implementation of LayoutManager that does things the way you want.

If you can distill this code to a single-class standalone example (with main) that fails on your machine, I’ll happily run it and see what’s going on. I think it’s important to have a complete example since any code I write to wrap around your snippet might alter the “experiment.”

This is probably why I couldn’t find the information I needed. Implimenting a new kind of layout is beyond my interest level for this problem.

Sure, I can try. I’m not sure how to avoid complicating the underlying table model, but perhaps you would so I’m just going to put “null” in the table constructor instead of the model I’ve implimented which is sort of non-trivial. But since the scrollpane is what I want resized, I don’t think it should matter if there is a table in it, a drawing area, or fifteen thousand buttons. The scrollpane’s resizing behavior shouldn’t depend on its contents.



import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;

public class SessionMonitor extends JFrame implements Runnable
{
        private boolean threadedMode;
        private static final String title = "Session Monitor";
        private JTable table;
        private JScrollPane resultPane;
        private JTextField sqlEntry;
	private String lastSQL;
        private JMenu database;
        private JMenuBar menuBar;
        private JMenuItem connectMenu,selectionMenu,exitMenu;
	private JPanel statusPanel;

	private JLabel statusLabel;
	private String statusLabelString;
        
        public SessionMonitor(boolean isThreaded)
        {
                super(title);
                threadedMode = isThreaded;
		lastSQL = new String();
		statusLabelString = new String();
		lastUpdate = new String();

                menuBar = new JMenuBar();
                setJMenuBar(menuBar);
                database = new JMenu("Database");
                menuBar.add(database);
                database.setMnemonic('D');
                
		connectMenu = new JMenuItem("Connect to New Database");
                connectMenu.addActionListener(new ActionListener()
                                                {
                                                        public void actionPerformed(ActionEvent e) { doConnect(); }
                                                });
                database.add(connectMenu);
		
		selectionMenu = new JMenuItem("Select Session to Monitor");
		selectionMenu.addActionListener(new ActionListener()
						{
							public void actionPerformed(ActionEvent e) { doSelectSession(); }
							});
                if (isThreaded)
		{
			database.add(selectionMenu);		
		}

		exitMenu = new JMenuItem("Exit");
                exitMenu.addActionListener(new ActionListener()
                                                {
                                                        public void actionPerformed(ActionEvent e) { doExit(); }
                                                });
                database.add(exitMenu);   

                table = new JTable(null);
                table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

		sqlEntry = new JTextField("Type SELECT statement here and press Enter");
		statusLabel = new JLabel(SessionMonitor.STATUS);
		resultPane = new JScrollPane(table);
		
		resultPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
		resultPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
                getContentPane().add(resultPane,BorderLayout.NORTH);

		JPanel statusPanel = new JPanel();
                
                sqlEntry.addActionListener(new ActionListener()
                                               {
                                                       public void actionPerformed(ActionEvent e) { doEnter(); }
                                               });
		statusLabel.setBorder(BorderFactory.createLoweredBevelBorder());
		statusPanel.add(statusLabel,BorderLayout.SOUTH);

                if (!threadedMode)
                {
                        statusPanel.add(sqlEntry,BorderLayout.NORTH);
                }

		getContentPane().add(statusPanel,BorderLayout.SOUTH);
		statusBarThread = null;
                
                pack();
		setVisible(true);

                doConnect();
        }
        
        protected void processWindowEvent(WindowEvent e)
        {
                if (e.getID() == WindowEvent.WINDOW_CLOSING)
                {
                        dispose();
                        System.exit(0);
                }
                super.processWindowEvent(e);
        }
        
        private void doConnect()
        {
		/* opens a seperate dialog */
        }
        
        private void doExit()
        {
                dispose();
                System.exit(0);
        }
        
        private void doEnter()
        {
		/* executes an SQL statement */
        }

	private void doSelectSession()
	{
		/* opens a seperate dialog */
	}
        
        private void errorDialog(String msg)
        {
                JOptionPane.showMessageDialog(SessionMonitor.this,msg,"Error Encountered",JOptionPane.WARNING_MESSAGE);
        }

	public void run()
	{
		/* updates text based on the status of various components */
	}
        
        public static void main (String [] args)
        {
                boolean isThreaded=true;
                if (args!=null) 
                {
                        if (args.length > 0) 
                        {
                                if (args[0].equals("nothread"))
                                {
                                        isThreaded=false;
                                }
                        }
                }
                SessionMonitor theMonitor = new SessionMonitor(isThreaded);
        }
}


Right now, this is what I had that wasn’t resizing anything (or trying to) and which I tried to use to get the JScrollPane to resize. I simply had the SessionMonitor class implement ComponentListener and went over the routine I mentioned in the comments a post ago.

But I think you’ve nailed it by suggesting I’d need to implement my own layout manager to get resizing to work properly.

Meh, there are some dangling variables in there. Sorry.

Got it.

I plopped it into JBuilder and tidied up the dangling stuff and got exactly what you said would happen.

This fixed it:

getContentPane().add(resultPane,BorderLayout.CENTER);

Now, it resizes perfectly!

(Strange, but I recall mentioning using BorderLayout.CENTER somewhere else :))

Oh, no question that it works in the CENTER area, no question at all. That’s how I had the program built and it did what it should. The question was more along the lines of how I would do it if it was somewhere else, if it could even be done (like in NORTH or EAST where one direction doesn’t automatically resize). That’s the sticker.

Right!

I guess I was in a bit of a rush to get on my way home and I didn’t read that part.

First point: If you are trying to manually resize things, you are missing out on the Zen of layout managers.

That said, perhaps you really mean to say that you want to force a fixed height or something. That’s quite common.
You can set the minimum/preferred/maximum sizes of the scroll pane to all have the same fixed vertical dimension and some very large horizontal dimension. This should allow you to have a fixed-size table at the top.

Another option is to do like one does in VB (or once did – I haven’t touched it since VB6): use a null layout manager and manually set the location and size of everything, catching frame resize events and moving things around as desired.

Almost any layout you want to do can be achieved by nesting JPanels with varying layout managers, however.

What is your ultimate goal here?