1

A JLabel allows HTML contents, which can contain an image among the contents:

String html = "<html><body>...<img src=\"http://some_url/image.png\"/>...</body></html>";
JLabel label = new JLabel(html);

Note that I use the JLabel for rendering images in a JXTreeTable, so the updating of the text of the JLabel is done on the EDT in the renderer.

The problem is that the image gets loaded synchronously. With a slow server, the EDT can be blocked for multiple seconds while the image is being loaded.

I already discovered why the image gets loaded synchronously, and which class I need to modify to switch to async loading of images.

The loading of the image is done by the javax.swing.text.html.ImageView class, which has a method setLoadsSynchronously.

The problem is that I have no clue how I can easily adjust the HTMLFactory / HTMLEditorKit which is responsible for creating that ImageView, and which is used internally by the JLabel . To make matters even more complicated, I need a solution which works for all Look and Feels.

In case the above is not clear, the following thread dump shows on what the EDT is blocked during the image retrieval:

"AWT-EventQueue-0@999" prio=6 tid=0x10 nid=NA waiting
  java.lang.Thread.State: WAITING
      at java.lang.Object.wait(Object.java:-1)
      at java.awt.MediaTracker.waitForID(MediaTracker.java:677)
      at javax.swing.ImageIcon.loadImage(ImageIcon.java:314)
      at javax.swing.ImageIcon.setImage(ImageIcon.java:381)
      at javax.swing.text.html.ImageView.loadImage(ImageView.java:704)
      at javax.swing.text.html.ImageView.refreshImage(ImageView.java:673)
      at javax.swing.text.html.ImageView.sync(ImageView.java:645)
      at javax.swing.text.html.ImageView.getPreferredSpan(ImageView.java:443)
      at javax.swing.text.FlowView$LogicalView.getPreferredSpan(FlowView.java:732)
      at javax.swing.text.FlowView.calculateMinorAxisRequirements(FlowView.java:233)
      at javax.swing.text.ParagraphView.calculateMinorAxisRequirements(ParagraphView.java:717)
      at javax.swing.text.html.ParagraphView.calculateMinorAxisRequirements(ParagraphView.java:157)
      at javax.swing.text.BoxView.checkRequests(BoxView.java:935)
      at javax.swing.text.BoxView.getMinimumSpan(BoxView.java:568)
      at javax.swing.text.html.ParagraphView.getMinimumSpan(ParagraphView.java:270)
      at javax.swing.text.BoxView.calculateMinorAxisRequirements(BoxView.java:903)
      at javax.swing.text.html.BlockView.calculateMinorAxisRequirements(BlockView.java:146)
      at javax.swing.text.BoxView.checkRequests(BoxView.java:935)
      at javax.swing.text.BoxView.getMinimumSpan(BoxView.java:568)
      at javax.swing.text.html.BlockView.getMinimumSpan(BlockView.java:378)
      at javax.swing.text.BoxView.calculateMinorAxisRequirements(BoxView.java:903)
      at javax.swing.text.html.BlockView.calculateMinorAxisRequirements(BlockView.java:146)
      at javax.swing.text.BoxView.checkRequests(BoxView.java:935)
      at javax.swing.text.BoxView.getMinimumSpan(BoxView.java:568)
      at javax.swing.text.html.BlockView.getMinimumSpan(BlockView.java:378)
      at javax.swing.text.BoxView.calculateMinorAxisRequirements(BoxView.java:903)
      at javax.swing.text.html.BlockView.calculateMinorAxisRequirements(BlockView.java:146)
      at javax.swing.text.BoxView.checkRequests(BoxView.java:935)
      at javax.swing.text.BoxView.getPreferredSpan(BoxView.java:545)
      at javax.swing.text.html.BlockView.getPreferredSpan(BlockView.java:362)
      at javax.swing.plaf.basic.BasicHTML$Renderer.<init>(BasicHTML.java:383)
      at javax.swing.plaf.basic.BasicHTML.createHTMLView(BasicHTML.java:67)
      at javax.swing.plaf.basic.BasicHTML.updateRenderer(BasicHTML.java:207)
      at javax.swing.plaf.basic.BasicLabelUI.propertyChange(BasicLabelUI.java:417)
      at javax.swing.plaf.synth.SynthLabelUI.propertyChange(SynthLabelUI.java:296)
      at java.beans.PropertyChangeSupport.fire(PropertyChangeSupport.java:335)
      at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:327)
      at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:263)
      at java.awt.Component.firePropertyChange(Component.java:8428)
      at org.jdesktop.swingx.renderer.JRendererLabel.firePropertyChange(JRendererLabel.java:292)
      at javax.swing.JLabel.setText(JLabel.java:330)
Robin
  • 36,233
  • 5
  • 47
  • 99
  • 2
    The examples [here](https://stackoverflow.com/q/4530428/230513) use `SwingWorker` to update a label's icon. – trashgod Jul 06 '17 at 09:58

2 Answers2

2

The image is only part of the HTML…calling setIcon is not an option.

One approach would be to load the image in the background of a SwingWorker, save it temporarily to the file system, and reference the saved file in the <img/> tag. The variation below, adapted from this example, is a proof of concept. Your actual implementation might employ a SwingWorker<List<Row>, Row>, where each Row contains an image File; your doInBackground() implementation would publish() interim results as they become available; your implementation of process() would ensure that the relevant tree-table renderer sees the correct File for a given Row.

import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;

/**
 * @see http://stackoverflow.com/questions/4530659 */
public class WorkerTest extends JFrame {

    private JPanel panel = new JPanel();
    private JLabel label = new JLabel("Loading...");

    public WorkerTest() {
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        label.setHorizontalTextPosition(JLabel.CENTER);
        label.setVerticalTextPosition(JLabel.CENTER);
        this.add(label);
        this.pack();
        this.setLocationRelativeTo(null);
    }

    private void start() {
        new ImageWorker().execute();
    }

    public static void main(String args[]) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                WorkerTest wt = new WorkerTest();
                wt.setVisible(true);
                wt.start();
            }
        });
    }

    class ImageWorker extends SwingWorker<File, Void> {

        private static final String TEST =
            "http://cdn.sstatic.net/stackexchange/img/logos/so/so-logo.png";
        private BufferedImage image;
        private File file;

        @Override
        protected File doInBackground() throws IOException {
            image = ImageIO.read(new URL(TEST));
            file = File.createTempFile("image", null);
            ImageIO.write(image, "png", file);
            return file;
        }

        @Override
        protected void done() {
            label.setText("<html><body><img src=\"file://"
                + file.getAbsolutePath() + "\"/></body></html>");
            panel.setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
            WorkerTest.this.pack();
            WorkerTest.this.setLocationRelativeTo(null);
        }
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • 1
    That approach would probably work. I am going to accept this answer without testing it. It is hard to implement this solution in my code, so in the end I just abandoned the `JLabel` approach, and completely avoided showing HTML in the `JXTreeTable` – Robin Jul 12 '17 at 10:20
0

Using SwingWorkers (as suggested bytrashgod) is the best solution. An alternative would be something like that: Create an asynch method that donwload the image:

new Thread(new Runnable() {
    public void run() {
       //download the image
       BufferedImage image = ImageIO.read(url);
       //update the label
       updateLabel(image)
    }
}).start();

that calls another method that updates the label in the EDT

private void updateLabel(final BufferedImage image) {
  SwingUtilities.invokeLater(new Runnable() {
    public void run(){
      // fine updating here... event dispatch thread
      label.setIcon(new ImageIcon(image));
    }
  });
}
navy1978
  • 1,411
  • 1
  • 15
  • 35
  • While not exactly visible in my question, the image is only part of the HTML. So calling `setIcon` is not an option. – Robin Jul 07 '17 at 07:15
  • So update your code, maybe it's better to translate the HTML code in swing components... – navy1978 Jul 07 '17 at 08:01