Вопрос

Я пытаюсь выяснить, как создать виртуальный список (или дерево или схему) в Swing - это будет тот, в котором список может отображать «представление» в большом наборе результатов из базы данных без получения всего набора результатов. содержание;все, что мне нужно, это предупредить, что элементы N1–N2 скоро должны быть отображены, чтобы я мог получить их и запросить содержимое элемента N.

Я знаю, как это сделать в Win32 (ListView + LVS_OWNERDATA) и в XUL (пользовательское древовидное представление), и я нашел кое-что для SWT, но не Свинг.

Какие-либо предложения?


обновлять:ага, я не понимал, что искать в поисковых системах, и в руководствах, похоже, это не называется «виртуальным списком» и не используется эта идея.Я нашел хороший урок с которого я могу начать, и один из Учебные пособия по солнечному свету вроде тоже нормально.

Вот мой пример программы, которая работает так, как я ожидаю... кроме похоже, что список запрашивает мою AbstractListModel для все строки, а не только видимые строки.Для виртуальной таблицы с миллионом строк это непрактично.Как я могу это исправить?(редактировать:похоже, что setPrototypeCellValue исправляет это.Но я не понимаю, почему...)

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

обновлять:

по предложению jfpoilpret я поставил точку останова в getElementData() функция.

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

Я посмотрел трассировку стека для рассматриваемого потока.Это не так уж и полезно (опубликовано ниже).Однако, судя по некоторым другим настройкам, виновниками являются fireIntervalAdded()/fireIntervalRemoved() и изменение результата getSize().FireIntervalxxxx, похоже, подсказывает Swing, что нужно проверить функцию getSize(), и если размер изменится, он сразу же выберет ВСЕ содержимое строки (или, по крайней мере, для этого он помещает запросы в очередь событий).

Там должен быть каким-то способом сказать: «Не делай этого!»но я не знаю что.

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) 

Обновлять:Я попробовал использовать часть кода FastRenderer.java из Статья о расширенном программировании JList и это исправило ситуацию.А оказывается, дело вовсе не в рендерере!Одна строка кода решила мою проблему, и я не понимаю, почему:

list1.setPrototypeCellValue(list1.getModel().getElementAt(0));
Это было полезно?

Решение

Я подозреваю, что причина доступа ко всей модели может быть связана с вычислением размера списка.

Вы можете попробовать добавить точку останова в метод getElementAt() вашей модели.Я предлагаю вам сделать это следующим образом:

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

Константа 100 — это значение < getSize(), но большее, чем исходное видимое количество строк (таким образом, у вас не будет разрыва для всех видимых строк).Когда вы войдете в эту точку останова, посмотрите, откуда была вызвана ваша модель, это может дать вам несколько подсказок.Вы можете опубликовать здесь трассировку стека, чтобы мы могли помочь вам в дальнейшем.

Другие советы

Проблема в том, что даже используя интеллектуальную предварительную выборку, вы не можете гарантировать, что все видимые строки были предварительно выбраны тогда, когда они необходимы.

Я набросаю решение, которое я однажды использовал в проекте и которое сработало очень хорошо.

Мое решение заключалось в том, чтобы ListModel возвращал заглушку для отсутствующих строк, которая сообщает пользователю, что элемент загружается.(Вы можете улучшить визуальное восприятие с помощью специального ListCellRenderer который специально визуализирует заглушку).Дополнительно сделайте ListModel поставьте в очередь запрос на получение недостающей строки.А ListModel придется создать поток, который читает очередь и извлекает недостающие строки.После того, как строка была получена, вызовите fireContentsChanges к выбранной строке.Вы также можете использовать Executor в своей модели списка:

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

Улучшить это эскизное решение можно следующими способами:

  • Извлекайте не только запрошенный элемент, но и некоторые предметы «вокруг» него.Пользователь, скорее всего, будет прокручивать вверх и вниз.
  • В случае действительно больших списков сделайте ListModel забудьте те строки, которые находятся далеко от тех, которые были выбраны последними.
  • Используйте LRU-кэш
  • При желании предварительно загрузите все элементы в фоновом потоке.
  • Сделайте ListModel декоратором для быстрой реализации ListModel (это то, что я сделал)
  • Если у вас есть несколько «больших» моделей ListModels для списков, видимых одновременно, используйте центральную очередь запросов для получения недостающих элементов.

Взгляните на привязки jgoodies.Я не уверен, что они сделают то, что вы хотите (я ими не пользовался...Я просто в курсе проекта).

Продлевать АннотацияСписокМодель, который вы можете передать в конструктор JList.

В вашей реализации сделайте размер списка настолько большим, насколько вам нужно (со значением, возвращаемым из getSize).Если данные для этого элемента в списке недоступны, верните пустую строку (через getElementAt).Когда данные будут, позвоните огоньСодержаниеИзменено для обновленных строк.

Ага:проблема в рендеринге, но я не совсем понимаю, почему.

Я использовал TextCellRenderer, упомянутый в программе FastRenderer.java из статьи Расширенное программирование JList.Но я не совсем понимаю, почему это работает и какие предостережения касаются этого....:/

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top