1

Swing で仮想リストボックス (またはツリーまたはアウトライン) を作成する方法を理解しようとしています。これは、結果セット全体を取得することなく、リストボックスがデータベースからの大きな結果セット内の「ビュー」を表示できるものです。コンテンツ; アイテム N1 ~ N2 をすぐに表示する必要があることを知らせるだけでよいので、それらを取得して、アイテム N の内容を尋ねることができます。

私は Win32 ( ListView + LVS_OWNERDATA ) と XUL ( custom treeview ) でそれを行う方法を知っており、 SWT用のものを見つけましたが、Swing 用のものは見つかりませんでした。

助言がありますか?


更新:ああ、検索エンジンで何を探すべきか理解できませんでした。チュートリアルでは、それを「仮想リストボックス」と呼んだり、アイデアを使用したりしていないようです。開始できる優れたチュートリアルを見つけました。また、 Sun のチュートリアルの 1 つも問題ないようです。

これが私の期待どおりに機能するサンプルプログラムです...リストボックスが、表示されている行だけでなく、すべての行についてAbstractListModelを照会しているように見えることを除いて。100 万行の仮想テーブルの場合、これは実用的ではありません。どうすればこれを修正できますか? (編集: 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) 

更新:高度な JList プログラミングの記事の FastRenderer.java コードの一部を使用してみましたが、修正されました。しかし、それはまったくレンダラーではないことが判明しました! 1 行のコードで問題が解決しましたが、その理由がわかりません。

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

5 に答える 5

3

問題は、インテリジェントなプリフェッチを使用しても、すべての可視行が必要なときにプリフェッチされたことを保証できないことです。

プロジェクトで一度使用し、非常にうまく機能したソリューションをスケッチします。

私の解決策は、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 を Decorator にします (これは私が行ったことです)。
  • 同時に表示されるリストの「大きな」ListModel が複数ある場合は、中央のリクエスト キューを使用して不足しているアイテムを取得します。
于 2009-03-06T10:16:11.270 に答える
1

モデル全体に​​アクセスする理由は、リストサイズの計算に関連しているのではないかと思います。

モデルのgetElementAt()メソッドにブレークポイントを追加してみてください。私はあなたがこのようにすることを提案します:

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

100定数は値<getSize()ですが、最初に表示される行数よりも大きくなります(これにより、表示されるすべての行が途切れることはありません)。このブレークポイントに入るときは、モデルがどこから呼び出されたかを確認してください。これにより、いくつかのヒントが得られる場合があります。スタックトレースをここに投稿して、さらにサポートしてもらうことができます。

于 2009-03-09T10:52:32.623 に答える
1

jgoodies bindingsを見てください。彼らがあなたが望むことをするかどうかはわかりません(私はそれらを使用していません...私はプロジェクトを知っているだけです)。

于 2009-03-05T22:38:52.163 に答える
1

JList コンストラクターに渡すことができるAbstractListModelを拡張します。

実装では、(getSize から返される値を使用して) 必要なだけリストのサイズを大きくします。リスト内のその項目のデータが利用できない場合は、(getElementAt を介して) 空白行を返します。データが利用可能になったら、更新された行に対してfireContentsChangedを呼び出します。

于 2009-03-06T00:28:24.803 に答える
0

Aha:レンダリングが問題ですが、その理由はよくわかりません。

記事AdvancedJListProgrammingのFastRenderer.javaプログラムで言及されているTextCellRendererを使用しました。しかし、なぜそれが機能するのか、そしてこれを行うことについての警告が何であるのか、私は本当に理解していません....:/

于 2009-03-09T14:38:27.447 に答える