Swing中的虚拟列表框

问题描述 投票:1回答:5

我正在尝试弄清楚如何在Swing中制作一个虚拟列表框(或树或轮廓)-这将是一个列表框可以从数据库中显示大型结果集中的“视图”而无需获取整个列表的情况。结果集的内容;它需要带给我的是一个提示,即将需要显示项目N1-N2,因此我可以提取它们,并请求项目N的内容。

我知道如何在Win32(ListView + LVS_OWNERDATA)和XUL(custom treeview)中执行此操作,但我找到了SWT的东西,但没有找到Swing。

有什么建议吗?


更新:啊哈,我不明白在搜索引擎中要寻找什么,并且这些教程似乎也没有将其称为“虚拟列表框”或使用该想法。我找到了可以开始的good tutorial,并且Sun tutorials之一似乎也不错。

这是我的示例程序,它以我期望的方式运行... 例外似乎列表框在我的AbstractListModel中查询all行,而不仅是可​​见行。对于一百万行的虚拟表,这是不切实际的。我怎样才能解决这个问题? (编辑:似乎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)

更新:我尝试使用Advanced JList Programming article中的某些FastRenderer.java代码,并对其进行了修复。但是事实证明它根本不是渲染器!一行代码解决了我的问题,但我不明白为什么:

list1.setPrototypeCellValue(list1.getModel().getElementAt(0));

java swing list virtual
5个回答
1
投票
我怀疑访问整个模型的原因可能与列表大小的计算有关。

您可以尝试在模型的getElementAt()方法中添加一些断点。我建议您这样做:

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

100常量是一个值

3
投票
问题是,即使使用智能预取,您也不能保证所有需要的可见行都在需要时被预取。

我将草拟一个解决方案,该解决方案在项目中曾经使用过,并且效果非常好。

我的解决方案是使ListModel会为缺少的行返回一个存根,告诉用户该项目正在加载。 (您可以通过自定义ListCellRenderer来特别增强存根来增强视觉体验)。另外,使ListModel排队以获取丢失的行的请求。 ListModel将必须产生一个线程,该线程读取队列并获取丢失的行。提取一行后,对提取的行调用fireContentsChanges。您还可以在列表模型中使用执行器:

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,请使用中央请求队列来获取丢失的项目。

1
投票
看看jgoodies bindings。我不确定他们会做您想要的事情(我没有使用过它们……我只是知道该项目)。

1
投票
扩展AbstractListModel,您可以将其传递给JList构造函数。

在您的实现中,使列表大小与所需的大小一样大(使用getSize返回的值)。如果列表中该项目的数据不可用,请返回空白行(通过getElementAt)。当数据可用时,为更新的行调用fireContentsChanged


0
投票
啊哈:渲染是问题所在,但我不太明白为什么。

我使用文章Advanced JList Programming中FastRenderer.java程序中提到的TextCellRenderer。但是我真的不明白为什么行得通,以及这样做的警告.... ::]]

© www.soinside.com 2019 - 2024. All rights reserved.