Make JScrollPane control multiple components

后端 未结 3 899
被撕碎了的回忆
被撕碎了的回忆 2020-11-30 16:05

For my application I am designing a script editor. At the moment I have a JPanel which contains another JPanel that holds the line number (position

相关标签:
3条回答
  • 2020-11-30 16:19

    You should use JScrollPane#setRowHeaderView to set the component that will appear at the left hand side of the scroll pane.

    The benefit of this is the row header won't scroll to the left as the view scrolls to the right...

    The example deliberately uses line wrapping...

    Line Numbering

    import java.awt.BorderLayout;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.FontMetrics;
    import java.awt.Graphics;
    import java.awt.Insets;
    import java.awt.Point;
    import java.awt.Rectangle;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JTextArea;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.event.DocumentEvent;
    import javax.swing.event.DocumentListener;
    import javax.swing.text.Element;
    import javax.swing.text.Utilities;
    
    public class ScrollColumnHeader {
    
        public static void main(String[] args) {
            new ScrollColumnHeader();
        }
    
        public ScrollColumnHeader() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    }
    
                    JTextArea ta = new JTextArea(20, 40);
                    ta.setWrapStyleWord(true);
                    ta.setLineWrap(true);
                    JScrollPane sp = new JScrollPane(ta);
                    sp.setRowHeaderView(new LineNumberPane(ta));
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setLayout(new BorderLayout());
                    frame.add(sp);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class LineNumberPane extends JPanel {
    
            private JTextArea ta;
    
            public LineNumberPane(JTextArea ta) {
                this.ta = ta;
                ta.getDocument().addDocumentListener(new DocumentListener() {
    
                    @Override
                    public void insertUpdate(DocumentEvent e) {
                        revalidate();
                        repaint();
                    }
    
                    @Override
                    public void removeUpdate(DocumentEvent e) {
                        revalidate();
                        repaint();
                    }
    
                    @Override
                    public void changedUpdate(DocumentEvent e) {
                        revalidate();
                        repaint();
                    }
                });
            }
    
            @Override
            public Dimension getPreferredSize() {
                FontMetrics fm = getFontMetrics(getFont());
                int lineCount = ta.getLineCount();
                Insets insets = getInsets();
                int min = fm.stringWidth("000");
                int width = Math.max(min, fm.stringWidth(Integer.toString(lineCount))) + insets.left + insets.right;
                int height = fm.getHeight() * lineCount;
                return new Dimension(width, height);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
    
                FontMetrics fm = ta.getFontMetrics(ta.getFont());
                Insets insets = getInsets();
    
                Rectangle clip = g.getClipBounds();
                int rowStartOffset = ta.viewToModel(new Point(0, clip.y));
                int endOffset = ta.viewToModel(new Point(0, clip.y + clip.height));
    
                Element root = ta.getDocument().getDefaultRootElement();
                while (rowStartOffset <= endOffset) {
                    try {
                        int index = root.getElementIndex(rowStartOffset);
                        Element line = root.getElement(index);
    
                        String lineNumber = "";
                        if (line.getStartOffset() == rowStartOffset) {
                            lineNumber = String.valueOf(index + 1);
                        }
    
                        int stringWidth = fm.stringWidth(lineNumber);
                        int x = insets.left;
                        Rectangle r = ta.modelToView(rowStartOffset);
                        int y = r.y + r.height;
                        g.drawString(lineNumber, x, y - fm.getDescent());
    
                        //  Move to the next row
                        rowStartOffset = Utilities.getRowEnd(ta, rowStartOffset) + 1;
                    } catch (Exception e) {
                        break;
                    }
                }
            }
    
        }
    
    }
    

    And as I just discovered, @camickr has a much more useful example, Text Component Line Number

    0 讨论(0)
  • 2020-11-30 16:23

    I suggest placing both components into a panel, then place that panel into the scroll pane.

    0 讨论(0)
  • 2020-11-30 16:42

    Create an Outer Panel which holds the Line Number panel and Text Area.

    Then put this new panel into the Scroll Pane so you end up with this arrangement:

    enter image description here

    Which in code is something like:

    private ScriptEditor() {
    
        setBackground(Color.WHITE);
    
        JPanel outerPanel = new JPanel();
    
        lineNumPanel = new LineNumberPanel();
    
        scriptArea = new JTextArea();
        scriptArea.setLineWrap(true);
        scriptArea.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 15));
        scriptArea.setMargin(new Insets(3, 10, 0, 10));
    
        outerPanel.add(lineNumPanel, BorderLayout.WEST)
        outerPanel.add(scriptArea, BorderLayout.CENTER)
    
        JScrollPane scrollPane = new JScrollPane(outerPanel);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setPreferredSize(new Dimension(width, height));
    
        scriptArea.addKeyListener(this);
    
        add(lineNumPanel);
        add(scrollPane);
    }
    
    0 讨论(0)
提交回复
热议问题