2009-03-05 76 views
1

我想弄清楚如何在Swing中創建虛擬列表框(或樹或大綱) - 這將是列表框可以在一個大型結果集中顯示「視圖」數據庫而不需要獲取整個結果集的內容;所有它需要給我的是項目N1 - N2將需要很快顯示,所以我可以取他們,並要求的內容項目N.Swing中的虛擬列表框

我知道如何做到這一點Win32(ListView + LVS_OWNERDATA)和XUL(custom treeview),我找到了一些SWT,但不是Swing。

有什麼建議嗎?


更新:啊哈,我不明白,尋找在搜索引擎什麼,&教程似乎並沒有稱之爲「虛擬列表框」或者使用的想法。我發現一個good tutorial,我可以從其中開始,並且其中Sun tutorials似乎也可以。

這裏是我的示例程序,它的工作原理我所期望的方式... 除了好像列表框查詢我的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代碼從Advanced JList Programming article和固定它。但事實證明,這不是渲染器!一行代碼解決了我的問題,我不明白爲什麼:

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

是的堆棧跟蹤是有用的:你看到它的getPreferredSize(),這匹配我原來的評論; JList計算它的首選大小,但是它需要檢查列表中的所有項目! 使用setPrototypeCellValue(),您告訴列表該值應該用於計算大小! – jfpoilpret 2009-03-09 23:13:55

+0

這就是爲什麼它解決了你的問題。 – jfpoilpret 2009-03-09 23:14:48

+0

所以它不是列表大小,它是列表中每個項目的單元格大小。 – 2009-03-10 00:22:11

回答

1

我懷疑訪問整個模型的原因可能與列表大小計算有關。

你可以嘗試的是在你的模型getElementAt()方法中添加一些斷點。我建議你這樣說:

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

的100常數的值<的getSize(),但大於行的初始可見的數字(這樣你就不必對所有可見的行休息)。 當你輸入這個斷點時,看看你的模型是從哪裏調用的,這可能會給你一些提示。您可以在此發佈堆棧跟蹤,以便我們嘗試進一步幫助您。

1

看看jgoodies bindings。我不確定他們會做你想做的事情(我沒有用過他們......我只知道這個項目)。

+0

+1,我以前沒聽說過。看起來有用。 – 2009-03-06 21:08:24

1

擴展AbstractListModel,您可以將它傳遞給JList構造函數。

在您的實現中,根據需要使用列表大小(使用從getSize返回的值)。如果列表中該項目的數據不可用,則返回一個空行(通過getElementAt)。當數據可用時,請致電fireContentsChanged獲取更新的行。

3

問題是,即使使用智能預取,您也無法保證所有可見行都在需要時被預取。

我會畫一個解決方案,我曾經在一個項目中使用過,並且工作得非常好。

我的解決方案是讓一個ListModel將返回一個缺失行的存根,告訴用戶該項目正在加載。 (您可以使用專門呈現存根的定製ListCellRenderer來增強視覺體驗)。此外,使ListModel排隊請求以獲取缺少的行。 ListModel將不得不產生一個讀取隊列並獲取缺失行的線程。在獲取一行後,調用fireContentsChanges到獲取的行。你也可以用你的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); 
    } 
} 

您可以改善以下方面,這草圖解決方案:

  • 沒有隻取所需項但也有一些項目「繞」了。用戶可能會上下滾動。
  • 如果是真正的大列表,ListModel會忘記那些遠離最後取得的行的行。
  • 使用LRU緩存
  • 如果需要預取後臺線程中的所有項目。
  • 充分利用的ListModel爲渴望實現的ListModel的裝飾(這是我做的)
  • 如果你有在同一時間看到列出使用中央請求隊列來獲取丟失物品多「大」 ListModels。
0

啊哈:渲染是問題,但我不明白爲什麼。

我使用文章Advanced JList Programming的FastRenderer.java程序中提到的TextCellRenderer。但我真的不明白爲什麼這樣做,以及這樣做的注意事項....:/