【问题标题】:JEditorPane linewrap in Java7Java 7 中的 JEditorPane 换行
【发布时间】:2013-07-06 04:30:08
【问题描述】:

首先我希望我开始一个新话题不是问题。 Tbh 我不知道如何根据已经回答的问题提出问题,所以我做了这个。

我对 Java 很陌生,我的问题如下。我正在编写一个小聊天程序,并使用JEditorPaneHTMLEditorKit 来显示不同颜色的文本、显示笑脸和显示超链接。

我的问题是,经过一些研究,我发现问题可能是由于 Java7 引起的,我无法让换行符正常工作。我希望文本自动换行并在超过组件宽度的字符串中间换行。 自动换行可以正常工作,但如果有人输入了一个很长的字符串,JEditorPane 会被扩展,您需要调整框架的大小以将所有内容都显示在屏幕上,这是我不希望发生的。

我已经尝试了一些解决这个问题的方法,但它们只允许换行,这样换行就不再起作用了。除此之外,我希望用户能够通过按 Enter 来换行他的文本。为此,我将 \n 添加到文本中,并且通过修复,这将不再影响结果,所有内容都将显示在一行中。

我觉得我已经在网络上花费了数年时间来寻找解决方案,但现在我的情况没有任何效果,特别是因为它似乎一直都是相同的解决方案。我希望你们能帮助我。

概括地说:

我有什么:

  • 如果长字符串由空格分隔,则换行
  • 如果您使用 Windows 并且您的输入包含通过回车创建的换行,它们也会换行
  • 如果您输入的字符串很长且没有空格,则面板会展开,您需要调整框架大小
  • HTML 格式允许我显示不同的颜色以及超链接和表情符号

我需要什么:

  • 目前的自动换行行为以防万一,但仅在长字符串未用空格分隔以防止面板扩展的情况下才换行。
  • 通过在输入区域中按 ENTER 或我将预先格式化的文本复制到输入面板中手动添加换行
  • HTML 格式就像我已经拥有的一样

我尝试过的和没有帮助的:

jtextpane doesn't wrap textJTextPane is not wrapping text

这里有一些代码可以自己尝试。左下角是一个输入区域,用于输入一些文本。您还可以通过按 Enter 来添加换行。点击按钮后,您将在上方区域看到文字。

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.border.TitledBorder;
import javax.swing.text.BadLocationException;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;

@SuppressWarnings("serial")
public class LineWrapTest extends JFrame implements ActionListener, KeyListener {

private JButton btnSend;
private JTextArea textAreaIn;
private JEditorPane textAreaOut;
private HTMLEditorKit kit;
private HTMLDocument doc;

public LineWrapTest() {

    this.setSize(600, 500);
    this.setDefaultCloseOperation(EXIT_ON_CLOSE);
    this.setLocationRelativeTo(null);
    this.setTitle("Linewrap Test");
}

/**
 * Not important for problem
 */
public void paintScreen() {

    this.setLayout(new BorderLayout());

    this.add(this.getPanelOut(), BorderLayout.CENTER);
    this.add(this.getPanelIn(), BorderLayout.SOUTH);

    this.textAreaIn.requestFocusInWindow();
    this.setVisible(true);
}

/**
 * Not important for problem
 * 
 * @return panelOut
 */
private JPanel getPanelOut() {

    JPanel panelOut = new JPanel();
    panelOut.setLayout(new BorderLayout());

    this.textAreaOut = new JEditorPane();
    this.textAreaOut.setEditable(false);
    this.textAreaOut.setContentType("text/html");

    this.kit = new HTMLEditorKit();
    this.doc = new HTMLDocument();

    StyleSheet styleSheet = this.kit.getStyleSheet();
    this.kit.setStyleSheet(styleSheet);

    this.textAreaOut.setEditorKit(this.kit);
    this.textAreaOut.setDocument(this.doc);

    TitledBorder border = BorderFactory.createTitledBorder("Output");
    border.setTitleJustification(TitledBorder.CENTER);

    panelOut.setBorder(border);
    panelOut.add(this.textAreaOut);

    return panelOut;
}

/**
 * Not important for problem
 * 
 * @return panelIn
 */
private JPanel getPanelIn() {

    JPanel panelIn = new JPanel();
    panelIn.setLayout(new BorderLayout());

    this.textAreaIn = new JTextArea();
    this.textAreaIn.setLineWrap(true);
    this.textAreaIn.setWrapStyleWord(true);

    TitledBorder border = BorderFactory.createTitledBorder("Input");
    border.setTitleJustification(TitledBorder.CENTER);

    panelIn.setBorder(border);
    panelIn.add(this.getBtnSend(), BorderLayout.EAST);
    panelIn.add(this.textAreaIn, BorderLayout.CENTER);

    return panelIn;
}

/**
 * Not important for problem
 * 
 * @return btnSend
 */
private JButton getBtnSend() {

    this.btnSend = new JButton("Send");
    this.btnSend.addActionListener(this);

    return this.btnSend;
}

private void append(String text) {

    try {
        this.kit.insertHTML(this.doc, this.doc.getLength(), text, 0, 0, null);
    } catch (BadLocationException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private String getHTMLText() {

    String txtIn = this.textAreaIn.getText().trim().replaceAll(SEPARATOR, "<br/>");

    StringBuffer htmlBuilder = new StringBuffer();

    htmlBuilder.append("<HTML>");
    htmlBuilder.append(txtIn);
    htmlBuilder.append("</HTML>");

    return htmlBuilder.toString();
}

@Override
public void actionPerformed(ActionEvent e) {

    if (e.getSource() == this.btnSend) {
        this.append(this.getHTMLText());
        this.textAreaIn.setText("");
        this.textAreaIn.requestFocusInWindow();
    }
}

public static void main(String[] args) {

    LineWrapTest test = new LineWrapTest();
    test.paintScreen();
}

@Override
public void keyPressed(KeyEvent e) {

    if (e.getKeyCode() == KeyEvent.VK_ENTER)
        if (!this.textAreaIn.getText().trim().isEmpty())
            this.textAreaIn.setText(this.textAreaIn.getText() + SEPARATOR);
}

@Override
public void keyReleased(KeyEvent e) {
}

@Override
public void keyTyped(KeyEvent e) {
}
}

更新: 基于http://java-sl.com/tip_java7_text_wrapping_bug_fix.html的某些部分

不知何故,我想办法离我的目标更近了一点。我尝试将 HTMLEditorKit 的修复与 StlyedEditorKit 修复结合起来。但老实说,我不知道我在那里实际做了什么 :( 可悲的是,手动换行不再适用于此作为 HTMLEditorKit 的替代品。 或许您可以以此为基础进行更好的实现。

要在我的示例中使用它,只需在项目中使用 CustomEditorKit 创建一个新类,并将示例中的 HTMLEditorKit 替换为此 CustomEditorKit。 您会注意到现在可以使用单词和字母换行,但是如果您按 ENTER 来获得自己的换行,则此更改将不再出现在输出面板中,并且所有内容都将显示在一行中。 另一个奇怪的问题是,如果你调整框架的大小,线条有时会相互重叠。

import javax.swing.SizeRequirements;
import javax.swing.text.Element;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.InlineView;
import javax.swing.text.html.ParagraphView;

@SuppressWarnings("serial")
public class CustomEditorKit extends HTMLEditorKit {

@Override
public ViewFactory getViewFactory() {

    return new HTMLFactory() {
        @Override
        public View create(Element e) {
            View v = super.create(e);
            if (v instanceof InlineView) {
                return new InlineView(e) {
                    @Override
                    public int getBreakWeight(int axis, float pos, float len) {
                        return GoodBreakWeight;
                    }

                    @Override
                    public View breakView(int axis, int p0, float pos, float len) {
                        if (axis == View.X_AXIS) {
                            this.checkPainter();
                            this.removeUpdate(null, null, null);
                        }
                        return super.breakView(axis, p0, pos, len);
                    }
                };
            }
            else if (v instanceof ParagraphView) {
                return new ParagraphView(e) {
                    @Override
                    protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
                        if (r == null) {
                            r = new SizeRequirements();
                        }
                        float pref = this.layoutPool.getPreferredSpan(axis);
                        float min = this.layoutPool.getMinimumSpan(axis);
                        // Don't include insets, Box.getXXXSpan will include them. 
                        r.minimum = (int) min;
                        r.preferred = Math.max(r.minimum, (int) pref);
                        r.maximum = Integer.MAX_VALUE;
                        r.alignment = 0.5f;
                        return r;
                    }

                };
            }
            return v;
        }
    };
    }
}

【问题讨论】:

  • 好的,以上代码对我来说就是这样。 JTextArea 完美包装。长字符串和单词都换行到下一行。 JEdi​​torPane 会包装单词,直到它被一个长字符串扩展。然后换行,句子必须比扩展组件的字符串长。使用通用行分隔符,目前按 Enter 不会导致自定义换行。你想要什么:为长字符串包装 JEditorPane,不添加空格,就像 JTextArea 一样,这样它就不会被扩展。此外,通过在 JEpane 中输入自定义包装。

标签: java swing jeditorpane dom word-wrap


【解决方案1】:

好的!所以,我终于得到了你在工作中遇到的所有问题。这需要一些研究和大量试验和错误,但这里是:

这是我所做的:

  • 将 JEditorPane 放在 JScrollPane 中,这样您就可以在消息变大时上下滚动
  • 添加了自定义自动换行。自定义自动换行将在单词的所需位置换行单词和长单词。你是对的,这是当前 Java 版本的一个错误。 http://bugs.sun.com/view_bug.do?bug_id=7125737
  • 增加了用户通过按 Enter 换行到新行的功能。不过,这干扰了自定义自动换行,所以你可能不喜欢我是如何做到这一点的。在代码示例中,我建议了其他选项。
  • 保留了您的 HTMLDocument 功能。我很想不这样做,但我找到了解决办法,以便保留它。
  • 该应用程序仍使用 JEditorPane,但您可以根据需要将其切换为 JTextPane。我都试过了,它们都可以正常工作。

所以这里是代码。它有点长,您可能希望根据自己的喜好进行更改。我评论了我所做的更改并试图解释它们。

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.SizeRequirements;
import javax.swing.border.TitledBorder;
import javax.swing.text.*;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.InlineView;
import javax.swing.text.html.StyleSheet;

@SuppressWarnings("serial")
public class LineWrapTest extends JFrame implements ActionListener, KeyListener {

    //This is the separator.
    private String SEPARATOR = System.getProperty("line.separator");
    private JButton btnSend;
    private JTextArea textAreaIn;
    private JEditorPane textAreaOut;
    private JScrollPane outputScrollPane;
    private HTMLEditorKit kit;
    private HTMLDocument doc;


    public LineWrapTest() {

        this.setSize(600, 500);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null);
        this.setTitle("Linewrap Test");
    }

    /**
     * Not important for problem
     */
    public void paintScreen() {

        this.setLayout(new BorderLayout());

        this.add(this.getPanelOut(), BorderLayout.CENTER);
        this.add(this.getPanelIn(), BorderLayout.SOUTH);

        this.textAreaIn.requestFocusInWindow();
        this.setVisible(true);
    }


    /**
     * Not important for problem
     * 
     * @return panelOut
     */
    private JPanel getPanelOut() {

        JPanel panelOut = new JPanel();
        panelOut.setLayout(new BorderLayout());

        this.textAreaOut = new JEditorPane();
        this.textAreaOut.setEditable(false);
        this.textAreaOut.setContentType("text/html");

        //I added this scroll pane.
        this.outputScrollPane = new JScrollPane(this.textAreaOut);

        /*
         * This is a whole whack of code.  It's a combination of two sources.
         * It achieves the wrapping you desire: by word and longgg strings
         * It is a custom addition to HTMLEditorKit
         */
        this.kit = new HTMLEditorKit(){
           @Override 
           public ViewFactory getViewFactory(){ 

               return new HTMLFactory(){ 
                   public View create(Element e){ 
                      View v = super.create(e); 
                      if(v instanceof InlineView){ 
                          return new InlineView(e){ 
                              public int getBreakWeight(int axis, float pos, float len) { 
                                  //return GoodBreakWeight;
                                  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;
                                      }
                                      try {
                                          //if the view contains line break char return forced break
                                          if (getDocument().getText(p0, p1 - p0).indexOf(SEPARATOR) >= 0) {
                                              return View.ForcedBreakWeight;
                                          }
                                      }
                                      catch (BadLocationException ex) {
                                          //should never happen
                                      }  

                                  }
                                  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);
                                      try {
                                          //if the view contains line break char break the view
                                          int index = getDocument().getText(p0, p1 - p0).indexOf(SEPARATOR);
                                          if (index >= 0) {
                                              GlyphView v = (GlyphView) createFragment(p0, p0 + index + 1);
                                              return v;
                                          }
                                      }
                                      catch (BadLocationException ex) {
                                          //should never happen
                                      }

                                  }
                                  return super.breakView(axis, p0, pos, len);
                            } 
                          }; 
                      } 
                      else if (v instanceof ParagraphView) { 
                          return new ParagraphView(e) { 
                              protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { 
                                  if (r == null) { 
                                        r = new SizeRequirements(); 
                                  } 
                                  float pref = layoutPool.getPreferredSpan(axis); 
                                  float min = layoutPool.getMinimumSpan(axis); 
                                  // Don't include insets, Box.getXXXSpan will include them. 
                                    r.minimum = (int)min; 
                                    r.preferred = Math.max(r.minimum, (int) pref); 
                                    r.maximum = Integer.MAX_VALUE; 
                                    r.alignment = 0.5f; 
                                  return r; 
                                } 

                            }; 
                        } 
                      return v; 
                    } 
                }; 
            } 
        }; 

        this.doc = new HTMLDocument();

        StyleSheet styleSheet = this.kit.getStyleSheet();
        this.kit.setStyleSheet(styleSheet);

        this.textAreaOut.setEditorKit(this.kit);
        this.textAreaOut.setDocument(this.doc);

        TitledBorder border = BorderFactory.createTitledBorder("Output");
        border.setTitleJustification(TitledBorder.CENTER);

        panelOut.setBorder(border);

        //I changed this to add the scrollpane, which now contains
        //the JEditorPane
        panelOut.add(this.outputScrollPane);

        return panelOut;
    }

    /**
     * Not important for problem
     * 
     * @return panelIn
     */
    private JPanel getPanelIn() {

        JPanel panelIn = new JPanel();
        panelIn.setLayout(new BorderLayout());

        this.textAreaIn = new JTextArea();
        this.textAreaIn.setLineWrap(true);
        this.textAreaIn.setWrapStyleWord(true);

        //This disables enter from going to a new line.  Your key listener does that.
        this.textAreaIn.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "none");
        //For the key listener to work, it needs to be added to the component
        this.textAreaIn.addKeyListener(this);

        TitledBorder border = BorderFactory.createTitledBorder("Input");
        border.setTitleJustification(TitledBorder.CENTER);

        panelIn.setBorder(border);
        panelIn.add(this.getBtnSend(), BorderLayout.EAST);
        panelIn.add(this.textAreaIn, BorderLayout.CENTER);

        return panelIn;
    }

    /**
     * Not important for problem
     * 
     * @return btnSend
     */
    private JButton getBtnSend() {

        this.btnSend = new JButton("Send");
        this.btnSend.addActionListener(this);

        return this.btnSend;
    }


    private void append(String text) {

        try {
            this.kit.insertHTML(this.doc, this.doc.getLength(), text, 0, 0, null);
        } catch (BadLocationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String getHTMLText() {
        //I tried to find a work around for this but I couldn't.  It could be done
        //by manipulating the HTMLDocument but it's beyond me.  Notice I changed
        //<br/> to <p/>.  For some reason, <br/> no longer went to the next line
        //when I added the custom wrap.  <p/> seems to work though.
        String txtIn = this.textAreaIn.getText().trim().replaceAll(SEPARATOR, "<p/>");

        //My IDE recommends you use StringBuilder instead, that's up to you.
        //I am not sure what the difference would be.
        StringBuffer htmlBuilder = new StringBuffer();

        htmlBuilder.append("<HTML>");
        htmlBuilder.append(txtIn);
        htmlBuilder.append("</HTML>");

        return htmlBuilder.toString();
    }

    @Override
    public void actionPerformed(ActionEvent e) {

        if (e.getSource() == this.btnSend) {
            this.append(this.getHTMLText());
            this.textAreaIn.setText("");
            this.textAreaIn.requestFocusInWindow();
        }
    }

    public static void main(String[] args) {
        LineWrapTest test = new LineWrapTest();
        test.paintScreen();
    }

    @Override
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ENTER){
            if (!this.textAreaIn.getText().trim().isEmpty()) {
                //I made this work by defining the SEPARATOR.
                //You could use append(Separator) instead if you want.
                this.textAreaIn.setText(this.textAreaIn.getText() + SEPARATOR);
            }
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
    }

    @Override
    public void keyTyped(KeyEvent e) {
    }

}

这里是(大部分)我用来解决这个问题的链接:

Enabling word wrap in a JTextPane with HTMLDocument

自定义包装是这两者的结合:

http://java-sl.com/tip_html_letter_wrap.html

http://java-sl.com/wrap.html

删除 JTextArea 的键绑定:

http://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html

如果您有任何问题,请在下方发表评论。我会回答他们。我真诚地希望这能解决您的问题

【讨论】:

  • 嗨艾伦,感谢您的详细回答。特别是分隔符对我来说是新的。但是您的修复对我没有帮助。我对自动换行或滚动窗格没有问题,我需要这两个字,一个字母换行。您写道您对我的代码示例有问题,出了什么问题?它为我工作:/
  • @BenGe89 也许我误解了你的问题。您能否对其进行编辑以使其更清楚,确切地说您需要什么以及您的问题是什么?当我运行你的代码时,出现的只是一个空白窗口,右下角有一个按钮。
  • 我已经编辑了 OP,希望它现在让事情变得更加清晰。我还在示例中添加了一些边框,并在 OP 中对其进行了简短描述。我还删除了带有滚动条的部分,因为它与问题没有太大关系,抱歉误导了它。简而言之,我想要我已经拥有的东西,但如果可能的话,使用 Java6 行为换行字,否则为 HTMLEditorKit 换行。该示例仅显示了问题。我还没有添加 line.separator 因为替换不起作用。通过按 Enter,我希望程序创建自己的换行。
  • @BenGe89 好的,感谢您的编辑。我将看看是否有任何解决方案可以提出并相应地编辑我的答案。如果我无法弄清楚,我可能不得不删除我的答案。
  • 非常感谢!非常感谢您对此的帮助!与此同时,我又尝试了一些事情。不是一个很大的成功,但一个小的成功是对 HTMLEditorKit 和 StyledEditorKit 的修复的组合。我已经在我的示例下添加了它,也许你可以从中获取一些信息,我不能:/我肯定需要更深入地研究 Java ...顺便说一句,你知道创建自己的换行符比我更好的方法吗在做什么?不太喜欢“replace(String, String)”方式,尤其是因为它似乎不适用于 line.separator。
【解决方案2】:

我发现了一个更好的解决方案: &lt;br&gt;HTMLEditorKit 正确处理,但 Patrick Sebastien 的帖子提到它不会。这是因为它的ViewFactory 威胁所有InlineView 对象都是可包装的,但BRView 也是InlineView。请参阅下面的解决方案:

class WrapColumnFactory extends HTMLEditorKit.HTMLFactory {

        @Override
        public View create(Element elem) {
            View v = super.create(elem);

            if (v instanceof LabelView) {

                // the javax.swing.text.html.BRView (representing <br> tag) is a LabelView but must not be handled
                // by a WrapLabelView. As BRView is private, check the html tag from elem attribute
                Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
                if ((o instanceof HTML.Tag) && o == HTML.Tag.BR) {
                    return v;
                }

                return new WrapLabelView(elem);
            }

            return v;
        }
    }

    class WrapLabelView extends LabelView {

        public WrapLabelView(Element elem) {
            super(elem);
        }

        @Override
        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);
            }
        }

    }

【讨论】:

  • 这是我为这个问题找到的最好的(最简单也是最全面的)解决方案。我已将此解决方案组件化为answer to a similar question
猜你喜欢
  • 1970-01-01
  • 2013-11-14
  • 2015-09-04
  • 1970-01-01
  • 1970-01-01
  • 2012-10-24
  • 2014-01-18
  • 1970-01-01
  • 2012-04-22
相关资源
最近更新 更多