如何:JTextPane 自动换行不是在单词边界上,例如 "space",而是在字母边界上
How to: JTextPane auto wrapping NOT on word boundary such as "space", but on letter boundary
我把一个JTextPane
放进了一个JScrollPane
。正如我所尝试的,如果 JTextPane
超过显示区域的宽度,它会自动换行。并且自动换行基于word boundary
,例如space
字符。
我的内容包含很多space
。我想显示它 literally
。所以我需要自动换行,但我希望它发生在 ONLY
显示区域的最大宽度,NOT
在 word boundary
上。
如何?
我尝试过的:
- 把所有的space换成'\0',所以字面意思,我的内容是一个
single big word
.
加 1
以下是我在阅读 StanislavL's solution 后 失败的 尝试。
我不是在责怪他的解决方案,因为我的情况与他的情况并不完全相同。
StanislavL
的解要求一行至少包含2个LabelView
。据他说,这是由 Swing
实施 layout()
方法 where forced break works only if row view has more than one child
强加的(参见:http://java-sl.com/wrap.html)。所以 StanislavL
故意 为 \r
字符分配了一个特殊属性,以确保单独的 LabelView
。并将\r
作为地标的环绕。但是在我的场景中,我无法在我的内容中插入任何字符。
我的想法很简单,只需为 StyledEditorKit
提供 ViewFactory
的自定义实现,因为 ViewFactory
接口决定了中断权重以及中断应该如何发生:
this.jTextPane.setEditorKit(new StyledEditorKit(){
@Override
public ViewFactory getViewFactory(){
return new LetterWrappingStyledViewFactory(maxCharWidth);
}
});
下面是我实现的接口ViewFactory
:
public class LetterWrappingStyledViewFactory implements ViewFactory {
public int maxCharWidth = -1; // this is the max width where I want wrap to happen.
public LetterWrappingStyledViewFactory(int maxCharWidth) {
this.maxCharWidth = maxCharWidth;
}
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new LabelView(elem) {
public int getBreakWeight(int axis, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
int p0 = getStartOffset();
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
if (p1 > maxCharWidth)
return View.ForcedBreakWeight;
else
return View.BadBreakWeight;
}
return super.getBreakWeight(axis, pos, len);
}
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
if (p0 == getStartOffset() && p1 <= maxCharWidth) {
return this;
}
return createFragment(p0, maxCharWidth);
}
return this;
}
};
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new ParagraphView(elem);
} else if (kind.equals(AbstractDocument.SectionElementName)) {
return new BoxView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
此示例适用于 StyledEditorKit(使用 java 7 测试)
import javax.swing.*;
import javax.swing.text.*;
public class WrapApp extends JFrame {
JEditorPane edit=new JEditorPane();
public WrapApp() {
super("Wrap in the mid");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
edit.setEditorKit(new WrapEditorKit());
edit.setText("111 222 333333333333333333333333333333333333333333333");
getContentPane().add(new JScrollPane(edit));
setSize(200,200);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
WrapApp m = new WrapApp();
m.setVisible(true);
}
}
class WrapEditorKit extends StyledEditorKit {
ViewFactory defaultFactory=new WrapColumnFactory();
public ViewFactory getViewFactory() {
return defaultFactory;
}
}
class WrapColumnFactory implements ViewFactory {
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new WrapLabelView(elem);
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new ParagraphView(elem);
} else if (kind.equals(AbstractDocument.SectionElementName)) {
return new BoxView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
class WrapLabelView extends LabelView {
public WrapLabelView(Element elem) {
super(elem);
}
public int getBreakWeight(int axis, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
int p0 = getStartOffset();
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
if (p1 == p0) {
// can't even fit a single character
return View.BadBreakWeight;
}
return View.GoodBreakWeight;
}
return super.getBreakWeight(axis, pos, len);
}
public float getMinimumSpan(int axis) {
switch (axis) {
case View.X_AXIS:
return 0;
case View.Y_AXIS:
return super.getMinimumSpan(axis);
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
GlyphView v = (GlyphView) createFragment(p0, p1);
return v;
}
return super.breakView(axis, p0, pos, len);
}
}
更新:
关于 GlyphPainter。事实上,我们应该找到一个可以打破 GlyphView 的位置(this
过去在 getBoundedPosition() 中)。 p0表示文档中的char偏移量(GlyphView表示Character元素或Element的片段))
想象一下一大串文本。在文档中它只是一个元素,因为它具有所有相同的属性。但是我们必须创建多个 LabelView,因为文本片段太大而无法容纳可用宽度。因此,对于第一个标签,p0 为 0。然后对于下一个标签,是我们打破初始巨大视图的偏移量。如果我们再次打破它,那么另一个抵消。然后2个参数只代表容器的x位移和宽度(例如父视图)。
所以我们有一个大文本,应该找到中断的地方。当前的方式更简单,因为我们不关心优先级(空格与其他字符)。
我把一个JTextPane
放进了一个JScrollPane
。正如我所尝试的,如果 JTextPane
超过显示区域的宽度,它会自动换行。并且自动换行基于word boundary
,例如space
字符。
我的内容包含很多space
。我想显示它 literally
。所以我需要自动换行,但我希望它发生在 ONLY
显示区域的最大宽度,NOT
在 word boundary
上。
如何?
我尝试过的:
- 把所有的space换成'\0',所以字面意思,我的内容是一个
single big word
.
加 1
以下是我在阅读 StanislavL's solution 后 失败的 尝试。 我不是在责怪他的解决方案,因为我的情况与他的情况并不完全相同。
StanislavL
的解要求一行至少包含2个LabelView
。据他说,这是由 Swing
实施 layout()
方法 where forced break works only if row view has more than one child
强加的(参见:http://java-sl.com/wrap.html)。所以 StanislavL
故意 为 \r
字符分配了一个特殊属性,以确保单独的 LabelView
。并将\r
作为地标的环绕。但是在我的场景中,我无法在我的内容中插入任何字符。
我的想法很简单,只需为 StyledEditorKit
提供 ViewFactory
的自定义实现,因为 ViewFactory
接口决定了中断权重以及中断应该如何发生:
this.jTextPane.setEditorKit(new StyledEditorKit(){
@Override
public ViewFactory getViewFactory(){
return new LetterWrappingStyledViewFactory(maxCharWidth);
}
});
下面是我实现的接口ViewFactory
:
public class LetterWrappingStyledViewFactory implements ViewFactory {
public int maxCharWidth = -1; // this is the max width where I want wrap to happen.
public LetterWrappingStyledViewFactory(int maxCharWidth) {
this.maxCharWidth = maxCharWidth;
}
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new LabelView(elem) {
public int getBreakWeight(int axis, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
int p0 = getStartOffset();
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
if (p1 > maxCharWidth)
return View.ForcedBreakWeight;
else
return View.BadBreakWeight;
}
return super.getBreakWeight(axis, pos, len);
}
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
if (p0 == getStartOffset() && p1 <= maxCharWidth) {
return this;
}
return createFragment(p0, maxCharWidth);
}
return this;
}
};
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new ParagraphView(elem);
} else if (kind.equals(AbstractDocument.SectionElementName)) {
return new BoxView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
此示例适用于 StyledEditorKit(使用 java 7 测试)
import javax.swing.*;
import javax.swing.text.*;
public class WrapApp extends JFrame {
JEditorPane edit=new JEditorPane();
public WrapApp() {
super("Wrap in the mid");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
edit.setEditorKit(new WrapEditorKit());
edit.setText("111 222 333333333333333333333333333333333333333333333");
getContentPane().add(new JScrollPane(edit));
setSize(200,200);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
WrapApp m = new WrapApp();
m.setVisible(true);
}
}
class WrapEditorKit extends StyledEditorKit {
ViewFactory defaultFactory=new WrapColumnFactory();
public ViewFactory getViewFactory() {
return defaultFactory;
}
}
class WrapColumnFactory implements ViewFactory {
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new WrapLabelView(elem);
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new ParagraphView(elem);
} else if (kind.equals(AbstractDocument.SectionElementName)) {
return new BoxView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
class WrapLabelView extends LabelView {
public WrapLabelView(Element elem) {
super(elem);
}
public int getBreakWeight(int axis, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
int p0 = getStartOffset();
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
if (p1 == p0) {
// can't even fit a single character
return View.BadBreakWeight;
}
return View.GoodBreakWeight;
}
return super.getBreakWeight(axis, pos, len);
}
public float getMinimumSpan(int axis) {
switch (axis) {
case View.X_AXIS:
return 0;
case View.Y_AXIS:
return super.getMinimumSpan(axis);
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
GlyphView v = (GlyphView) createFragment(p0, p1);
return v;
}
return super.breakView(axis, p0, pos, len);
}
}
更新:
关于 GlyphPainter。事实上,我们应该找到一个可以打破 GlyphView 的位置(this
过去在 getBoundedPosition() 中)。 p0表示文档中的char偏移量(GlyphView表示Character元素或Element的片段))
想象一下一大串文本。在文档中它只是一个元素,因为它具有所有相同的属性。但是我们必须创建多个 LabelView,因为文本片段太大而无法容纳可用宽度。因此,对于第一个标签,p0 为 0。然后对于下一个标签,是我们打破初始巨大视图的偏移量。如果我们再次打破它,那么另一个抵消。然后2个参数只代表容器的x位移和宽度(例如父视图)。
所以我们有一个大文本,应该找到中断的地方。当前的方式更简单,因为我们不关心优先级(空格与其他字符)。