3

I am having trouble getting a JSplitPane to maintain the same relative position when its container is resized. If the split pane is assumed to have a fixed position everything works fine. I can get the divider location to stay close to the same relative position using the code shown below but the divider shifts to the left when the JFrame is resized. The shift is larger if the JFrame is resized slowly. The shift is to the left if the JFrame is made smaller or larger.

How do I get the divider to stay in the exact same proportional location?

This is what the GUI initially looks like.

enter image description here

This is what it looks like after several resizings.
enter image description here

And this is what the code looks like. This attempt is based on the information provided here:

JSplitPane splitting 50% precisely

JSplitPane SetDividerLocation Problem

https://docs.oracle.com/javase/tutorial/uiswing/components/splitpane.html

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;

public class Example {

    public static void main(String[] args) {
        new Example().showGui();
    }

    private void showGui() {
        // create the jframe
        JFrame jFrame = new JFrame();
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jFrame.setSize(400, 200);
        // create the left and right panels
        JPanel left = new JPanel();
        left.setBackground(Color.yellow);
        JPanel right = new JPanel();
        right.setBackground(Color.orange);
        ResizableSplitPane split = new ResizableSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right, jFrame);
        jFrame.getContentPane().add(split);
        // show the gui
        jFrame.setVisible(true);
    }

    public class ResizableSplitPane extends JSplitPane {

        //
        // instance variables
        //

        private boolean painted;

        private double defaultDividerLocation;

        private ResizableSplitPane resizableSplitPane = this;

        private double currentDividerLocation;

        private Component first;

        private Component second;

        private boolean dividerPositionCaptured = false;

        //
        // constructors
        //

        public ResizableSplitPane(int splitType, Component first, Component second, Component parent) {
            this(splitType, first, second, parent, 0.5);
        }

        public ResizableSplitPane(int splitType, Component first, Component second, Component parent, double defaultDividerLocation) {
            super(splitType, first, second);
            this.defaultDividerLocation = defaultDividerLocation;
            this.currentDividerLocation = defaultDividerLocation;
            this.setResizeWeight(defaultDividerLocation);
            this.first = first;
            this.second = second;
            parent.addComponentListener(new DividerLocator());
            first.addComponentListener(new DividerMovedByUserComponentAdapter());
        }

        //
        // trivial getters and setters
        //

        public double getDefaultDividerLocation() {
            return defaultDividerLocation;
        }

        public void setDefaultDividerLocation(double defaultDividerLocation) {
            this.defaultDividerLocation = defaultDividerLocation;
        }

        //
        // implementation
        //

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            if (!painted) {
                painted = true;
                this.setDividerLocation(currentDividerLocation);
            }
            dividerPositionCaptured = false;
        }

        private class DividerLocator extends ComponentAdapter {
            @Override
            public void componentResized(ComponentEvent e) {
                setDividerLocation(currentDividerLocation);
            }
        }

        private class DividerMovedByUserComponentAdapter extends ComponentAdapter {
            @Override
            public void componentResized(ComponentEvent e) {
                if (dividerPositionCaptured == false) {
                    dividerPositionCaptured = true;
                    currentDividerLocation = (double) first.getWidth() / (double) (first.getWidth() + second.getWidth());
                    System.out.println(currentDividerLocation);
                }
            }
        }

    }

}
Community
  • 1
  • 1
John
  • 2,320
  • 2
  • 17
  • 33
  • 1
    I'm assuming when you say _"in the exact same proportional location"_ you mean that if the splitter is in the middle, it stays in the middle during a resize? – Sean Bright Mar 17 '16 at 18:36

2 Answers2

2
  • Here's my attempt:
    • Override the doLayout method of the parent component of JSplitPane, after the resizing of JSplitPane has been completed, to adjust the position of the divider.
import java.awt.*;
import java.awt.event.*;
import java.math.BigDecimal;
import javax.swing.*;

public class Example2 {
  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      f.getContentPane().add(new Example2().makeUI());
      f.setSize(400, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
  private JComponent makeUI() {
    JPanel p = new JPanel(new GridLayout(0, 1, 0, 0));

    JPanel left1 = new JPanel();
    left1.setBackground(Color.YELLOW);
    JPanel right1 = new JPanel();
    right1.setBackground(Color.ORANGE);
    JSplitPane split1 = new ResizableSplitPane(
        JSplitPane.HORIZONTAL_SPLIT, left1, right1, p);
    p.add(split1);

    JPanel left2 = new JPanel();
    left2.setBackground(Color.YELLOW);
    JPanel right2 = new JPanel();
    right2.setBackground(Color.ORANGE);
    JSplitPane split2 = new JSplitPane(
        JSplitPane.HORIZONTAL_SPLIT, left2, right2);
    p.add(new SplitPaneWrapper(split2));

    return p;
  }
}

class ResizableSplitPane extends JSplitPane {
  //
  // instance variables
  //

  private boolean painted;

  private double defaultDividerLocation;

  private ResizableSplitPane resizableSplitPane = this;

  private double currentDividerLocation;

  private Component first;

  private Component second;

  private boolean dividerPositionCaptured = false;

  //
  // constructors
  //

  public ResizableSplitPane(int splitType, Component first, Component second, Component parent) {
    this(splitType, first, second, parent, 0.5);
  }

  public ResizableSplitPane(int splitType, Component first, Component second, Component parent, double defaultDividerLocation) {
    super(splitType, first, second);
    this.defaultDividerLocation = defaultDividerLocation;
    this.currentDividerLocation = defaultDividerLocation;
    this.setResizeWeight(defaultDividerLocation);
    this.first = first;
    this.second = second;
    parent.addComponentListener(new DividerLocator());
    first.addComponentListener(new DividerMovedByUserComponentAdapter());
  }

  //
  // trivial getters and setters
  //

  public double getDefaultDividerLocation() {
    return defaultDividerLocation;
  }

  public void setDefaultDividerLocation(double defaultDividerLocation) {
    this.defaultDividerLocation = defaultDividerLocation;
  }

  //
  // implementation
  //

  @Override
  public void paint(Graphics g) {
    super.paint(g);
    if (!painted) {
      painted = true;
      this.setDividerLocation(currentDividerLocation);
    }
    dividerPositionCaptured = false;
  }

  private class DividerLocator extends ComponentAdapter {
    @Override
    public void componentResized(ComponentEvent e) {
      setDividerLocation(currentDividerLocation);
    }
  }

  private class DividerMovedByUserComponentAdapter extends ComponentAdapter {
    @Override
    public void componentResized(ComponentEvent e) {
      if (dividerPositionCaptured == false) {
        dividerPositionCaptured = true;
        currentDividerLocation = (double) first.getWidth() / (double)(first.getWidth() + second.getWidth());
        System.out.println(currentDividerLocation);
      }
    }
  }

}

class SplitPaneWrapper extends JPanel {
  private final JSplitPane splitPane;
  protected SplitPaneWrapper(JSplitPane splitPane) {
    super(new BorderLayout());
    this.splitPane = splitPane;
    splitPane.setResizeWeight(.5);
    add(splitPane);
  }
  private static int getOrientedSize(JSplitPane sp) {
    return sp.getOrientation() == JSplitPane.VERTICAL_SPLIT
           ? sp.getHeight() - sp.getDividerSize()
           : sp.getWidth()  - sp.getDividerSize();
  }
  @Override public void doLayout() {
    int size = getOrientedSize(splitPane);
    double d = splitPane.getDividerLocation() / (double) size;
    BigDecimal bd = new BigDecimal(d).setScale(2, BigDecimal.ROUND_HALF_UP);
    super.doLayout();
    if (splitPane.isShowing()) {
      EventQueue.invokeLater(() -> {
        int s = getOrientedSize(splitPane);
        int iv = (int)(.5 + s * bd.doubleValue());
        splitPane.setDividerLocation(iv);
      });
    }
  }
}
aterai
  • 9,288
  • 4
  • 30
  • 41
  • My project is limited to Java 1.7 so the lambda expressions didn't compile for me. – John Mar 18 '16 at 12:00
  • 1
    @John Replace them with anonymous inner classes. Learn more [here](https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html) and [here](https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html). – sponge Mar 18 '16 at 14:07
1

This is what I ended up using. It seems to do exactly what I set out to do: create a split pane that proportionally resizes including proportionally resizing after the divider is moved.

This is what the example gui looks like before and after resize:

enter image description here

enter image description here

And here's what the code looks like. This is a complete example that includes proportional resizing of the split pane for horizontal and vertical split panes and the case where the user has moved the divider.

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;

public class Example {

    public static void main(String[] args) {
        new Example().showGui();
    }

    private void showGui() {
        // create the jframe
        JFrame jFrame = new JFrame();
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jFrame.setSize(400, 200);
        // create the left panel
        JPanel left = new JPanel();
        left.setBackground(Color.yellow);
        // create the right panel
        JPanel right = new JPanel();
        right.setBackground(Color.orange);
        // create the bottom panel
        JPanel bottom = new JPanel();
        bottom.setBackground(Color.green);
        // create the split panes
        ResizableSplitPane horizontalSplit = new ResizableSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right, jFrame);
        ResizableSplitPane verticalSplit = new ResizableSplitPane(JSplitPane.VERTICAL_SPLIT, horizontalSplit, bottom, jFrame);
        jFrame.getContentPane().add(verticalSplit);
        // show the gui
        jFrame.setVisible(true);
    }

    public class ResizableSplitPane extends JSplitPane {

        //
        // instance variables
        //

        private boolean painted;

        private double defaultDividerLocation;

        private double dividerProportionalLocation;

        private int currentDividerLocation;

        private Component first;

        private Component second;

        private boolean dividerPositionCaptured = false;

        //
        // constructors
        //

        public ResizableSplitPane(int splitType, Component first, Component second, Component parent) {
            this(splitType, first, second, parent, 0.5);
        }

        public ResizableSplitPane(int splitType, Component first, Component second, Component parent, double defaultDividerLocation) {
            super(splitType, first, second);
            this.defaultDividerLocation = defaultDividerLocation;
            this.dividerProportionalLocation = defaultDividerLocation;
            this.setResizeWeight(defaultDividerLocation);
            this.first = first;
            this.second = second;
            parent.addComponentListener(new DividerLocator());
            second.addComponentListener(new DividerMovedByUserComponentAdapter());
        }

        //
        // trivial getters and setters
        //

        public double getDefaultDividerLocation() {
            return defaultDividerLocation;
        }

        public void setDefaultDividerLocation(double defaultDividerLocation) {
            this.defaultDividerLocation = defaultDividerLocation;
        }

        //
        // implementation
        //

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            if (painted == false) {
                painted = true;
                this.setDividerLocation(dividerProportionalLocation);
                this.currentDividerLocation = this.getDividerLocation();
            }
        }

        private class DividerLocator extends ComponentAdapter {
            @Override
            public void componentResized(ComponentEvent e) {
                setDividerLocation(dividerProportionalLocation);
                currentDividerLocation = getDividerLocation();
            }
        }

        private class DividerMovedByUserComponentAdapter extends ComponentAdapter {
            @Override
            public void componentResized(ComponentEvent e) {
                System.out.println("RESIZED: " + dividerPositionCaptured);
                int newDividerLocation = getDividerLocation();
                boolean dividerWasMovedByUser = newDividerLocation != currentDividerLocation;
                System.out.println(currentDividerLocation + "\t" + newDividerLocation + "\t" + dividerProportionalLocation);
                if (dividerPositionCaptured == false || dividerWasMovedByUser == true) {
                    dividerPositionCaptured = true;
                    painted = false;
                    if(getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
                        dividerProportionalLocation = (double) first.getWidth() / (double) (first.getWidth() + second.getWidth());
                    } else {
                        dividerProportionalLocation = (double) first.getHeight() / (double) (first.getHeight() + second.getHeight());

                    }
                    System.out.println(dividerProportionalLocation);
                }
            }
        }

    }

}
John
  • 2,320
  • 2
  • 17
  • 33