javaswingjscrollpane

adding a non-scrolling component to a JScrollPane


I'd like to add a non-scrolling element, always visible element (a "pinned" element) in a JScrollPane of a Swing GUI. Taking this example code:

public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        JEditorPane editorPane = new JEditorPane();
        editorPane.setText("""
                d
                d
                d
                d
                d
                d
                d
                d
                d
                d
                d
                d
                d
                d
                d
                d
                d
                d
                d
                d
                d
                """);
        JScrollPane scroll = new JScrollPane(editorPane);
        editorPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
        editorPane.add(new JLabel("this shouldn't scroll!"));

        frame.getContentPane().add(scroll);
        frame.setSize(600, 400);
        frame.setVisible(true);
    }
}

Which produces this GUI:
the swing panel created by the code

I need a way to:

  1. Make sure that the "this shouldn't scroll!" JLabel doesn't move when the scroll pane scrolls.
  2. Pin the "this shouldn't scroll!" JLabel to the right side of the panel (currently, it becomes hidden with a resize of the panel. I'd like it to move left when the right side of the panel is pushed smaller, to make sure it's always on screen).

I've been able to make an awful implementation of this using swing's glass pane, but it means a lot more manual updating for me, and position always being the same, no matter how the swing panel is resized.

Thank you for any help!


Solution

  • As a personal preference, I'd probably use a compound layout, simular to this example as it gives you more control over the the component been rendered (ie much easier to deal with live components like buttons) and it's position.

    You "could" make use of the JLayer API, for example...

    enter image description here

    import java.awt.BorderLayout;
    import java.awt.EventQueue;
    import java.awt.FontMetrics;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JLayer;
    import javax.swing.JPanel;
    import javax.swing.JScrollBar;
    import javax.swing.JScrollPane;
    import javax.swing.JTextArea;
    import javax.swing.plaf.LayerUI;
    
    public class Main {
        public static void main(String[] args) {
            new Main();
        }
    
        public Main() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        JFrame frame = new JFrame();
                        frame.add(new TestPane());
                        frame.pack();
                        frame.setLocationRelativeTo(null);
                        frame.setVisible(true);
                    } catch (IOException ex) {
                        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            });
        }
    
        public class TestPane extends JPanel {
    
            public TestPane() throws IOException {
                FixedTextLayerUI layerUI = new FixedTextLayerUI();
                layerUI.setText("this shouldn't scroll!");
    
                JTextArea ta = new JTextArea(20, 40);
                StringBuilder sb = new StringBuilder(1024);
                // Bring your own content ;)
                try (BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/resources/StarWarsNewHope.txt")))) {
                    String text = null;
                    while ((text = br.readLine()) != null) {
                        sb.append(text);
                        sb.append(System.lineSeparator());
                    }
                    ta.setText(sb.toString());
                }
                JLayer<JComponent> layer = new JLayer<JComponent>(new JScrollPane(ta), layerUI);
                setLayout(new BorderLayout());
                add(layer);
            }
    
        }
    
        public class FixedTextLayerUI extends LayerUI<JComponent> {
    
            private String text;
    
            public void setText(String text) {
                this.text = text;
            }
    
            public String getText() {
                return text;
            }
    
            @Override
            public void paint(Graphics g, JComponent c) {
                super.paint(g, c);
    
                Graphics2D g2 = (Graphics2D) g.create();
    
                FontMetrics fm = g2.getFontMetrics();
                int stringWidth = fm.stringWidth(getText());
                int x = c.getWidth() - stringWidth - 8;
                int y = fm.getHeight() + fm.getAscent() + 8;
    
                if (c instanceof JLayer) {
                    JLayer layer = (JLayer) c;
                    if (layer.getView() instanceof JScrollPane) {
                        JScrollPane scrollPane = (JScrollPane) layer.getView();
                        JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
                        if (scrollBar.isVisible()) {
                            x -= scrollBar.getWidth();
                        }
                    }
                }
    
                g2.drawString(text, x, y);
    
                g2.dispose();
            }
    
        }
    }
    

    See How to Decorate Components with the JLayer Class for more details.