Pergunta

Eu estou tentando descobrir como fazer uma caixa de listagem virtual (ou árvore ou esquema) em Swing - este seria aquele em que a caixa de listagem pode mostrar uma "visão" dentro de um grande conjunto de resultados a partir de um banco de dados sem ter a todo o conteúdo do conjunto de resultados; tudo o que precisa para me dar é um heads-up que Itens N1 -. N2 vão necessidade de ser exibido em breve, para que eu possa buscá-los, e pedir o conteúdo do artigo N

Eu sei como fazê-lo no Win32 ( ListView + LVS_OWNERDATA ) e em XUL ( costume treeview), e eu achei algo para SWT , mas não swing.

Todas as sugestões?


atualização: aha, eu não entendia o que procurar nos motores de busca, e os tutoriais não parecem chamá-lo de "caixa de listagem virtual" ou usar a idéia. Eu encontrei um bom tutorial que eu possa começar a partir de, e um dos tutoriais Sun parece ok também.

Aqui está o meu exemplo de programa, que funciona da maneira que eu esperava ... , exceto , parece que a caixa de listagem consulta minha AbstractListModel para todas linhas, não apenas as linhas que são visível. Para uma tabela virtual milhões de linhas isso não é prático. Como posso consertar isso? (Edit: parece que correções setPrototypeCellValue isso, mas eu não entendo por que ....)

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

atualização:

por sugestão de jfpoilpret, coloquei um ponto de interrupção na função getElementData().

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

Eu olhei para o stacktrace para o segmento em questão. Não é realmente tão útil (publicado abaixo). De algum outro ajuste, no entanto, parece que os culpados são os fireIntervalAdded () / fireIntervalRemoved () ea variação no resultado de getSize (). O fireIntervalxxxx parece balanço pista para verificação do funcionamento getSize (), e se as alterações de tamanho, ele vai e refetches todo o conteúdo linha imediatamente (ou pelo menos ele coloca pedidos na fila de eventos para fazê-lo).

Não deve haver alguma maneira de dizer que não faço isso !!!! mas eu não sei o que.

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) 

Update: Eu tentei usar parte do código FastRenderer.java do Avançado JList Programação artigo e que fixa-lo. Mas acontece que não é o renderizador em tudo! Uma linha de código fixo meu problema, e eu não entendo por que:

list1.setPrototypeCellValue(list1.getModel().getElementAt(0));
Foi útil?

Solução

Eu suspeito que a razão para acessar todo o modelo pode estar relacionado com o tamanho da lista de computação.

O que você poderia tentar é adicionar algum ponto de interrupção em seu método getElementAt () modelo. Eu sugiro que você fazê-lo desta maneira:

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

O 100 constante é um valor

Outras dicas

O problema é que, mesmo usando inteligente de pré-busca que você não pode garantir que todas as linhas visíveis foram prefetched quando eles são necessários.

Eu vou esboçar uma solução que eu usei uma vez em um projeto e que funcionou muito bem.

A minha solução foi fazer um ListModel retornará um esboço para as linhas que dizem ao usuário ausente, que o item é de carregamento. (Você pode melhorar a experiência visual com uma ListCellRenderer personalizado que torna o stub especialmente). Além disso fazer a enqueue ListModel um pedido para buscar a linha em falta. O ListModel terá que gerar um segmento que lê a fila e vai buscar as linhas ausentes. Depois de uma linha foi selecionada fireContentsChanges invocar a linha obtida. Você também pode usar um Executor em você 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);
  }
}

Você pode melhorar esta solução esboçada das seguintes formas:

  • Não Apenas buscar o item solicitado, mas também alguns itens "em torno" de TI. O usuário provavelmente vai rolar para cima e para baixo.
  • Em caso de realmente grandes listas de tornar o ListModel esquecer essas linhas que estão longe dos queridos última buscados.
  • Use um LRU-cache
  • Se desejado prefetch todos os itens na discussão de fundo.
  • Faça o ListModel um decorador para uma implementação ansioso de ListModel (isto é o que eu fiz)
  • Se você tem vários "grandes" ListModels para listas visíveis ao mesmo tempo o uso de uma fila de solicitações central para buscar os itens em falta.

Dê uma olhada na JGoodies ligações . Não estou certo de que irá fazer o que quiser (eu não usei-los ... Eu sou apenas ciente do projeto).

AbstractListModel , que você pode passar para o construtor JList.

Na sua implementação, faça o seu tamanho da lista tão grande quanto você precisa (com o valor retornado de getSize). Se os dados para esse item na lista não estiver disponível, retornar uma linha em branco (via getElementAt). Quando os dados estão disponíveis, chamada fireContentsChanged para as linhas atualizadas.

Aha:. A prestação é o problema, mas eu realmente não entendo por que

Eu usei o TextCellRenderer mencionado no programa FastRenderer.java do artigo de Avançado JList programação . Mas eu realmente não entendo por que funciona e o que as advertências são sobre como fazer isso ....: /

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top