How to set JSplitPane-Divider collapse/expand State?

16.8k views Asked by At

I have a JFrame with a JSplitPane that is OneTouchExpandable. I want to remember the last Divider position of the JSplitPane on JFrame dispose and restore the Position if the JFrame is reopened.

It works well, but if the User expand one Side via the oneTouchExpandable UI-Widget then I store only the 'int'-Position on dispose and set the 'int'-Position back again with the consequence on JFrame-resizing the JSplitPane-Divider jumps to the collapsed Component preferredSize.

How can I get/set the collapse/expand State?

EDIT

Now: the resize-Behavior is OK, but it is not exactly the same behavior like the first-time-open - cause now I have no MinimumDividerLocation. I wanted the SnapIn but further the collapsedState.

public class SplitPaneState {
    public static void main( String[] args ) {
        SwingUtilities.invokeLater( new Runnable() {
            @Override
            public void run() {
                new SplitPaneState().createAndSowGUI();
            }
        });
    }

    private int position = -1;
    private Dimension size = new Dimension( 500, 300 );

    private void createAndSowGUI() {
        final JFrame frame = new JFrame("frame");
        frame.setSize( 200, 100 );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setLocationRelativeTo( null );
        frame.getContentPane().add( new JButton( new AbstractAction(){
           {
               putValue( Action.NAME, "Open Dialog" );
           }
            @Override
            public void actionPerformed( ActionEvent e ) {
                final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JLabel( "left Component" ), new JLabel( "right Component" ));
                splitPane.setContinuousLayout( true );
                splitPane.setOneTouchExpandable( true );
                if(position != -1) {
                    boolean LeftIsCollapsed = position < splitPane.getMinimumDividerLocation();
                    if(LeftIsCollapsed) {
                        splitPane.getLeftComponent().setMinimumSize(new Dimension()); // fix by Martijn Courteaux
                        splitPane.setDividerLocation(0.0d);                           // fix by Martijn Courteaux
                    }else {
                        splitPane.setDividerLocation(position);
                    }
                }
                JDialog dialog = new JDialog(frame,"dialog"){
                    @Override
                    public void dispose() {
                        position = splitPane.getDividerLocation();
                        size = this.getSize();
                        super.dispose();
                    }
                };
                dialog.setSize( size );
                dialog.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
                dialog.setLocationRelativeTo( frame );
                dialog.getContentPane().add( splitPane );
                dialog.setVisible( true );
                }
           }
       ));
       frame.setVisible( true );
    }
}
7

There are 7 answers

3
Martijn Courteaux On BEST ANSWER

I found that it is possible to collapse one side of the splitpane by setting the minimum size of the component to new Dimension() and then set the divider location:

// Hide left or top
pane.getLeftComponent().setMinimumSize(new Dimension());
pane.setDividerLocation(0.0d);

// Hide right or bottom
pane.getRightComponent().setMinimumSize(new Dimension());
pane.setDividerLocation(1.0d);

You can play with these settings to store and restore the collapse/expand state.

0
Highlander On

JSplitPane has a method setDividerLocation(double), which sets the divider location as a percentage of the JSplitPane's size. I tried to create similar functionality some time ago. I had the same problem. But even when I use the setDividerLocation(double) method, it didn't work properly. I believe that it's just JSplitPane bug.

0
Martijn Courteaux On

Is hiding your dialog/frame an option?

// Create the dialog/frame which contains the JSplitPane
final JFrame frame = new JFrame("JSplitPane Problem");
frame.setCloseOperation(JFrame.HIDE_ON_CLOSE);
// ...
myButton.addActionListener(new ActionListener()
{
    public void actionPerformed(ActionEvent ae)
    {
        if (!frame.isVisible())
           frame.setVisible(true);
    }

});
0
SpeedProg On

http://docs.oracle.com/javase/7/docs/api/javax/swing/JSplitPane.html

void javax.swing.JSplitPane.setDividerLocation(double proportionalLocation)

Sets the divider location as a percentage of the JSplitPane's size. This method is implemented in terms of setDividerLocation(int). This method immediately changes the size of the split pane based on its current size. If the split pane is not correctly realized and on screen, this method will have no effect (new divider location will become (current size * proportionalLocation) which is 0).

So basically you need to have created your whole UI, called .pack() on the main JFrame AND after that you can use JSplitPane.setDividerLocation(double). If you do it before UI layouting and all that stuff is done, the method will just do nothing as it states in the documentation and you already experienced.

1
kac- On
public static void toggle(JSplitPane sp, boolean collapse) {
        try {
            BasicSplitPaneDivider bspd = ((BasicSplitPaneUI) sp.getUI()).getDivider();
            Field buttonField = BasicSplitPaneDivider.class.
                    getDeclaredField(collapse ? "rightButton" : "leftButton");
            buttonField.setAccessible(true);
            JButton button = (JButton) buttonField.get(((BasicSplitPaneUI) sp.getUI()).getDivider());
            button.getActionListeners()[0].actionPerformed(new ActionEvent(bspd, MouseEvent.MOUSE_CLICKED,
                    "bum"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
0
Marko Kotar On

I improved version of toggle function:

/**
 * toggle JSplitPane
 * @param sp - splitpane to toggle
 * @param upLeft - is it left or top component to collapse? or button or right
 * @param collapse - true component should be collapsed
 */
public void toggle(JSplitPane sp, boolean upLeft, boolean collapse) {
    try {
        //get divider object
        BasicSplitPaneDivider bspd = ((BasicSplitPaneUI) sp.getUI()).getDivider();
        Field buttonField;

        //get field button from divider
        if (upLeft) {
            if (collapse != (sp.getDividerLocation() < sp.getMinimumDividerLocation())) {
                return;
            }
            buttonField = BasicSplitPaneDivider.class.getDeclaredField(collapse ? "rightButton" : "leftButton");
        } else {
            if (collapse != (sp.getDividerLocation() > sp.getMaximumDividerLocation())) {
                return;
            }

            buttonField = BasicSplitPaneDivider.class.getDeclaredField(collapse ? "leftButton" : "rightButton");
        }
        //allow access
        buttonField.setAccessible(true);
        //get instance of button to click at
        JButton button = (JButton) buttonField.get(((BasicSplitPaneUI) sp.getUI()).getDivider());
        //click it
        button.doClick();
        //if you manage more dividers at same time before returning from event,
        //you should update layout and ui, otherwise nothing happens on some dividers:
        sp.updateUI();
        sp.doLayout();


    } catch (Exception e) {
        e.printStackTrace();
    }
}
0
Matthieu On

Forcing the divider position to a very small/large value to hide the top/bottom component works, but is defeated when the split pane gets resized, because of the component minimum size. Setting that size to 0 (as proposed in the accepted answer) works, but there are cases when you cannot/don't want to override that.

After looking into the BasicSplitPaneUI and associated classes, it turns out the "one touch expanding" buttons are calling BasicSPlitPaneUI.setKeepHidden(true), so the divider will stick to either end when resized.

Unfortunately, that method is package-private but setting the associated keepHidden field can be done using introspection, as shown in another answer:

sp.setDividerLocation(<0 or 999999>); // Divider is positioned at the top/bottom
Field m = BasicSplitPaneUI.class.getDeclaredField("keepHidden");
m.setAccessible(true);
m.set(sp.getUI(), true); // Divider position will stick