2017-07-06 2 views
1

Un JLabel permet le contenu HTML, qui peuvent contenir une image parmi le contenu:JLabel: charge images HTML de façon asynchrone

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

Notez que j'utilise le JLabel pour le rendu des images dans un JXTreeTable, de sorte que la mise à jour du texte du JLabel est fait sur l'EDT dans le moteur de rendu.

Le problème est que l'image est chargée de manière synchrone. Avec un serveur lent, l'EDT peut être bloqué pendant plusieurs secondes pendant le chargement de l'image.

J'ai déjà découvert pourquoi l'image est chargée de manière synchrone et quelle classe je dois modifier pour passer au chargement asynchrone d'images.

Le chargement de l'image est effectué par la classe javax.swing.text.html.ImageView, qui a une méthode setLoadsSynchronously.

Le problème est que je n'ai pas la moindre idée comment je peux facilement régler le HTMLFactory/HTMLEditorKit qui est responsable de la création de ce ImageView, et qui est utilisé en interne par le JLabel. Pour rendre les choses encore plus compliquées, j'ai besoin d'une solution qui fonctionne pour tout Look and Feels.

Dans le cas où ce qui précède n'est pas clair, la décharge de fil suivant montre ce que l'EDT est bloquée au cours de la recherche d'images:

"[email protected]" 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) 
+2

Les exemples [ici] (https://stackoverflow.com/q/4530428/230513) utilisent 'SwingWorker' pour mettre à jour l'icône d'un label. – trashgod

Répondre

1

L'image est seulement partie du HTML ... appelant setIcon n'est pas une option.

Une approche serait de charger l'image en arrière-plan d'un SwingWorker, enregistrez temporairement au système de fichiers, et référencer le fichier enregistré dans la balise <img/>. La variation ci-dessous, adaptée de cette example, est une preuve de concept. Votre implémentation réelle peut utiliser un SwingWorker<List<Row>, Row>, où chaque contient une image File; votre mise en œuvre doInBackground()publish() résultats provisoires dès qu'ils deviennent disponibles; votre implémentation de process() garantirait que le rendu de table arborescente pertinent voit le File correct pour un donné.

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); 
     } 
    } 
} 
+1

Cette approche fonctionnerait probablement. Je vais accepter cette réponse sans la tester.Il est difficile d'implémenter cette solution dans mon code, donc à la fin j'ai juste abandonné l'approche 'JLabel', et j'ai complètement évité de montrer du HTML dans le' JXTreeTable' – Robin

0

En utilisant SwingWorkers (comme suggéré bytrashgod) est la meilleure solution. Une alternative serait quelque chose comme ça: Créer une méthode asynch qui donwload l'image:

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

qui appelle une autre méthode qui met à jour l'étiquette dans l'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)); 
    } 
    }); 
} 
+0

Bien que pas exactement visible dans ma question, l'image n'est qu'une partie du code HTML. Donc appeler 'setIcon' n'est pas une option. – Robin

+0

Alors mettez à jour votre code, peut-être qu'il vaut mieux traduire le code HTML dans les composants swing ... – navy1978