Search code examples
javaswingresizejscrollpanejtree

make JScrollPane lower in hierarchy resize when JFrame does


I have a JTree on a JPanel which is on a JScrollPane which is the left component of a JSplitPane. The JSplitPane is on its own JScrollPane. The latter is not allowed to display its vertical scroll bar... instead, whenever the JFrame is resized so that some of the tree goes out of view I want the tree panel's vertical scroll bar to kick in...

In other words I want the viewport of the tree's scroll pane always to be the same height as the height of the frame's content pane... I have tried various experiments along these lines

Here is an SSCCE:

import java.awt.*;
import java.awt.event.ComponentEvent;
import java.lang.reflect.InvocationTargetException;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;


public class Packing {
  public static void main( String[] args ) throws InterruptedException, InvocationTargetException{
    EventQueue.invokeLater( new Runnable(){
      @Override
      public void run() {
        JTree jt = new JTree();
        DefaultTreeModel model = (DefaultTreeModel)jt.getModel();
        for( int i = 0; i < 30; i++ ){
          model.insertNodeInto( new DefaultMutableTreeNode( i ), 
              (DefaultMutableTreeNode)model.getRoot(), 0 );
        }


        JPanel left_panel = new JPanel();
        left_panel.setLayout( new BorderLayout() );
        left_panel.add( jt, BorderLayout.WEST );
        JScrollPane tree_jsp = new JScrollPane( left_panel );


        JSplitPane split_pane = new JSplitPane();
        split_pane.setDividerLocation( 0.5d );
        split_pane.setLeftComponent( tree_jsp );
        JScrollPane split_jsp = new JScrollPane( split_pane );
        split_jsp.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER );


        JFrame frame = new JFrame( "Resizing prob" );
        frame.getContentPane().add( split_jsp );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.addComponentListener(new java.awt.event.ComponentAdapter() {
          public void componentResized(ComponentEvent e) {
            System.out.println( ">>> frame resized" );
          }
        });
        frame.pack();
        frame.setVisible( true );
      }
    });
  }
}

edit

possibly may have cracked it... added these lines to componentResized:

  int vp_width = tree_jsp.getViewport().getSize().width;
  tree_jsp.getViewport().setPreferredSize( 
            new Dimension( vp_width, frame.getContentPane().getSize().height - 40 ));
  tree_jsp.getViewport().validate();

obviously this is a rough draft... the 40 figure is an oddity which I'm trying to understand... on my machine the resizing doesn't work if this figure is set to 20 or less...

NB also tree_jsp has to be made final, obviously, and these two lines might be added to the main bit of code to understand the method in my madness

  JTable table = new JTable( 1, 2 );
  left_panel.add( table, BorderLayout.EAST );

and another thing: we're told not to use setPreferredSize... maybe the above behaviour could be achieved purely by using layouts...?

edit 2

came to the conclusion in the end that my aim was a bit skewed: really I needed a scroll pane on the left-hand component and a separate scroll pane on the right-hand one. After that it seems there should be no need for a scroll pane on the JSplitPane (or JPanel containing it): scroll panes holding scroll panes lower in the container hierarchy no doubt have to exist sometimes... but I realised it's quite a high priority to design this situation out... I found problems with BorderLayout really doing what I wanted... a combination of BoxLayout and GridLayout really helped (this is all in the context of trying to avoid setXXXSize completely). No doubt the answer also lies in trying to get to grips with GridBagLayout...


Solution

  • Get rid of the panel. Just add the JTree to the JScrollPane:

    //        JPanel left_panel = new JPanel();
    //        left_panel.setLayout( new BorderLayout() );
    //        left_panel.add( jt, BorderLayout.WEST );
    //        JScrollPane tree_jsp = new JScrollPane( left_panel );
            JScrollPane tree_jsp = new JScrollPane( jt );
    

    Edit: you should always be adding the tree to the scrollpane because that is what you want to scroll when there is not enough space. But you can then add the scrollpane to the panel and the panel to the splitpane:

        JScrollPane tree_jsp = new JScrollPane( jt );
    
        JPanel left_panel = new JPanel();
        left_panel.setLayout( new BorderLayout() );
        left_panel.add( tree_jsp, BorderLayout.WEST );
    
        JSplitPane split_pane = new JSplitPane();
        split_pane.setDividerLocation( 0.5d );
        // split_pane.setLeftComponent( tree_jsp );
        split_pane.setLeftComponent( left_panel );
    

    Edit2: if you only want a scrollpane on the lef_panel then you only need a single scrollpane. You don't need a scrollpane for the split pane. Just add the splitpane directly to the frame:

    //frame.getContentPane().add( split_jsp );
    frame.add( split_pane );
    

    This approach may cause problems with the JTable because it is always expected that a table is added to a scrollpane since this is the easiest way to display the table header.

    Also, you don't need the getContentPane() method since JDK5, the frame will add components to the content pane for you.

    Edit 3: you can play with the Scollable interface to tell the scrollpane to fix the height of component added to the scrollpane to be the height of the scrollpane. See Scrollable Panel for a helper class. Using this class your code would be:

    //JScrollPane split_jsp = new JScrollPane( split_pane );
    //split_jsp.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER );
    
    ScrollablePanel scrollable = new ScrollablePanel( new BorderLayout() );
    scrollable.setScrollableHeight( ScrollablePanel.ScrollableSizeHint.FIT );
    scrollable.setScrollableWidth( ScrollablePanel.ScrollableSizeHint.STRETCH );
    scrollable.add(split_pane);
    JScrollPane split_jsp = new JScrollPane( scrollable );