Search code examples
javaswingjsplitpane

JSplitPane divider drag not working with complex layout


I have a Java GUI program with a JSplitPane dividing the content. The left side of the JSplitPane is a JTabbedPane with a number of tabs - a very complex layout. When the left side reaches a certain level of complexity, dragging the divider to move the divider location no longer works, but I can still move the divider by explicitly setting the location. (If it matters, I am using Nimbus LAF.)

It doesn't seem to be just the number of tabs on the left. Certain tabs that I include on the left make it stop working and others are OK.

Has anyone ever run into this?

I was able to work around it by adding a hack workaround method on my JSplitPane subclass.

    public void enableDividerWorkaround() {
      javax.swing.plaf.basic.BasicSplitPaneUI l_ui = (javax.swing.plaf.basic.BasicSplitPaneUI) getUI();
      BasicSplitPaneDivider l_divider = l_ui.getDivider();

      l_divider.addMouseMotionListener(new MouseMotionAdapter() {
        @Override
        public void mouseDragged(MouseEvent e) {
          Dimension l_pane_size = getSize();
          if (getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
            int l_new_loc = getDividerLocation() + e.getX();
            if (l_new_loc >= 0 && l_new_loc <= l_pane_size.width) {
              setDividerLocation(l_new_loc);
            }
          } else {
            int l_new_loc = getDividerLocation() + e.getY();
            if (l_new_loc >= 0 && l_new_loc <= l_pane_size.height) {
              setDividerLocation(l_new_loc);
            }
          }
        }
      });
    }

UPDATE Here is the SSCCE (below). When I run this, the first time I drag the slider to the right, it "snaps" to the end of the long label and then remains fixed there. I believe it is triggered by the long label. If I shorten the label, I get more range of motion on the slider. So is it a bug, or the intended behavior?

    public class SplitPaneTest extends javax.swing.JFrame {
      public SplitPaneTest() {
        initComponents();
      }
      private void initComponents() {

        jSplitPane1 = new javax.swing.JSplitPane();
        jLabel1 = new javax.swing.JLabel();
        jPanel1 = new javax.swing.JPanel();
        jLabel2 = new javax.swing.JLabel();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jSplitPane1.setDividerLocation(450);
        jSplitPane1.setName("jSplitPane1"); 

        jLabel1.setText("right side");
        jLabel1.setName("jLabel1"); 
        jSplitPane1.setRightComponent(jLabel1);

        jPanel1.setName("jPanel1"); 

        jLabel2.setText("left side asd adsf asdf asdf asdf sadf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdfa end");
        jLabel2.setName("jLabel2"); 

        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
          jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(jPanel1Layout.createSequentialGroup()
            .addContainerGap()
            .addComponent(jLabel2)
            .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        jPanel1Layout.setVerticalGroup(
          jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(jPanel1Layout.createSequentialGroup()
            .addContainerGap()
            .addComponent(jLabel2)
            .addContainerGap(420, Short.MAX_VALUE))
        );

        jSplitPane1.setLeftComponent(jPanel1);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
          layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addComponent(jSplitPane1)
        );
        layout.setVerticalGroup(
          layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addComponent(jSplitPane1)
        );

        pack();
      }

      /**
       * @param args the command line arguments
       */
      public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
          public void run() {
            new SplitPaneTest().setVisible(true);
          }
        });
      }
      protected javax.swing.JLabel jLabel1;
      protected javax.swing.JLabel jLabel2;
      protected javax.swing.JPanel jPanel1;
      protected javax.swing.JSplitPane jSplitPane1;
    }

Solution

  • This seems to be intentional behavior. Found the following code in the constructor of javax.swing.plaf.basic.BasicSplitPaneDivider.DragController:

    minX = leftC.getMinimumSize().width;
    

    then later on in that same class

        /**
        * Returns the new position to put the divider at based on
        * the passed in MouseEvent.
        */
        protected int positionForMouseEvent(MouseEvent e) {
          int newX = (e.getSource() == BasicSplitPaneDivider.this) ?
                      (e.getX() + getLocation().x) : e.getX();
    
          newX = Math.min(maxX, Math.max(minX, newX - offset));
          return newX;
        }
    

    So it appears that the basic UI intentionally doesn't let you drag the divider in such a way as to make the left side smaller than its minimum size.

    In my case, that behavior doesn't fit my needs, so my little workaround is necessary.