有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

带有HTMLEditorKit列表的java JTextPane项目符号无法正确呈现,除非我执行setText(getText())并重新绘制

我有:

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进行转储,这确实包含了正确的代码。但是,它将被渲染得非常糟糕,如下图所示,甚至不接近合理:

snapshot of generated bullet

但是,如果我这样做:

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

那么一切都好了<但是如果我不同时做这两行,那么两行都不能单独工作我还可以通过在使jtextPane可见之前设置文本使其工作

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

PS:这非常类似于这个问题:How to implement bullet points in a JTextPane?,它可以工作,只是它不能正确地渲染。我不知道这是否与HTMLEditorKit vs RTFEditorKit有关,但是我发现了一些导致渲染失败的原因。下面的html源代码是完美的

PS2:This link is also very handy但它也没有显示解决方案

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

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

共 (2) 个答案

  1. # 1 楼答案

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

    原来Swing依赖于段落来进行光标定位和导航。例如,每次写入新行时,都会生成一个新的页码。即使是文档的相应视图也取决于段落是否位于正确的位置。文件中必须始终有一段。否则,奇怪的事情就会开始发生

    那么,如果文档完全为空,会发生什么?当然,这里不需要一个段落。好吧,难以置信,即使在这种情况下,也有一段。这是文档所称的p-隐含隐含段落的影响之一。为空白文档生成的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”相对于插入的项目符号显示在右侧。现在,正如Stephane在问题中提到的,setText(getText())神奇地解决了这个问题。这是因为手动设置JTextPane实例的内容会触发解析器,而解析器又会将“内部指针”放置在它应该位于的位置;在名单里面。现在,当您键入“Hello”时,它将显示得更接近子弹。我说得更近了,因为HTML仍然存在一些不正确的地方:

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

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

    你是怎么做到的?这就是Stephane说的棘手的一点。正如我们所看到的,您可能会遇到bug(如this one)、未记录的故障(如this one)和默认行为的组合。最简单的方法是使用Stephane列表中的一种解决方案。我同意谢夫是所有人中最好的,但自2009年以来就没有那么多活动了(!)。就我个人而言,我发现Stanislav's website对EditorIt的所有功能都非常有用

    你也可以看看ADAPRO:一个非常稳定的开源辅助编辑器,我参与了很多工作。辅助功能有缺陷,但核心编辑功能已经过全面测试。以下代码来自该项目。它需要来自SHEF'snet的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); 
            }        
    }
    
  2. # 2 楼答案

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

    话虽如此,我研究了几种开源解决方案,以更好地理解其中所涉及的内容。许多长时间之后(以及之前花费的许多时间),我终于能够很好地解决我需要的问题。但这相当复杂。这里写起来太复杂了,需要一本书的一章来解释这些概念。即使这样,我仍然对一些细节模糊不清(我仍在努力)

    我现在明白了为什么人们会为此销售组件了

    我发现大多数开源解决方案并不能很好地处理列表。它们通常有些工作,但大多数都有明显的bug。或者他们只处理列表中最基本的情况

    以下是我查看的系统列表,以了解它们是如何工作的,从而更好地了解一切。不幸的是,我发现文档缺乏或难以理解,因此查看这些项目对我的帮助最大

    最有用的

    • Shef-最有帮助的
    • ekit不错,但是有很多bug,而且不是最好的代码组织
    • MetaphaseEditor-类似于ekit

    中等程度的帮助(更复杂、有问题、不太相关等)

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

    附加链接

    支付

    • JWord-看起来很有趣,但这超出了我的预算