JList et JCombobox de grande taille

Sun, 05/11/2008 - 12:52 — Serge Rosmorduc

En développant un dictionnaire hiéroglyphique, j'ai le problème suivant: je souhaite utiliser des JList et/ou des JCombobox pour afficher la liste des mots (avec un Renderer adapté). Hélas, le résultat est très lent. Que se passe-t-il ?

En fait, Swing gère au départ des liste dont les éléments peuvent avoir des tailles très diverses (mettons une liste d'images, par exemple). Résultat: il est impossible de calculer rapidement la taille de la liste. La seule solution est donc de simuler le dessin de la liste dans son intégralité. Et chez moi, ça prenait un temps certain.

Heureusement, il y a setPrototypeCellValue. Cette fonction de JList permet de préciser que toutes les cases auront peu ou prou la même taille. On appelle donc

  ElementDeListe elt= new ElementDeListe("ABCDEFGHIJKLMNOP");
  maJListe.setPrototypeCellValue(elt);

et c'est réglé : java peut calculer la hauteur de la liste en multipliant le nombre d'élément par la hauteur du prototype, et la largeur de la liste est celle du prototype. L'élément qui est passé doit bien évidemment correspondre à ceux que vous désirez mettre dans la liste, et plus exactement à leur taille maximale (si un élément de la liste dépasse, il ne sera pas intégralement affiché). Pour les JCombobox, il y a setPrototypeDisplayValue qui est supposé faire la même chose… Mais hélas, hélas, hélas, il y a un bug (plus précisément le bug 4762915). Pour faire vite: la méthode en question fixe bien la taille de la JCombobox "fermée", mais pas celle de la JList qui est affichée quand on fait une recherche. Après avoir passé un certain temps à identifier le problème, on s'aperçoit qu'il est impossible d'accéder directement à ladite JList, car elle est gérée par la couche de Look and Feel. Donc pas de "getPopupList" ou équivalent à l'horizon pour notre JCombobox.

Heureusement, on peut bricoler. Mais je ne garantis pas la sûreté de la chose. Voilà un patch, inspiré de http://www.orbital-computer.de/JComboBox/ :

combobox = new JComboBox();
combobox.setRenderer(new MonRendererAMoi()); // Je fixe le renderer qui va s'occuper de dessiner mon texte
combobox.setPrototypeDisplayValue(new MotAAfficher("<-A-A-A-A-A-A-A-A->")); // un mot à afficher assez long
                        // remplacez bien évidemment par les données que vous voulez mettre dans votre combobox

// On récupère la JList sous-jacente.
JList listBox;
try {
                                // On accède au champ protégé UI.
Field field = JComponent.class.getDeclaredField("ui");
field.setAccessible(true);
BasicComboBoxUI ui = (BasicComboBoxUI) field.get(combobox);
                                // Là, il y a un champ listBox
field = BasicComboBoxUI.class.getDeclaredField("listBox");
field.setAccessible(true);
                                // On le récupère
listBox = (JList) field.get(ui);
                                // On fixe son prototype
listBox.setPrototypeCellValue(combobox.getPrototypeDisplayValue());
} catch (NoSuchFieldException nsfe) {
throw new RuntimeException(nsfe); // Réexpédition des exceptions sous forme de RuntimeException...
} catch (IllegalAccessException iae) {
throw new RuntimeException(iae);
}

Et ça marche. Pour le problème de la JCombobox (identifié dès le JDK 1.4), j'invite le lecteur intéressé à demander sa résolution.