使JScrollPane控制多个组件

对于我的应用程序,我正在设计脚本编辑器。 目前我有一个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的构造函数,它在其自身中添加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); } 

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

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

在此处输入图像描述

在代码中是这样的:

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

您应该使用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 = ry + 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

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