Why do I need to reset setText() in a JLabel to prevent errors?

六眼飞鱼酱① 提交于 2019-12-10 23:55:27

问题


As a follow-up to this question that I posted earlier, I am wondering about the cause of the issue I had.

The problem was that I was getting this error when updating a JLabel with a lot of HTML text.

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
    at javax.swing.text.html.StyleSheet$ListPainter.paint(Unknown Source)
    at javax.swing.text.html.ListView.paintChild(Unknown Source)
    at javax.swing.text.BoxView.paint(Unknown Source)
    at javax.swing.text.html.BlockView.paint(Unknown Source)
    at javax.swing.text.html.ListView.paint(Unknown Source)
    at javax.swing.text.BoxView.paintChild(Unknown Source)
    at javax.swing.text.BoxView.paint(Unknown Source)
    at javax.swing.text.html.BlockView.paint(Unknown Source)
    at javax.swing.text.BoxView.paintChild(Unknown Source)
    at javax.swing.text.BoxView.paint(Unknown Source)
    at javax.swing.text.html.BlockView.paint(Unknown Source)
    at javax.swing.text.BoxView.paintChild(Unknown Source)
    at javax.swing.text.BoxView.paint(Unknown Source)
    at javax.swing.text.html.BlockView.paint(Unknown Source)
    at javax.swing.plaf.basic.BasicHTML$Renderer.paint(Unknown Source)
    at javax.swing.plaf.basic.BasicLabelUI.paint(Unknown Source)
    at javax.swing.plaf.ComponentUI.update(Unknown Source)
    at javax.swing.JComponent.paintComponent(Unknown Source)
    at javax.swing.JComponent.paint(Unknown Source)
    at javax.swing.JComponent.paintToOffscreen(Unknown Source)
    at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(Unknown Source)
    at javax.swing.RepaintManager$PaintManager.paint(Unknown Source)
    at javax.swing.RepaintManager.paint(Unknown Source)
    at javax.swing.JComponent._paintImmediately(Unknown Source)
    at javax.swing.JComponent.paintImmediately(Unknown Source)
    at javax.swing.RepaintManager$4.run(Unknown Source)
    at javax.swing.RepaintManager$4.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
    at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
    at javax.swing.RepaintManager.prePaintDirtyRegions(Unknown Source)
    at javax.swing.RepaintManager.access$1200(Unknown Source)
    at javax.swing.RepaintManager$ProcessingRunnable.run(Unknown Source)
    at java.awt.event.InvocationEvent.dispatch(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$500(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)

The HTML that's being set is generated by a StringBuilder and the converted .toString. Something like this, but more extensive:

public static String entityOverviewHTML() {
    StringBuilder sb = new StringBuilder();
    List<Entity> entities = Entity.getEntities();

    sb.append("<html><div style='padding: 12px;'>");

    sb.append("<h1>People: alive</h1>");

    // A lot of appending: also loop through getEntities (dynamic, can change)
    // and get contents from each entity in #getEntityInfo below
    if (entities.size() > 0) {
        sb.append("<ul>");
        for (Entity e: entities) {
            getEntityInfo(e);
        }
        sb.append("</ul>");
    }

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

private static StringBuilder getEntityInfo(Entity e) {
    StringBuilder sbInfo = new StringBuilder();
    // A lot of appending: append info of Entity e such as e.getFirstName()
    sbInfo.append("<li>");
    sbInfo.append(e.getFirstName())
    sbInfo.append("</li>");

    return sbInfo;
}

After some events, the HTML will change after which I call a custom refresh method:

public static void bringToFront() {
    getInstance().setVisible(true);
    getInstance().setExtendedState(JFrame.ICONIFIED);
    getInstance().setExtendedState(JFrame.NORMAL);
}

public static void refresh() {
    // text is a JLabel
    text.setText(entityOverviewHTML());
    bringToFront();
}

And it's then that the errors at the top of this post happen, however not always! I haven't figured out why this happens, but I did find that when resetting the text to an empty string and then calling entityOverviewHTML solves the issue.

public static void refresh() {
    text.setText(""); // Here we go
    text.setText(entityOverviewHTML());
    bringToFront();
}

text is defined as a Class variable:

private static JLabel text = new JLabel();

What I like to know is: why? How can this single line of seemingly obsolete code solve the problem? Exactly what is the problem?


回答1:


The problem is that your refresh() method is not called on the Swing EDT.

Swing is not threadsafe (https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html) which means that the call text.setText(entityOverviewHTML()); corrupts the internal data structures that your JLabel instance uses to display the text.

Your refresh-method needs to be rewritten like this:

public static void refresh() {
    try {
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                text.setText(entityOverviewHTML());
                bringToFront();
            }
        });
    } catch (InvocationTargetException | InterruptedException e) {
        e.printStackTrace();
    }
}



回答2:


Usually when JLabel is instantiated, it has no width , if it is instantiated with empty text width is zero. When JLabel text is updated it won't show up as its size is not set properly.

Use preferred size: text.setPreferredSize(new Dimension(x, y)); then text.setText(html)



来源:https://stackoverflow.com/questions/34593471/why-do-i-need-to-reset-settext-in-a-jlabel-to-prevent-errors

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!