Java 从非EventDispatchThread使用JEditorPane

Java 从非EventDispatchThread使用JEditorPane,java,multithreading,swing,jeditorpane,Java,Multithreading,Swing,Jeditorpane,我正在使用一个JEditorPane作为“橡皮图章”,将HTML文本呈现为PDF。我需要文本以特定的宽度换行,并且需要在文本后面应用白色的“突出显示”。因此,我在PDF渲染线程中创建一个JEditorPane,设置文本和样式表,然后将其绘制为PDF图形。然而,在HTML编辑器工具包的内部,我得到了一个间歇性的NullPointerException。这在SSCCE中是可复制的: import javax.swing.*; import javax.swing.text.View; import

我正在使用一个JEditorPane作为“橡皮图章”,将HTML文本呈现为PDF。我需要文本以特定的宽度换行,并且需要在文本后面应用白色的“突出显示”。因此,我在PDF渲染线程中创建一个JEditorPane,设置文本和样式表,然后将其绘制为PDF图形。然而,在HTML编辑器工具包的内部,我得到了一个间歇性的NullPointerException。这在SSCCE中是可复制的:

import javax.swing.*;
import javax.swing.text.View;
import javax.swing.text.html.HTMLDocument;
import java.awt.*;
import java.awt.image.BufferedImage;

/**
 * @author sbarnum
 */
public class TextMarkerUtilsTest {
    public static void main(String[] args) throws Exception {
        Rectangle bounds = new Rectangle(255, 255);
        BufferedImage image = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_RGB);
        Graphics2D d = image.createGraphics();
        d.setClip(bounds);
        for (int i=0; i<1000; i++) {
            JEditorPane renderHelper = new JEditorPane("text/html", "<html><body>This is my text.</body></html>");
            HTMLDocument document = (HTMLDocument) renderHelper.getDocument();
            document.getStyleSheet().addRule("foo{color:black;}");
            View rootView = renderHelper.getUI().getRootView(renderHelper);
            rootView.paint(d, bounds);
        }
    }

}
一些有趣的发现:

  • 如果上述操作在事件分派线程中运行,则它可以工作
  • 如果我调用
    addRule(“foo{color:black;}”)
    ,它会工作(我需要指定规则,但规则是什么似乎无关紧要,如果添加了任何规则,它就会失败)
问题出在
javax.swing.text.GlyphPainter1.sync()
,其中
javax.swing.text.GlyphView.getFont()
返回空值。通过设置条件断点,我看到本例中的
GlyphView
javax.swing.text.html.InlineView
。断点停止后调用
getFont()
将返回非空字体,因此某些内容没有及时初始化


我意识到swing组件不是线程安全的,但我是否应该能够在后台线程中实例化JEditorPane并在该后台线程中安全地操作它,只要只有一个线程正在调用该组件?

因为您只使用轻量级组件,这可能是一种选择。您可以使用
ProcessBuilder
(如图所示)将工作从GUI的EDT中删除

publicstaticvoidmain(字符串[]args)引发异常{
setProperty(“java.awt.headless”、“true”);
invokeLater(新的Runnable(){
@凌驾
公开募捐{
矩形边界=新矩形(255,255);
BuffereImage=新的BuffereImage(
bounds.width、bounds.height、BuffereImage.TYPE_INT_RGB);
Graphics2D=image.createGraphics();
d、 setClip(边界);
对于(int i=0;i<1000;i++){
JEditorPane renderHelper=新的JEditorPane(
“text/html”,“这是我的文本。”);
HTMLDocument document=(HTMLDocument)renderHelper.getDocument();
document.getStyleSheet().addRule(“foo{color:black;}”);
View rootView=renderHelper.getUI().getRootView(renderHelper);
rootView.paint(d,边界);
}
}
});
}

由于您仅使用轻量级组件,因此可能是一种选择。您可以使用
ProcessBuilder
(如图所示)将工作从GUI的EDT中删除

publicstaticvoidmain(字符串[]args)引发异常{
setProperty(“java.awt.headless”、“true”);
invokeLater(新的Runnable(){
@凌驾
公开募捐{
矩形边界=新矩形(255,255);
BuffereImage=新的BuffereImage(
bounds.width、bounds.height、BuffereImage.TYPE_INT_RGB);
Graphics2D=image.createGraphics();
d、 setClip(边界);
对于(int i=0;i<1000;i++){
JEditorPane renderHelper=新的JEditorPane(
“text/html”,“这是我的文本。”);
HTMLDocument document=(HTMLDocument)renderHelper.getDocument();
document.getStyleSheet().addRule(“foo{color:black;}”);
View rootView=renderHelper.getUI().getRootView(renderHelper);
rootView.paint(d,边界);
}
}
});
}

感谢Marko建议我查找事件调度线程的回调,我最终在
HTMLDocument.styleChanged()
中找到了一个。我的子类:

public class ThreadFriendlyHTMLDocument extends HTMLDocument {
    @Override
    protected void styleChanged(final Style style) {
        // to fix GlyphPainter1.sync NullPointerException, we call this in the current thread, instead of the EDT
        DefaultDocumentEvent dde = new DefaultDocumentEvent(0,
                          this.getLength(),
                          DocumentEvent.EventType.CHANGE);
        dde.end();
        fireChangedUpdate(dde);
    }
}

感谢Marko建议我查找事件调度线程的回调,我最终在
HTMLDocument.styleChanged()
中找到了一个。我的子类:

public class ThreadFriendlyHTMLDocument extends HTMLDocument {
    @Override
    protected void styleChanged(final Style style) {
        // to fix GlyphPainter1.sync NullPointerException, we call this in the current thread, instead of the EDT
        DefaultDocumentEvent dde = new DefaultDocumentEvent(0,
                          this.getLength(),
                          DocumentEvent.EventType.CHANGE);
        dde.end();
        fireChangedUpdate(dde);
    }
}

您确定您执行的Swing代码从未注册任何回调吗?这些回调将在EDT上执行。我不是指您编写的代码,而是指由于您的Swing调用而执行的所有代码。@Marko,我在上面引用的Swing代码中找不到任何回调,但它相当复杂。我本可以猜到CSS解析是在一个线程中发生的,但据我所知,一切都是在同一个线程中发生的。作为替代方案,任何关于如何在后台线程中将HTML文本呈现给图形对象的建议都是受欢迎的。我确实需要突出显示对背景的支持,以及为HTML指定默认字体和指定固定宽度的功能。@Marko,我确实在EDT上找到了一个实例:DefaultStyledDocument.styleChanged posts,然后在视图上调用rootView.changedUpdate。这可能是罪魁祸首。然而,我看不到任何简单的解决方法。寻找替代方案。但是为什么不将相关代码段的控制权转移给EDT呢?为什么这不是一个选项?您确定您执行的Swing代码从未注册任何回调吗?这些回调将在EDT上执行。我不是指您编写的代码,而是指由于您的Swing调用而执行的所有代码。@Marko,我在上面引用的Swing代码中找不到任何回调,但它相当复杂。我本可以猜到CSS解析是在一个线程中发生的,但据我所知,一切都是在同一个线程中发生的。作为替代方案,任何关于如何在后台线程中将HTML文本呈现给图形对象的建议都是受欢迎的。我确实需要突出显示对背景的支持,以及为HTML指定默认字体和指定固定宽度的功能。@Marko,我确实发现了一个实例:DefaultStyledDocument.styleChanged posts在EDT上,然后
public class ThreadFriendlyHTMLDocument extends HTMLDocument {
    @Override
    protected void styleChanged(final Style style) {
        // to fix GlyphPainter1.sync NullPointerException, we call this in the current thread, instead of the EDT
        DefaultDocumentEvent dde = new DefaultDocumentEvent(0,
                          this.getLength(),
                          DocumentEvent.EventType.CHANGE);
        dde.end();
        fireChangedUpdate(dde);
    }
}