Question

J'essaie de comprendre comment créer une zone de liste virtuelle (ou une arborescence ou un contour) dans Swing - il s'agirait d'une boîte où la zone de liste peut afficher une & "; vue &"; dans un ensemble de résultats volumineux à partir d'une base de données sans obtenir le contenu complet de l'ensemble de résultats; Tout ce qu’il me faut, c’est que les éléments N1 à N2 doivent être affichés sous peu, pour que je puisse les récupérer et demander le contenu de l’article N.

.

Je sais comment le faire dans Win32 ( ListView + LVS_OWNERDATA ) et dans XUL ( analyse personnalisée ), et j'ai trouvé Quelque chose pour SWT , mais pas Swing.

Des suggestions?

update: aha, je ne comprenais pas quoi rechercher dans les moteurs de recherche, & amp; les tutoriels ne semblent pas appeler cela une " liste virtuelle " ou utiliser l'idée. J'ai trouvé un bon tutoriel à partir duquel je peux commencer et l'un des le didacticiels Sun semble également correct.

Voici mon exemple de programme, qui fonctionne comme je l'espérais ... sauf que la listbox interroge mon AbstractListModel pour toutes les lignes, pas seulement les lignes qui sont visible. Pour une table virtuelle d'un million de lignes, ce n'est pas pratique. Comment puis-je réparer cela? (edit: il semble que setPrototypeCellValue corrige cela. Mais je ne comprends pas pourquoi ...)

package com.example.test;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.AbstractListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

// based on:
// http://www.java2s.com/Tutorial/Java/0240__Swing/extendsAbstractListModel.htm
// http://www.java2s.com/Tutorial/Java/0240__Swing/SpinnerNumberModel.htm
// http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/SpinnerNumberModel.html
// http://www.java2s.com/Tutorial/Java/0240__Swing/ListeningforJSpinnerEventswithaChangeListener.htm

public class HanoiMoves extends JFrame {
    public static void main(String[] args) {
        HanoiMoves hm = new HanoiMoves();
    }

    static final int initialLevel = 6;
    final private JList list1 = new JList();
    final private HanoiData hdata = new HanoiData(initialLevel);

    public HanoiMoves() {
        this.setTitle("Solution to Towers of Hanoi");
        this.getContentPane().setLayout(new BorderLayout());
        this.setSize(new Dimension(400, 300));
        list1.setModel(hdata);

        SpinnerModel model1 = new SpinnerNumberModel(initialLevel,1,31,1);
        final JSpinner spinner1 = new JSpinner(model1);

        this.getContentPane().add(new JScrollPane(list1), BorderLayout.CENTER);
        JLabel label1 = new JLabel("Number of disks:");
        JPanel panel1 = new JPanel(new BorderLayout());
        panel1.add(label1, BorderLayout.WEST);
        panel1.add(spinner1, BorderLayout.CENTER);
        this.getContentPane().add(panel1, BorderLayout.SOUTH);      

        ChangeListener listener = new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                Integer newLevel = (Integer)spinner1.getValue();
                hdata.setLevel(newLevel);
            }
        };

        spinner1.addChangeListener(listener);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
    }
}

class HanoiData extends AbstractListModel {
    public HanoiData(int level) { this.level = level; }

    private int level;
    public int getLevel() { return level; }
    public void setLevel(int level) {
        int oldSize = getSize();
        this.level = level;
        int newSize = getSize();

        if (newSize > oldSize)
            fireIntervalAdded(this, oldSize+1, newSize);
        else if (newSize < oldSize)
            fireIntervalRemoved(this, newSize+1, oldSize);
    }   

    public int getSize() { return (1 << level); }

    // the ruler function (http://mathworld.wolfram.com/RulerFunction.html)
    // = position of rightmost 1
    // see bit-twiddling hacks page:
    // http://www-graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup
    public int rulerFunction(int i)
    {
        long r1 = (i & (-i)) & 0xffffffff;
        r1 *= 0x077CB531;
        return MultiplyDeBruijnBitPosition[(int)((r1 >> 27) & 0x1f)];       
    }
    final private static int[] MultiplyDeBruijnBitPosition = 
    {
        0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 
        31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
    };  

    public Object getElementAt(int index) {
        int move = index+1;
        if (move >= getSize())
            return "Done!";

        int disk = rulerFunction(move)+1;
        int x = move >> (disk-1); // guaranteed to be an odd #
        x = (x - 1) / 2;
        int K = 1 << (disk&1); // alternate directions for even/odd # disks
        x = x * K;
        int post_before = (x % 3) + 1;
        int post_after  = ((x+K) % 3) + 1;
        return String.format("%d. move disk %d from post %d to post %d", 
                move, disk, post_before, post_after);
    }
}

mettre à jour:

Par suggestion de jfpoilpret, je mets un point d'arrêt dans la getElementData() fonction.

if ((index & 0x3ff) == 0)
{
  System.out.println("getElementAt("+index+")");
}

J'ai regardé le stacktrace pour le fil en question. Ce n'est pas vraiment utile (posté ci-dessous). Cependant, il semble que les coupables soient fireIntervalAdded () / fireIntervalRemoved () et la modification du résultat de getSize (). FireIntervalxxxx semble indiquer Swing pour vérifier la fonction getSize () et, si la taille change, il récupère TOUT le contenu de la ligne immédiatement (ou au moins il place des requêtes dans la file d’événements pour le faire).

Il faut qu'il y ait un moyen de le dire: ne faites pas ça !!!! mais je ne sais pas quoi.

com.example.test.HanoiMoves at localhost:3333   
    Thread [main] (Suspended (breakpoint at line 137 in HanoiData)) 
        HanoiData.getElementAt(int) line: 137   
        BasicListUI.updateLayoutState() line: not available 
        BasicListUI.maybeUpdateLayoutState() line: not available    
        BasicListUI.getPreferredSize(JComponent) line: not available    
        JList(JComponent).getPreferredSize() line: not available    
        ScrollPaneLayout$UIResource(ScrollPaneLayout).layoutContainer(Container) line: not available    
        JScrollPane(Container).layout() line: not available 
        JScrollPane(Container).doLayout() line: not available   
        JScrollPane(Container).validateTree() line: not available   
        JPanel(Container).validateTree() line: not available    
        JLayeredPane(Container).validateTree() line: not available  
        JRootPane(Container).validateTree() line: not available 
        HanoiMoves(Container).validateTree() line: not available    
        HanoiMoves(Container).validate() line: not available    
        HanoiMoves(Window).show() line: not available   
        HanoiMoves(Component).show(boolean) line: not available 
        HanoiMoves(Component).setVisible(boolean) line: not available   
        HanoiMoves(Window).setVisible(boolean) line: not available  
        HanoiMoves.<init>() line: 69    
        HanoiMoves.main(String[]) line: 37  
    Thread [AWT-Shutdown] (Running) 
    Daemon Thread [AWT-Windows] (Running)   
    Thread [AWT-EventQueue-0] (Running) 

Mise à jour: j'ai essayé d'utiliser le code FastRenderer.java de Article de programmation avancée JList et corrigé. Mais il s'avère que ce n'est pas du tout le moteur de rendu! Une ligne de code a corrigé mon problème et je ne comprends pas pourquoi:

list1.setPrototypeCellValue(list1.getModel().getElementAt(0));
Était-ce utile?

La solution

Je soupçonne que la raison pour accéder à l'ensemble du modèle pourrait être liée au calcul de la taille de la liste.

Vous pouvez essayer d'ajouter un point d'arrêt dans votre méthode getElementAt () de modèle. Je vous suggère de le faire de cette façon:

if (index == 100)
{
    System.out.println("Something");//Put the breakpoint on this line
}

La constante 100 est une valeur < getSize () mais plus grand que le nombre de lignes visibles initialement (de cette façon, vous ne couperez pas pour toutes les lignes visibles). Lorsque vous entrez ce point d'arrêt, jetez un œil à l'endroit d'où votre modèle a été appelé. Cela peut vous donner des indices. Vous pouvez poster la trace de pile ici pour que nous essayions de vous aider davantage.

Autres conseils

Le problème est que même en utilisant la prélecture intelligente, vous ne pouvez pas garantir que toutes les lignes visibles ont été prélues en cas de besoin.

Je vais esquisser une solution que j'ai utilisée une fois dans un projet et qui a extrêmement bien fonctionné.

Ma solution consistait à créer un ListModel qui renverrait un stub pour les lignes manquantes indiquant à l'utilisateur que l'élément est en cours de chargement. (Vous pouvez améliorer l'expérience visuelle avec un ListCellRenderer personnalisé qui rend le stub spécialement). De plus, faites en ListModel mettre en file d'attente une demande d'extraction de la ligne manquante. Le fireContentsChanges devra générer un thread qui lit la file d'attente et récupère les lignes manquantes. Après l'extraction d'une ligne, appelez <=> la ligne extraite. Vous pouvez également utiliser un exécuteur dans votre liste:

private Map<Integer,Object> cache = new HashMap<Integer,Object>();
private Executor executor = new ThreadPoolExecutor(...);
...
public Object getElementAt(final int index) {
  if(cache.containsKey(index)) return cache.get(index);
  executor.execute(new Runnable() {
        Object row = fetchRowByIndex(index);
        cache.put(index, row);
        fireContentsChanged(this, index, index);
  }
}

Vous pouvez améliorer cette solution esquissée des manières suivantes:

  • Non, seulement chercher l'élément demandé, mais aussi certains éléments & "autour de &"; il. L'utilisateur sera probablement faire défiler de haut en bas.
  • Dans le cas de listes vraiment volumineuses, <=> oubliez les lignes les plus éloignées de celles récupérées en dernier.
  • Utiliser un cache LRU
  • Si vous le souhaitez, effectuez une pré-extraction de tous les éléments du fil d'arrière-plan.
  • Faites du ListModel un décorateur pour une implémentation enthousiaste de ListModel (c'est ce que j'ai fait)
  • Si vous avez plusieurs " gros " Les ListModels for Lists visibles en même temps utilisent une file d'attente de demandes centrale pour extraire les éléments manquants.

Consultez les liaisons jgoodies . Je ne suis pas sûr qu'ils feront ce que vous voulez (je ne les ai pas utilisés ... je suis juste au courant du projet).

Etendre AbstractListModel , que vous pouvez passer dans le constructeur JList.

Dans votre implémentation, définissez la taille de votre liste aussi grande que nécessaire (avec la valeur renvoyée par getSize). Si les données pour cet élément de la liste ne sont pas disponibles, retournez une ligne vide (via getElementAt). Lorsque les données sont disponibles, appelez fireContentsChanged pour les lignes mises à jour.

Aha: le problème réside dans le rendu, mais je ne comprends pas vraiment pourquoi.

J'ai utilisé le TextCellRenderer mentionné dans le programme FastRenderer.java de l'article Programmation JList avancée . Mais je ne comprends pas vraiment pourquoi cela fonctionne et quelles sont les mises en garde à ce sujet ....: /

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top