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