使 JScrollPane 控制多个组件

Posted

技术标签:

【中文标题】使 JScrollPane 控制多个组件【英文标题】:Make JScrollPane control multiple components 【发布时间】:2014-03-13 01:26:47 【问题描述】:

对于我的应用程序,我正在设计一个脚本编辑器。目前我有一个JPanel,其中包含另一个JPanel,其中包含行号(位于左侧)和一个JTextArea,用于允许用户输入他们的代码(位于右侧)。

目前,我在JTextArea 上实现了JScrollPane,以允许用户滚动浏览他们的代码。

对于包含行号的JPanel,它们会在用户每次按下回车键时递增。

但是,问题是我想要同样的 JScrollPane(在 JTextArea 上实现的那个)来控制行号 JPanel 的滚动;即当用户在 JTextArea 上滚动时,行号 JPanel 也应该滚动。但由于行号保存在 JPanel 中,我无法将该组件添加到 JTextArea。

包含 JTextArea 和行号 JPanel 的 JPanel 类的构造函数:

private ScriptEditor() 

    setBackground(Color.WHITE);

    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));

    JScrollPane scrollPane = new JScrollPane(scriptArea);
    scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    scrollPane.setPreferredSize(new Dimension(width, height));

    scriptArea.addKeyListener(this);

    add(lineNumPanel);
    add(scrollPane);

行号 JPanel 的构造函数,它在自身内部添加 JLabels 以表示行号:

public LineNumberPanel() 

    setPreferredSize(new Dimension(width, height));

    box = Box.createVerticalBox();
    add(box);

    //setup the label
    label = new JLabel(String.valueOf(lineCount));
    label.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 15));

    //setup the label alignment
    label.setVerticalAlignment(JLabel.TOP);
    label.setHorizontalAlignment(JLabel.CENTER);
    label.setVerticalTextPosition(JLabel.TOP);
    setAlignmentY(TOP_ALIGNMENT);

    box.add(label);

【问题讨论】:

查看使用 Rob Camick 博客中的 text component line numbers。这是一个组件,可用作包含您的文本区域的滚动窗格的行标题。 【参考方案1】:

您应该使用JScrollPane#setRowHeaderView 来设置将出现在滚动窗格左侧的组件。

这样做的好处是当视图向右滚动时,行标题不会向左滚动...

示例故意使用换行...

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;
                
            
        

    


正如我刚刚发现的,@camickr 有一个更有用的例子,Text Component Line Number

【讨论】:

【参考方案2】:

创建一个包含行号面板和文本区域的外部面板。

然后将这个新面板放入 Scroll Pane 中,这样你就得到了这样的安排:

代码中是这样的:

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);

【讨论】:

【参考方案3】:

我建议将这两个组件放入一个面板中,然后将该面板放入滚动面板中。

【讨论】:

以上是关于使 JScrollPane 控制多个组件的主要内容,如果未能解决你的问题,请参考以下文章

在 JScrollPane 中查找可见组件的快速方法

如何在 JScrollPane 中获取(可变)组件的完整大小?

jscrollpane java_Java JScrollPane

JScrollPane

大于 JFrame 的可拖动组件(JScrollPane,每侧都有不可见的滚动条)

JScrollPane (滚动面板)使用心得