Domanda

Sto cercando di capire come creare una casella di riepilogo virtuale (o albero o struttura) in Swing - questo sarebbe uno in cui la casella di riepilogo può mostrare un " view " all'interno di un ampio set di risultati da un database senza ottenere l'intero contenuto del set di risultati; tutto quello che deve darmi è un avvertimento che gli articoli N1 - N2 dovranno essere visualizzati presto, quindi posso recuperarli e chiedere il contenuto dell'articolo N.

So come farlo in Win32 ( ListView + LVS_OWNERDATA ) e in XUL ( treeview personalizzato ) e ho trovato qualcosa per SWT , ma non Swing.

Qualche suggerimento?


aggiornamento: aha, non ho capito cosa cercare nei motori di ricerca, & amp; i tutorial non sembrano chiamarlo un " listbox virtuale " o usa l'idea. Ho trovato un un buon tutorial da cui posso iniziare, e uno dei il Tutorial Sun sembra anche ok.

Ecco il mio programma di esempio, che funziona come mi aspetto ... tranne sembra che la casella di riepilogo interroghi il mio AbstractListModel per tutte le righe, non solo le righe che sono visibile. Per una tabella virtuale da un milione di righe questo non è pratico. Come posso risolvere questo problema? (modifica: sembra che setPrototypeCellValue risolva questo problema. Ma non capisco perché ...)

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

Aggiornamento:

per il suggerimento di jfpoilpret, ho inserito un punto di interruzione nella funzione getElementData().

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

Ho guardato lo stacktrace per il thread in questione. Non è poi così utile (pubblicato di seguito). Da alcune altre modifiche, tuttavia, sembra che i colpevoli siano fireIntervalAdded () / fireIntervalRemoved () e la modifica del risultato di getSize (). FireIntervalxxxx sembra indurre Swing a controllare la funzione getSize () e, se la dimensione cambia, va e recupera immediatamente TUTTO il contenuto della riga (o almeno inserisce richieste nella coda degli eventi per farlo).

Ci deve essere un modo per dirlo Non farlo !!!! ma non so cosa.

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) 

Aggiornamento: ho provato a utilizzare parte del codice FastRenderer.java dal Articolo di programmazione Advanced JList e questo è stato risolto. Ma si scopre che non è affatto il renderer! Una riga di codice ha risolto il mio problema e non capisco perché:

list1.setPrototypeCellValue(list1.getModel().getElementAt(0));
È stato utile?

Soluzione

Sospetto che il motivo per accedere all'intero modello potrebbe essere correlato al calcolo delle dimensioni dell'elenco.

Quello che potresti provare è aggiungere un punto di interruzione nel tuo metodo getElementAt () modello. Ti suggerisco di farlo in questo modo:

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

La costante 100 è un valore < getSize () ma maggiore del numero iniziale visibile di righe (in questo modo non avrai interruzioni per tutte le righe visibili). Quando inserisci questo punto di interruzione, dai un'occhiata al nome da cui è stato chiamato il tuo modello, questo potrebbe darti alcuni suggerimenti. Puoi pubblicare la traccia dello stack qui per noi per provare ad aiutarti ulteriormente.

Altri suggerimenti

Il problema è che anche usando il pre-fetch intelligente non puoi garantire che tutte le righe visibili siano state precaricate quando sono necessarie.

Disegnerò una soluzione che ho usato una volta in un progetto e che ha funzionato molto bene.

La mia soluzione era quella di creare un ListModel che restituisse uno stub per le righe mancanti che informavano l'utente che l'elemento stava caricando. (Puoi migliorare l'esperienza visiva con un ListCellRenderer personalizzato che rende lo stub in modo speciale). Inoltre, accoda una richiesta per recuperare la riga mancante. ListModel dovrà generare un thread che legge la coda e recupera le righe mancanti. Dopo aver recuperato una riga, richiamare fireContentsChanges nella riga recuperata. Puoi anche usare un Executor nel tuo listmodel:

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

Puoi migliorare questa soluzione abbozzata nei seguenti modi:

  • Non solo recupera l'elemento richiesto ma anche alcuni elementi " intorno a " esso. L'utente probabilmente scorrerà su e giù.
  • In caso di liste davvero grandi, fai <<> dimenticare quelle righe che sono lontane da quelle recuperate per ultime.
  • Utilizza una cache LRU
  • Se lo si desidera, precaricare tutti gli elementi nel thread in background.
  • Rendi ListModel un decoratore per un'implementazione desiderosa di ListModel (questo è quello che ho fatto)
  • Se hai più " big " ListModels per gli elenchi visibili contemporaneamente utilizza una coda di richiesta centrale per recuperare gli elementi mancanti.

Dai un'occhiata ai jgoodies binding . Non sono sicuro che faranno quello che vuoi (non li ho usati ... sono solo a conoscenza del progetto).

Estendi AbstractListModel , che puoi passare al costruttore JList.

Nella tua implementazione, ingrandisci le dimensioni dell'elenco di cui hai bisogno (con il valore restituito da getSize). Se i dati per quell'elemento nell'elenco non sono disponibili, restituire una riga vuota (tramite getElementAt). Quando i dati sono disponibili, chiama fireContentsChanged per le righe aggiornate.

Aha: il rendering è il problema, ma non capisco davvero perché.

Ho utilizzato TextCellRenderer menzionato nel programma FastRenderer.java dall'articolo Programmazione JList avanzata . Ma non capisco davvero perché funzioni e quali avvertimenti facciano questo ....: /

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top