调整大小时,JScrollPane 动态 RowHeader 不同步
JScrollPane dynamic RowHeader out of sync when resizing
我想实现一个 TextArea
,它带有一个侧边栏,显示换行文本每行中最左边字符的索引。这是通过 JScrollPane
实现的,使用 RowHeader
显示数字 JTextPane
,Viewport
显示序列 JTextArea
。
除水平调整大小时 RowHeader
和 Viewport
不同步外,一切正常。在下面的 SSCCE 中,它也发生在启动时。
图片说明:JTextArea
的光标在第一个位置,而JTextPane
的下端显示。
我知道它与 JTextPane
动态更改其内容有关,因为它适用于静态内容。
是否可以防止这种奇怪的行为?
package main;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Collections;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Utilities;
public class SequenceAreaExample {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(150, 200);
SequenceArea sequenceArea = new SequenceArea();
frame.add(sequenceArea);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.setVisible(true);
sequenceArea.setText(String.join("", Collections.nCopies(200, "a")));
}
}
class SequenceArea extends JScrollPane implements ComponentListener {
private static final long serialVersionUID = 1L;
private JTextArea text;
private JTextPane numbers;
// FIXME text and numbers are out of sync when resizing horizontally
SequenceArea() {
text = new JTextArea();
text.setLineWrap(true);
text.setEnabled(true);
text.addComponentListener(this);
numbers = new JTextPane();
numbers.setEnabled(false);
setViewportView(text);
setRowHeaderView(numbers);
}
void updateNumbers() {
StringBuilder newNumbers = new StringBuilder();
int length = text.getText().length();
int index = 0;
while (index < length) {
try {
int start = Utilities.getRowStart(text, index);
int end = Utilities.getRowEnd(text, index);
newNumbers.append(start);
newNumbers.append('\n');
index = end + 1;
} catch (BadLocationException e) {
break;
}
}
numbers.setText(newNumbers.toString());
}
void setText(String t) {
text.setText(t);
}
@Override
public void componentResized(ComponentEvent e) {
updateNumbers();
}
@Override
public void componentMoved(ComponentEvent e) {}
@Override
public void componentShown(ComponentEvent e) {}
@Override
public void componentHidden(ComponentEvent e) {}
}
根据我的评论,我尝试构建一个替代解决方案,其中 header 行是视口的一部分,到目前为止它似乎工作正常。 (顺便说一句,在一开始就添加 SSCCE 的荣誉)
JPanel panel = new ScrollablePanel();
panel.setLayout(new GridBagLayout());
//can be reused as constraints are only read when adding components
GridBagConstraints constraints = new GridBagConstraints();
//components should take all vertical space if possible.
constraints.weighty = 1.0;
//components should expand even if it doesn't need more space
constraints.fill = GridBagConstraints.BOTH;
//add the numbers component
panel.add(numbers, constraints );
//add specific constraints for the text component
//text takes as much of the width as it can get
constraints.weightx = 1.0;
//numbers component seems to have some insets so add 2px at the top to get better alignment - could be done differently as well
constraints.insets = new Insets(2, 0, 0, 0);
panel.add(text, constraints);
setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
setViewportView(panel);
为了使其正常工作,您还需要使用一个能够识别滚动并能够根据视口调整其尺寸的面板:
class ScrollablePanel extends JPanel implements Scrollable {
public Dimension getPreferredScrollableViewportSize() {
//the panel prefers to take as much height as possible
return new Dimension(getPreferredSize().width, Integer.MAX_VALUE);
}
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 1;
}
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 1;
}
public boolean getScrollableTracksViewportWidth() {
return true;
}
public boolean getScrollableTracksViewportHeight() {
return true;
}
}
我想实现一个 TextArea
,它带有一个侧边栏,显示换行文本每行中最左边字符的索引。这是通过 JScrollPane
实现的,使用 RowHeader
显示数字 JTextPane
,Viewport
显示序列 JTextArea
。
除水平调整大小时 RowHeader
和 Viewport
不同步外,一切正常。在下面的 SSCCE 中,它也发生在启动时。
图片说明:JTextArea
的光标在第一个位置,而JTextPane
的下端显示。
我知道它与 JTextPane
动态更改其内容有关,因为它适用于静态内容。
是否可以防止这种奇怪的行为?
package main;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Collections;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Utilities;
public class SequenceAreaExample {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(150, 200);
SequenceArea sequenceArea = new SequenceArea();
frame.add(sequenceArea);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.setVisible(true);
sequenceArea.setText(String.join("", Collections.nCopies(200, "a")));
}
}
class SequenceArea extends JScrollPane implements ComponentListener {
private static final long serialVersionUID = 1L;
private JTextArea text;
private JTextPane numbers;
// FIXME text and numbers are out of sync when resizing horizontally
SequenceArea() {
text = new JTextArea();
text.setLineWrap(true);
text.setEnabled(true);
text.addComponentListener(this);
numbers = new JTextPane();
numbers.setEnabled(false);
setViewportView(text);
setRowHeaderView(numbers);
}
void updateNumbers() {
StringBuilder newNumbers = new StringBuilder();
int length = text.getText().length();
int index = 0;
while (index < length) {
try {
int start = Utilities.getRowStart(text, index);
int end = Utilities.getRowEnd(text, index);
newNumbers.append(start);
newNumbers.append('\n');
index = end + 1;
} catch (BadLocationException e) {
break;
}
}
numbers.setText(newNumbers.toString());
}
void setText(String t) {
text.setText(t);
}
@Override
public void componentResized(ComponentEvent e) {
updateNumbers();
}
@Override
public void componentMoved(ComponentEvent e) {}
@Override
public void componentShown(ComponentEvent e) {}
@Override
public void componentHidden(ComponentEvent e) {}
}
根据我的评论,我尝试构建一个替代解决方案,其中 header 行是视口的一部分,到目前为止它似乎工作正常。 (顺便说一句,在一开始就添加 SSCCE 的荣誉)
JPanel panel = new ScrollablePanel();
panel.setLayout(new GridBagLayout());
//can be reused as constraints are only read when adding components
GridBagConstraints constraints = new GridBagConstraints();
//components should take all vertical space if possible.
constraints.weighty = 1.0;
//components should expand even if it doesn't need more space
constraints.fill = GridBagConstraints.BOTH;
//add the numbers component
panel.add(numbers, constraints );
//add specific constraints for the text component
//text takes as much of the width as it can get
constraints.weightx = 1.0;
//numbers component seems to have some insets so add 2px at the top to get better alignment - could be done differently as well
constraints.insets = new Insets(2, 0, 0, 0);
panel.add(text, constraints);
setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
setViewportView(panel);
为了使其正常工作,您还需要使用一个能够识别滚动并能够根据视口调整其尺寸的面板:
class ScrollablePanel extends JPanel implements Scrollable {
public Dimension getPreferredScrollableViewportSize() {
//the panel prefers to take as much height as possible
return new Dimension(getPreferredSize().width, Integer.MAX_VALUE);
}
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 1;
}
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 1;
}
public boolean getScrollableTracksViewportWidth() {
return true;
}
public boolean getScrollableTracksViewportHeight() {
return true;
}
}