使 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 java_Java JScrollPane