使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
我建议将两个组件放入面板,然后将该面板放入滚动窗格。