题
我正在尝试弄清楚如何在 Swing 中创建一个虚拟列表框(或树或大纲)——这将是列表框可以在数据库中的大型结果集中显示“视图”,而无需获取整个结果集的内容;它需要给我的只是提醒我,项目 N1 - N2 需要很快显示,这样我就可以获取它们,并询问项目 N 的内容。
我知道如何在 Win32 中执行此操作(列表视图 + LVS_OWNERDATA) 和 XUL (自定义树视图),我找到了一些东西 斯威特, ,但不是 Swing。
有什么建议么?
更新:啊哈,我不明白在搜索引擎中寻找什么,并且教程似乎没有将其称为“虚拟列表框”或使用这个想法。我找到了一个 很好的教程 我可以从其中之一开始 太阳教程 看起来也不错。
这是我的示例程序,它按照我期望的方式工作...... 除了 似乎列表框查询我的 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常量是值<!> lt; getSize()但大于初始可见行数(这样你就不会有所有可见行的中断)。 当您输入此断点时,请查看模型的调用位置,这可能会给您一些提示。您可以在此处发布堆栈跟踪,以便我们进一步帮助您。
其他提示
问题是,即使使用智能预取,也无法保证所有可见行在需要时都被预取。
我将概述一个我在项目中使用过一次并且效果非常好的解决方案。
我的解决方案是让 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 的热切实现(这就是我所做的)
- 如果您同时有多个可见列表的“大”ListModel,请使用中央请求队列来获取丢失的项目。
查看 jgoodies绑定。我不确定他们会做你想做的事(我没有用过它们......我只知道这个项目)。
扩展 AbstractListModel ,您可以将其传递给JList构造函数。
在您的实现中,使您的列表大小尽可能大(使用getSize返回的值)。如果列表中该项目的数据不可用,则返回一个空行(通过getElementAt)。当数据可用时,请调用 fireContentsChanged 。
啊哈:渲染是问题所在,但我真的不明白为什么。
我使用了文章高级JList编程。但我真的不明白为什么这样做以及关于这样做的警告......:/