Обернуть длинные слова в JTextPane (Java 7)
Во всех версиях Java до 6 стандартное поведение JTextPane, помещенного в JScrollPane, было следующим: перенос строк на границах слов, если это возможно. Если нет, то оберните их в любом случае.
В JDK 7 поведение по умолчанию выглядит следующим образом: перенос строк на границах слов, если это возможно. Если нет, просто увеличьте ширину JTextPane (никогда не переносите длинные слова).
Это легко воспроизвести, вот SSCCE:
public class WrappingTest extends JFrame
{
public static void main ( String[] args )
{
new WrappingTest();
}
public WrappingTest ()
{
setSize(200,200);
getContentPane().setLayout(new BorderLayout());
JTextPane jtp = new JTextPane();
JScrollPane jsp = new JScrollPane(jtp);
jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
getContentPane().add(jsp,BorderLayout.CENTER);
setVisible(true);
}
}
Просто запустите его в JDK 6 и в JDK 7, напишите несколько маленьких слов и напишите длинное слово, и вы увидите разницу.
Мой вопрос прост... новое поведение по умолчанию в JDK 7 полностью портит мою программу (они должны быть более осторожны в Oracle с изменением этого типа значений по умолчанию... они кажутся неважными, но когда вы используете JTextPane для отображения данные, которые обычно содержат очень длинные цепочки букв, не так уж и важны - на самом деле я собираюсь подать отчет об ошибке, но я бы хотел обойти это решение, пока / если они его не решат). Есть ли способ вернуться к предыдущему поведению?
Обратите внимание, что я проверил ответ на связанный с этим вопрос. Как в JTextPane реализована перенос слов и как сделать так, чтобы строка переносилась без пробелов? но он не отвечает на этот вопрос - он предоставляет способ создания переноса JTextPane без какого-либо отношения к пробелам, но для меня желаемое поведение - разбивать строки на пробел, если это возможно, и в других местах, если это невозможно (как в предыдущем Java версии).
5 ответов
У меня исправление работает (проверено под 1.7.0_09)
import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
public class WrapTestApp extends JFrame {
public static void main ( String[] args ) {
new WrapTestApp();
}
public WrapTestApp () {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(200,200);
getContentPane().setLayout(new BorderLayout());
JTextPane jtp = new JTextPane();
jtp.setEditorKit(new WrapEditorKit());
JScrollPane jsp = new JScrollPane(jtp);
jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
getContentPane().add(jsp, BorderLayout.CENTER);
jtp.setText("ExampleOfTheWrapLongWordWithoutSpaces");
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 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);
}
}
}
}
Хороший улов от @dk89, но, увы, данные обходные пути не работают: JDK 7, по-видимому, все еще не предлагает ждать установки настраиваемого BreakIterator на JTextComponent; даже на GlyphView, где генерация BreakIterator является частной. И если мы вставим строку char с помощью char, она все равно не будет работать: я полагаю, что последовательные прогоны текста с одинаковым стилем (AttributeSet) свернуты вместе.
Я потратил два дня, пытаясь создать пользовательский EditorKit, как рекомендовано в другом месте, но он не работает как текст (по крайней мере, с JDK 1.7.0_4) в качестве текста.
Я попробовал решение, приведенное в разделе Как переносить текст, хранящийся в JTextPanes, которые являются ячейками в JList, и вариант, найденный по адресу http://www.experts-exchange.com/Programming/Languages/Java/Q_20393892.html
Но я обнаружил, что breakView больше не вызывается, когда JTextPane меньше самого длинного слова в предложении. Так что это не работает вообще, когда есть только одно (длинное) слово. Это наш случай, так как мы показываем предоставленные пользователем идентичные строки, часто без пробелов, в довольно маленьких местах.
В конце концов я нашел простое решение, основанное на предложении в отчете об ошибке: действительно, вставьте строку char с помощью char, но альтернативные стили! Таким образом, у нас столько сегментов, сколько у нас есть символов, и строка обернута в границы символов. До следующего "исправления ошибки"?
Фрагменты кода:
private JTextPane tp;
private SimpleAttributeSet sas = new SimpleAttributeSet();
tp= new JTextPane();
sas.addAttribute( "A", "C" ); // Arbitrary attribute names and value, not used actually
// Set the global attributes (italics, etc.)
tp.setParagraphAttributes(styleParagraphAttributes, true);
Document doc = tp.getDocument();
try
{
doc.remove(0, doc.getLength()); // Clear
for (int i = 0; i < textToDisplay.length(); i++)
{
doc.insertString(doc.getLength(), textToDisplay.substring(i, i+1),
// Change attribute every other char
i % 2 == 0 ? null : sas);
}
}
catch (BadLocationException ble)
{
log.warn("Cannot happen...", ble);
}
Как указывалось в сообщении об ошибке, они должны были предоставить простой способ (возможно, какое-то свойство или что-то вводимое) вернуться к старому поведению.
Посмотрите на эту ошибку:
Привет, у меня была такая же проблема, но я нашел обходной путь:
просто создайте расширенный класс JTextPane, например
MyWrapJTextPane extends JTextPane
и переписать следующий метод - он работает;-)
public boolean getScrollableTracksViewportWidth() {
return true;
}
Переопределение getMinimumSpan и возврат 0 для X_AXIS приводит к разрыву текста по пробелам. Если вам нужен взлом на основе персонажей, попробуйте следующее:
internal class WhitespaceBasedWrapLabelView(elem: Element?) : LabelView(elem) {
override fun getMinimumSpan(axis: Int): Float {
return when (axis) {
X_AXIS -> 0f
Y_AXIS -> super.getMinimumSpan(axis)
else -> throw IllegalArgumentException("Invalid axis: $axis")
}
}
}
internal class CharacterBasedWrapLabelView(elem: Element?) : LabelView(elem) {
override fun getMinimumSpan(axis: Int): Float {
return when (axis) {
X_AXIS -> 0f
Y_AXIS -> super.getMinimumSpan(axis)
else -> throw IllegalArgumentException("Invalid axis: $axis")
}
}
override fun breakView(axis: Int, p0: Int, pos: Float, len: Float): View {
if (axis == X_AXIS) {
checkPainter()
val p1 = glyphPainter.getBoundedPosition(this, p0, pos, len)
// bounded region.
if (p0 == startOffset && p1 == endOffset) {
return this
}
val v = createFragment(p0, p1) as GlyphView
// v.x = pos
getTabbedSpan(pos, tabExpander)
return v
}
return super.breakView(axis, p0, pos, len)
}
override fun getBreakWeight(axis: Int, pos: Float, len: Float): Int {
if (axis == X_AXIS) {
checkPainter()
val p0 = startOffset
val p1 = glyphPainter.getBoundedPosition(this, p0, pos, len)
return if (p1 == p0) BadBreakWeight else GoodBreakWeight
}
return super.getBreakWeight(axis, pos, len)
}
}