使JScrollPane控制多个组件

时间:2014-02-13 22:09:14

标签: java swing jscrollpane jtextarea

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

目前,我已在JScrollPane上实施JTextArea,以允许用户滚动其代码。

对于包含行号的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的构造函数,它在其自身中添加JLabel以表示行号:

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

3 个答案:

答案 0 :(得分:4)

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

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

该示例故意使用换行...

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

    }

}

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

答案 1 :(得分:2)

创建一个外部面板,其中包含“行号”面板和“文本区域”。

然后将这个新面板放入滚动窗格,以便最终得到这样的安排:

enter image description here

代码中的内容如下:

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

答案 2 :(得分:0)

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