Search code examples
javaswingjscrollpane

Recommended layout for component that always stays in the same place on the x-axis?


My situation is a bit complex, so rather than explain I'll just show a picture of what I currently have:

Screenshot of the UI

The idea is that I want to move the instrument selector (the combo box that says "Flute" in each staff) above the staff. However, I always want to keep it in the same place, on the left directly above the staff, even when scrolling horizontally. When scrolling vertically, it should move so that it is always directly above its staff. Kind of like a toolbar. The problem is that it's already inside of a JScrollPane (as there could be multiple staves and you need to scroll both axes and there's one "instrument panel" per staff (though eventually there will be other UI elements to interact with in this pseudo-toolbar local to to staff in which it is attached)). Is this something where using absolute positioning + listening for scroll/resize/window move events is needed? Or is there perhaps a layout I'm not aware of that can do this sort of thing?

Thanks for looking!


Solution

    1. Add the components to individual scroll panes, but never show the horizontal scrollbar
    2. Have all the scroll panes share the same BoundRangeModel.
    3. Create a separate JScrollBar component that uses this model. Whenever its scrolls the separate scroll panes will also scroll:

    Something like:

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import javax.swing.text.*;
    
    public class ScrollSSCCE extends JPanel
    {
        public ScrollSSCCE()
        {
            setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
            BoundedRangeModel model = null;
    
            for (int i = 0; i < 5; i++)
            {
                JLabel label = new JLabel("Flute " + i);
                label.setAlignmentX(JComponent.LEFT_ALIGNMENT);
                add( label );
    
                JTextArea textArea = new JTextArea(3, 20);
                textArea.setText("Just some text to make a horizontal scroll necessary");
                JScrollPane scrollPane = new JScrollPane( textArea );
                scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
                scrollPane.setAlignmentX(JComponent.LEFT_ALIGNMENT);
                add( scrollPane );
    
                //  Share the horizontal scrollbar model
    
                JScrollBar horizontal = scrollPane.getHorizontalScrollBar();
    
                if (i == 0)
                    model = horizontal.getModel();
                else
                    horizontal.setModel( model );
            }
    
            //  Create the scrollbar that uses the shared model
    
            JScrollBar shared = new JScrollBar( JScrollBar.HORIZONTAL );
            shared.setModel( model );
            shared.setAlignmentX(JComponent.LEFT_ALIGNMENT);
            add( shared );
        }
    
        private static void createAndShowUI()
        {
            JFrame frame = new JFrame("Scroll SSCCE");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add( new ScrollSSCCE() );
            frame.setLocationByPlatform( true );
            frame.setSize(200, 400);
            frame.setVisible( true );
        }
    
        public static void main(String[] args)
        {
            EventQueue.invokeLater(new Runnable()
            {
                public void run()
                {
                    createAndShowUI();
                }
            });
        }
    }
    

    Edit:

    You can actually do this without even creating the "shared" scrollbar. Just use the scrollbar of the last scrollpane:

    if (i != 4)
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    

    I need a vertical scroll bar as well.

    Don't think you need to create another panel. Just add the current panel directly to a scroll pane:

    JScrollPane master = new JScrollPane( new ScrollSSCCE() );
    master.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    
    JFrame frame = new JFrame("Scroll SSCCE");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    //frame.add( new ScrollSSCCE() );
    frame.add( master );
    frame.setLocationByPlatform( true );
    frame.setSize(200, 400);
    frame.setVisible( true );
    

    That's the last tip I have. I won't be around for a couple of days. Good luck.