0

我有:

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

然后稍后我尝试向工具栏添加一个无序列表按钮,以便操作是:

Action insertBulletAction = 
        HTMLEditorKit.InsertHTMLTextAction ("Bullets", "<ul><li> </li></ul>", 
                                            HTML.Tag.P, HTML.Tag.UL);
JButton insertBulletJButton = new JButton(insertBulletAction);

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

生成的子弹的快照

但是,如果我这样做:

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

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

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

PS:这与这个问题非常相似:How to implement bullet points in a 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", 
                                    "<ul><li> </li></ul>", 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);
}
4

2 回答 2

2

答案其实很复杂。基本上,InsertHtmlAction它本身还不够好。您需要大量的工作和逻辑才能完成工作列表操作。这需要很多逻辑!所以你肯定要重写 Action 类。基本上,参数InsertHtmlAction将根据您在 html 代码中的位置而变化。

话虽如此,我研究了几个开源解决方案,以便更好地了解所有相关内容。很多小时后(以及之前花费了很多小时),我终于能够很好地弄清楚我需要什么。但它相当复杂。在这里写太复杂了,仅仅解释这些概念就需要一本书的一章。即便如此,我仍然对一些细节感到模糊(我仍在努力)。

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

我发现大多数开源解决方案并不能很好地处理列表。它们通常有些工作,但大多数都有明显的错误。或者他们只是除了最基本的列表案例之外什么都不处理。

这是我查看的系统列表,以了解它们如何工作以更好地了解所有内容。不幸的是,我发现文档缺乏或难以理解,因此查看这些项目对我的帮助比其他任何事情都大。

最有帮助的

  • Shef - 最有帮助的。
  • ekit - 体面但有很多错误,不是最好的代码组织
  • MetaphaseEditor - 类似于 ekit

适度有帮助(更复杂、有问题、相关性较低等)

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

附加链接

有薪酬的

  • JWord - 看起来很有趣,但它超出了我所做的预算。
于 2013-07-10T10:30:34.397 回答
1

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

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

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

<html>
  <head></head>
  <body>
    <p style="margin-top: 0">

    </p>
  </body>
</html>

预计,当您插入一个列表时,它被放置段落内:

<html>
  <head></head>
  <body>
    <p style="margin-top: 0">
      <ul>
        <li>

        </li>
      </ul>
    </p>
  </body>
</html>

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

<html>
  <head></head>
  <body>
    <p style="margin-top: 0">
      <ul>
        <li>

        </li>
      </ul>
      Hello
    </p>
  </body>
</html>

这就是为什么“Hello”相对于插入的项目符号出现在右侧的原因。现在,正如斯蒂芬在问题中提到的那样,setText(getText())神奇地解决了这个问题。这是因为手动设置 JTextPane 实例的内容会触发解析器,进而将“内部指针”放置在应有的位置;列表内。现在,当您键入“Hello”时,它会显得更接近项目符号。我说得更近了,因为 HTML 仍有一些不正确的地方:

<html>
  <head></head>
  <body>
    <p style="margin-top: 0">
      <ul>
        <li>
          Hello
        </li>
      </ul>      
    </p>
  </body>
</html>

请注意,列表中没有包含新文本的段落。这就是为什么文本不会出现在项目符号旁边的原因。

你如何处理这一切?嗯,这就是斯蒂芬所说的棘手的一点。正如我们所见,您将面临错误(例如这个)、未记录的故障(例如这个)和默认行为的组合。最简单的方法是使用 Stephane 列表中的一种解决方案。我同意Shef是最好的,但自 2009 年以来没有那么多活动(!)。就个人而言,我发现Stanislav 的网站对所有 EditorKit 都非常有用。

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

    //HTML representation of an empty paragraph
    private static final String sEmptyParagraph = "<p style=\"margin-top: 0\"></p>";

    /**
     * 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 <LI> 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 = "<p style=\"margin-top: 0;\">" +sHTML+ "</p>";

                   //Text in a list already => no nesting (delete <UL>/<OL> tags)
                   if (sHTML.indexOf("<li>") != -1) { 
                      sHTML = sHTML.substring (sHTML.indexOf("<li>"), sHTML.length());
                      sHTML = sHTML.substring (0, sHTML.lastIndexOf("</li>") + 5);

                   //Non-listed text => add <LI> tags     
                   } else sHTML = "<li>" +sHTML+ "</li>"; 

                   sHTMLBlock = sHTMLBlock + sHTML;                  
               }
               sHTMLBlock = "<"+sTypeList+">" +sHTMLBlock.trim()+ "</"+sTypeList+">";

               //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 <LI> block, remove the <LI> tag
                   for (i = iStartIndex; i <= iEndIndex; i++) {
                       sHTML = extractHTML (root.getElement(i), new StringWriter());        
                       sHTML = sHTML.substring(sHTML.indexOf("<li>") + 4, sHTML.length());
                       sHTML = sHTML.substring(0, sHTML.lastIndexOf("</li>"));
                       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); 
        }        
}
于 2013-11-02T01:27:27.970 回答