javaswingjscrollbar

Scrollbars in BorderLayout.PAGE_START / NORTH


Is there a way such that a component that is added to the BorderLayout.PAGE_START area will become scrollable if (and only if) there's not enough space?

I have attached a minimal example. If you resize the complete frame the label in the center will become scrollable, the label at the top won't.

Unfortunately, I can't change the BorderLayout.PAGE_START positioning, as this is given by a framework. However I do have full control over the creation of myComponent.

  public static void main(String[] args){
    JPanel panel = new JPanel(new BorderLayout());
    JComponent myComponent = new JScrollPane(new JLabel("<html>START-START<br><br>START-START</html>"));

    panel.add(myComponent, BorderLayout.PAGE_START);
    panel.add(new JScrollPane(new JLabel("<html>CENTER-CENTER<br><br>CENTER-CENTER</html>")), BorderLayout.CENTER);

    final JFrame mainframe = new JFrame("Test");
    mainframe.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    mainframe.getContentPane().add(panel);

    javax.swing.SwingUtilities.invokeLater(() -> {
      mainframe.pack();
      mainframe.setVisible(true);});
 }

Solution

  • Is there a way such that a component that is added to the BorderLayout.PAGE_START area will become scrollable if there's not enough space?

    Yes there is. Make its preferred height less than its actual height. BorderLayout respects the preferred height of the component contained at PAGE_START, so it doesn't matter how many lines you put in your JLabel, it will be displayed at its preferred height – without scrollbars.

    Try the following.

    public static void main(String[] args) {
        JPanel panel = new JPanel(new BorderLayout());
        JComponent myComponent = new JScrollPane(new JLabel("<html>START-START<br><br>START-START</html>"));
        myComponent.setPreferredSize(new Dimension(100, 20));
    
        panel.add(myComponent, BorderLayout.PAGE_START);
        panel.add(new JScrollPane(new JLabel("<html>CENTER-CENTER<br><br>CENTER-CENTER</html>")),
                BorderLayout.CENTER);
    
        final JFrame mainframe = new JFrame("Test");
        mainframe.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainframe.getContentPane().add(panel);
    
        javax.swing.SwingUtilities.invokeLater(() -> {
            mainframe.pack();
            mainframe.setVisible(true);
        });
    }
    

    EDIT

    The below code doesn't give the best result but I think it is the way to go. You just need to play around with the different sizes of the different components.

    I added a ComponentListener to the content pane of the JFrame. When the JFrame is resized, you need to recalculate the heights of the top component and the center component according to your needs and then update the relevant component sizes. Note that the below code is not a complete solution but hopefully enough to allow you to arrive at a complete solution.

    public static void main(String[] args) {
        final JFrame mainframe = new JFrame("Test");
        JLabel topLabel = new JLabel("<html>START-START<br><br>START-START");
        JScrollPane topPane = new JScrollPane(topLabel);
        mainframe.add(topPane, BorderLayout.PAGE_START);
        JLabel centerLabel = new JLabel("<html>CENTER-CENTER<br><br>CENTER-CENTER");
        centerLabel.setMinimumSize(centerLabel.getPreferredSize());
        JPanel centerPane = new JPanel();
        centerPane.add(centerLabel);
        mainframe.add(centerPane, BorderLayout.CENTER);
    
        javax.swing.SwingUtilities.invokeLater(() -> {
            mainframe.pack();
            mainframe.setVisible(true);
            final Dimension centerPaneDim = centerPane.getPreferredSize();
            final Dimension topLabelDim = topLabel.getPreferredSize();
            mainframe.getContentPane().addComponentListener(new ComponentListener() {
                
                @Override
                public void componentShown(ComponentEvent e) {
                    // Do nothing.
                }
                
                @Override
                public void componentResized(ComponentEvent e) {
                    Dimension size = e.getComponent().getSize();
                    if (size.height < centerPaneDim.height + topLabelDim.height + 10) {
                        topPane.setPreferredSize(new Dimension(topLabelDim.width, 10 + size.height - centerPaneDim.height));
                    }
                    else {
                        topPane.setPreferredSize(new Dimension(topLabelDim.width, topLabelDim.height + 10));
                    }
                    e.getComponent().revalidate();
                    e.getComponent().repaint();
                }
                
                @Override
                public void componentMoved(ComponentEvent e) {
                    // Do nothing.
                }
                
                @Override
                public void componentHidden(ComponentEvent e) {
                    // Do nothing.
                }
            });
        });
    }