JEditorPane.scrollToReference()与动态生成的HTML

4
我无法理解如何在动态生成的HTML页面中使用JEditorPane.scrollToReference()。我想打开一个带有JEditorPane的对话框,滚动到我选择的锚点。无论我如何处理问题,视图总是滚动到页面底部。
作为一种丑陋的解决方法,我目前解析整个HTML文档以查找锚标记,在Map<String,Integer>中保存它们的偏移量,然后调用:
editorPane.setCaretPosition(anchorMap.get("anchor-name"));

...这甚至不会产生一个吸引人的结果,因为可见矩形内的插入符位置似乎是不可预测的,并且很少在窗口顶部。我正在寻找更像浏览器的行为,其中锚定的文本出现在可见区域的顶部。

下面是我的笨拙解决方法的屏幕截图("header 6"上有一个锚点),没有触摸滚动条:

screenshot

我觉得我缺少了什么,但我无法确定具体是什么。
我当前的解决方法来源:
import javax.swing.*;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.*;
import javax.swing.text.html.HTMLEditorKit.*;
import javax.swing.text.html.parser.*;
import java.io.*;
import java.awt.*;
import java.util.HashMap;

public class AnchorTest
{
    public static void main(String[] args)
    {
        final String html = generateLongPage();
        final HashMap<String, Integer> anchors = anchorPositions(html);

        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                JEditorPane editor  = new JEditorPane("text/html", html);
                JScrollPane scroller= new JScrollPane(editor);

                scroller.setPreferredSize(new Dimension(400, 250));

                //editor.scrollToReference("anchor6");  // doesn't work...
                editor.setCaretPosition(anchors.get("anchor6")); //sorta works

                JOptionPane.showMessageDialog(new JPanel(), scroller, "",
                        JOptionPane.PLAIN_MESSAGE);
            }});
    }

    public static HashMap<String, Integer> anchorPositions(String html)
    {
        final HashMap<String, Integer> map = new HashMap<String, Integer>();

        Reader reader = new StringReader(html);
        HTMLEditorKit.Parser parser = new ParserDelegator();

        try
        {
            ParserCallback cb = new ParserCallback()
            {
                public void handleStartTag(HTML.Tag t,
                                           MutableAttributeSet a,
                                           int pos)
                {
                    if (t == HTML.Tag.A) {
                        String name = 
                            (String)a.getAttribute(HTML.Attribute.NAME);
                        map.put(name, pos);
                    }
                }
            };

            parser.parse(reader, cb, true);
        }
        catch (IOException ignore) {}

        return map;
    }


    public static String generateLongPage()
    {
        StringBuilder sb = new StringBuilder(
                "<html><head><title>hello</title></head><body>\n");

        for (int i = 0; i < 10; i++) {
            sb.append(String.format(
                    "<h1><a name='anchor%d'>header %d</a></h1>\n<p>", i, i));

            for (int j = 0; j < 100; j++) {
                sb.append("blah ");
            }
            sb.append("</p>\n\n");
        }

        return sb.append("</body></html>").toString();
    }
}

1个回答

4

问题可能是由于当执行scrollToReference时,面板尚未可见或布局尚未完成,因此无法确定其大小。

scrollToReference的实现具有以下块:

Rectangle r = modelToView(iter.getStartOffset());
if (r != null) {
   ...
   scrollRectToVisible(r);
}

在这个示例中,对于anchor6,矩形是null,因为视图尚未可见或其大小不为正。
尝试使用以下“脏”解决方法来延迟滚动:
SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        editor.scrollToReference("anchor6");
    }
});
JOptionPane.showMessageDialog(new JPanel(), scroller, "",
        JOptionPane.PLAIN_MESSAGE);

我和你有同样的想法,但当我尝试时并没有什么不同。你得到了不同的结果吗? - Nick Rippe
@NickRippe 是的,它可以正常滚动。 - tenorsax
啊,没错 - 现在我可以让它工作了。showMessageDialogscrollToReference 都需要放在 invokeLater 语句中(按照这个顺序)。我可能之前出了点问题。+1 - Nick Rippe
谢谢,这个可行。如果有人需要帮助,我想补充一下你发布的第二段代码必须从EDT中运行(如果我错了,请纠正我)。因此,可能需要在invokeLater()中调用invokeLater()。我猜更优雅的解决方案是创建一个JFrame或JDialog,pack()它,然后调用editor.scrollToReference(),最后显示窗口。 - user786233
1
@Chèvre JOptionPane.showMessageDialog 需要在 EDT 上运行。我认为 JOptionPane 只是用于演示。invokeLater 在这里只是充当延迟作用,请求将被排队,并在处理完所有挂起的事件后在 EDT 上执行。 - tenorsax

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接