JTextPane – 具有HTMLEditorKit列表的子弹无法正确呈现,除非我执行setText(getText())并重新绘制

我有:

JTextPane jtextPane = new JTextPane(); jtextPane.setEditorKit(new HTMLEditorKit()); ... 

然后我尝试将无序列表按钮添加到工具栏,以便操作为:

 Action insertBulletAction = HTMLEditorKit.InsertHTMLTextAction ("Bullets", "
", HTML.Tag.P, HTML.Tag.UL); JButton insertBulletJButton = new JButton(insertBulletAction);

如果我转储生成的html,这包括正确的代码。 然而,它将被渲染得非常糟糕,因为如下所示甚至没有接近合理:

生成的子弹的快照

但是,如果我这样做:

 jtextPane.setText(jtextPane.getText()); jtextPane.repaint(); 

一切都很好。 但如果我不做两条线,那么两者都不会单独工作。 我还可以通过在使jtextPane可见之前设置文本来使其工作。

这真的很奇怪,我不明白为什么我必须做一个setText(getText())后跟一个repaint()

PS:这与这个问题非常相似: 如何在JTextPane中实现项目符号? 它的工作原理除了它没有正确渲染。 我不知道它是否与HTMLEditorKit和RTFEditorKit有关,但我导致渲染失败。 下面的html源代码是完美的……

PS2: 此链接也非常方便,但它也没有显示解决方案。

更新:这是所要求的完整代码,但没有其他…

 public static void main(String[] args) { JFrame jframe = new JFrame(); jframe.setSize(800, 600); jframe.setVisible(true); JTextPane jtextPane = new JTextPane(); jtextPane.setEditorKit(new HTMLEditorKit()); Action insertBulletAction = new HTMLEditorKit.InsertHTMLTextAction ("Bullets", "
", HTML.Tag.P, HTML.Tag.UL); JButton insertBulletJButton = new JButton(insertBulletAction); insertBulletJButton.setRequestFocusEnabled(false); jframe.setLayout(new BorderLayout()); jframe.add(new JScrollPane(jtextPane)); jframe.add(insertBulletJButton, BorderLayout.SOUTH); }

答案实际上非常复杂。 基本上, InsertHtmlAction本身就不够好。 您需要大量的工作和逻辑才能进入工作列表操作。 它需要很多逻辑! 所以你必须覆盖Action类。 基本上, InsertHtmlAction的参数将根据您的html代码中的InsertHtmlAction而改变。

话虽如此,我研究了几个开源解决方案,以便更好地理解所涉及的内容。 许多很长时间之后(以及之前花了很多时间),我终于能够很好地理解我需要的东西了。 但它相当复杂。 在这里写起来太复杂了,只需要一本书的一章来解释这些概念。 即便如此,我仍然对某些细节感到模糊(我还在努力)。

我现在可以理解为什么人们为此出售组件!

我发现大多数开源解决方案并不能很好地处理列表。 它们通常有些工作,但大多数都有明显的错误。 除了最基本的列表情况之外,它们或者它们并不真正处理任何事情。

这是我查看的系统列表,以了解它们如何工作以更好地理解所有内容。 遗憾的是,我发现文档缺乏或难以理解,因此查看这些项目对我的帮助最大。

最有帮助的

  • 谢夫 – 最有帮助的。
  • ekit – 体面但很多错误,而不是最好的代码组织
  • MetaphaseEditor – 与ekit类似

适度有用(更复杂,越野车,不太相关等)

  • OOoBean – 为我需要的东西尝试了太多(因而太复杂)。 看起来真的很好,你只需要投入时间。
  • JXHTMLEdit – 看似兴趣

其他链接

  • JWebEngine – 主要用于渲染
  • Joeffice – 很有趣,但这都是video而且还不够准备。
  • Richtext – 没有评论。 我只是简单地看了一下。
  • JRichTextEditor – 没有评论,同样的事情。

付费

  • JWord – 看起来非常有趣,但它超出了预算,我正在做的事情。

对于那些需要更具体解释HTMLEditorKit处理列表的特殊方法的人来说,这一切都归结为生成的标记。 我会尽力保持它尽可能简单。 让我们回过头来谈谈Swing中的HTML文档。

事实certificate,Swing依赖于用于进行光标定位和导航的段落 。 例如,每次在新行中写入时,都会生成一个新的图表。 甚至文档的相应视图也取决于正确位置是否存在段落。 文档中必须始终有一个段落。 否则,奇怪的事情开始发生。

那么,如果文档完全空白会发生什么? 当然,那里不需要一个段落。 嗯,令人难以置信的是,即使在那种情况下也有一段。 这是文档称为p隐含隐含段落的影响之一 。 为空白文档生成的HTML是:

    

预计,当您插入列表时,它将放在段落中:

    

…这当然是无效的标记(不仅仅是因为头部内没有标题)。 可是等等! 它变得更有趣。 插入列表后,文档的“内部指针”将保持在结束标记之后。 因此,如果您键入“Hello”,它将被放置在列表之外:

    

Hello

这就是“Hello”相对于插入的子弹出现在右侧的原因。 现在,正如Stephane在问题中提到的那样, setText(getText())神奇地解决了这个问题。 那是因为手动设置JTextPane实例的内容会触发解析器,而解析器又将“内部指针”置于应该位于的位置; 在列表中。 现在当你输入“Hello”时,它会更接近子弹。 我说得更近了,因为HTML还有一些不对的地方:

    

  • Hello

请注意,列表中没有包含新文本的段落。 这就是为什么文本不会出现在子弹旁边。

你怎么去这一切? 嗯,这是Stephane谈论的棘手问题。 你会遇到一些错误(比如这个 ),未记录的故障(比如这个 )以及我们看到的默认行为。 最简单的方法是使用Stephane列表中的一个解决方案。 我同意Shef是最好的,但自2009年以来没有那么多活动(!)。 就个人而言,我发现Stanislav的网站对于所有的东西都非常有用。

您还可以看看ADAPRO :一个非常稳定的开源辅助编辑器,我参与其中。辅助function有问题,但核心编辑function已经过全面测试。 以下代码来自该项目。 它需要SHEF的 net.atlanticbb.tantlinger.ui.text包中的 ElementWriter

  //HTML representation of an empty paragraph private static final String sEmptyParagraph = "

"; /** * Translates into HTML a given element of the document model. * @param element Element to serialise to a HTML string * @param out Serialiser to HTML string * @return HTML string "equivalent" to given element */ static String extractHTML (Element element, StringWriter out) { ElementWriter writer = new ElementWriter (out, element); try { writer.write(); } catch (IOException e) { System.out.println ("Error encountered when serialising element: " +e); e.printStackTrace(); } catch (BadLocationException e) { System.out.println ("Error encountered when extracting HTML at the element's position: " +e); e.printStackTrace(); } return out.toString(); } /** * Determines if the parent element of the current paragraph element is one of a number provided as a list * of tag names. If so, it returns the parent element. * @param document Document model of the text * @param iCaretPos Caret's current position * @param sTags Possible parent tags * @return Parent element */ static Element getNearestParent (HTMLDocument document, int iCaretPos, String sTags) { Element root; root = document.getParagraphElement (iCaretPos); do { root = root.getParentElement(); } while (sTags.indexOf (root.getName()) == -1); return root; } /** * Inserts all HTML tags required to build an ordered/unordered list at the caret's current position. * If the aim is instead to turn the numbered/bulleted paragraphs into plain ones, it takes care of * deleting the necessary tags. * @param sTypeList Type of list to build: "ul" or "ol". * @param textArea Editable area containing text. */ static void insertList (String sTypeList, JTextPane textArea) { boolean bOnlyListSelected; //selection includes a list exclusively int iStartIndex, iEndIndex, //element indexes included in selection iStartSel, iEndSel, //starting and ending offset of selected text iItemNo, //total number of list items i; String sHTML, //HTML code of text represented by a given element sHTMLBlock, //HTML code block to be inserted into document model sRest; //part of the text remaining unselected after the selected block HTML.Tag tag; //current list tag HTMLDocument document; //data model underlying the typed text Element root, //root element of the document model tree section; //element representing a block of text SimpleAttributeSet attribIns; //backup of current input attributes //Fetches the current document document = (HTMLDocument) textArea.getDocument(); //Finds the topmost parent element of the current paragraph (effectively, is the list inside a table?) root = getNearestParent (document, textArea.getCaretPosition(), "td body"); //Range of elements included in the selection iStartSel = textArea.getSelectionStart(); iEndSel = textArea.getSelectionEnd(); iStartIndex = root.getElementIndex (iStartSel); iEndIndex = root.getElementIndex (iEndSel); //HTML-related initialisations sHTML = ""; sHTMLBlock = ""; tag = null; //Checks if selection is comprised of just list items i = iStartIndex; bOnlyListSelected = true; do { tag = HTML.getTag (root.getElement(i).getName()); //Is it a list tag? if ((tag == null) || ((!tag.equals (HTML.Tag.OL)) && (!tag.equals (HTML.Tag.UL)))) bOnlyListSelected = false; i++; } while (bOnlyListSelected && (i <= iEndIndex)); //Back up current input attributes attribIns = new SimpleAttributeSet (textArea.getInputAttributes()); try { //At some point in the selection there is no previous list... if (!bOnlyListSelected) { //Inserts
  • tags for every text block for (i = iStartIndex; i <= iEndIndex; i++) { section = root.getElement(i); tag = HTML.getTag (section.getName()); //Retrieves current HTML sHTML = extractHTML (section, new StringWriter()); //If it is non-listed text, reconstitute the paragraph if (tag == null) sHTML = "

    " +sHTML+ "

    "; //Text in a list already => no nesting (delete
      /
        tags) if (sHTML.indexOf("
      1. ") != -1) { sHTML = sHTML.substring (sHTML.indexOf("
      2. "), sHTML.length()); sHTML = sHTML.substring (0, sHTML.lastIndexOf("
      3. ") + 5); //Non-listed text => add
      4. tags } else sHTML = "
      5. " +sHTML+ "
      6. "; sHTMLBlock = sHTMLBlock + sHTML; } sHTMLBlock = "<"+sTypeList+">" +sHTMLBlock.trim()+ ""; //Gets the text coming after caret or end of selection sRest = textArea.getText (iEndSel, document.getLength() - iEndSel); //Adds an empty paragraph at the end of the list if the latter coincides with the end of the document //or if the rest of the document is empty. This is to avoid a glitch in the editor kit's write() method. //http://java-sl.com/tip_html_kit_last_empty_par.html if ((root.getElement(iEndIndex).getEndOffset() == root.getEndOffset()) || sRest.replaceAll ("[\\p{Z}\\s]", "").trim().isEmpty()) sHTMLBlock = sHTMLBlock + sEmptyParagraph; //Removes the remaining old non-listed text block and saves resulting HTML string to document model document.setOuterHTML (root.getElement(iEndIndex), sHTMLBlock); if (iEndIndex > iStartIndex) document.remove (root.getElement(iStartIndex).getStartOffset(), root.getElement(iEndIndex - 1).getEndOffset() - root.getElement(iStartIndex).getStartOffset()); //Selection just includes list items } else { //Works out the list's length in terms of element indexes root = root.getElement (root.getElementIndex (iStartSel)); iItemNo = root.getElementCount(); iStartIndex = root.getElementIndex (textArea.getSelectionStart()); iEndIndex = root.getElementIndex (textArea.getSelectionEnd()); //For everery
      7. block, remove the
      8. tag for (i = iStartIndex; i <= iEndIndex; i++) { sHTML = extractHTML (root.getElement(i), new StringWriter()); sHTML = sHTML.substring(sHTML.indexOf("
      9. ") + 4, sHTML.length()); sHTML = sHTML.substring(0, sHTML.lastIndexOf("
      10. ")); sHTMLBlock = sHTMLBlock + sHTML; } //List selected partially? => divide list if (iItemNo > (iEndIndex - iStartIndex + 1)) { //Saves HTML string to document model ((HTMLEditorKit) textArea.getEditorKit()).insertHTML (document, root.getElement(iEndIndex).getEndOffset(), sHTMLBlock, 3, 0, HTML.Tag.P); //Removes the old block document.remove (root.getElement(iStartIndex).getStartOffset(), root.getElement(iEndIndex).getEndOffset() - root.getElement(iStartIndex).getStartOffset()); //Removes the list tag associated with the block } else document.setOuterHTML (root, sHTMLBlock.trim()); } } catch (Exception eTexto) { System.out.println ("Problemas al eliminar/insertar texto: " +eTexto); eTexto.printStackTrace(); } //Recover selection. Previous operations displace the cursor and thus selection highlight is lost textArea.setSelectionStart (iStartSel); textArea.setSelectionEnd (iEndSel); //If only one list item has been created and is the first one, copy all previous style information to the list if ((!bOnlyListSelected) && (iStartSel == iEndSel)) { textArea.setCharacterAttributes (attribIns, false); } }