質問
Swingで仮想リストボックス(またはツリーまたはアウトライン)を作成する方法を見つけようとしています。これは、リストボックスが結果セット全体を取得せずに、データベースから大きな結果セット内の「ビュー」を表示できるものになります。コンテンツ;必要なのは、アイテム N1 ~ N2 をすぐに表示する必要があるという警告だけです。そのため、アイテム N1 ~ N2 を取得して、アイテム N の内容を尋ねることができます。
Win32でそれを行う方法は知っています(リストビュー + LVS_OWNERDATA) および XUL (カスタムツリービュー)そして私は何かを見つけました SWT, 、しかしスイングではありません。
助言がありますか?
アップデート:ああ、私は検索エンジンで何を探せばよいのか理解できませんでした。チュートリアルではそれを「仮想リストボックス」と呼んだり、そのアイデアを使用したりしていないようです。見つけました 良いチュートリアル そこから始められるもの、そしてそのうちの 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)
アップデート:FastRenderer.java コードの一部を使用してみました。 高度な JList プログラミングの記事 それで修正されました。しかし、それはレンダラーではないことが判明しました。1 行のコードで問題は解決しましたが、その理由がわかりません。
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
キューに入れます。 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);
}
}
このスケッチしたソリューションは、次の方法で改善できます。
- リクエストされたアイテムだけでなく、いくつかのアイテムもフェッチします<!> quot; around <!> quot;それ。ユーザーはおそらく上下にスクロールします。
- リストが非常に大きい場合、最後にフェッチされた行から遠く離れた行を<=>に忘れさせます。
- LRUキャッシュを使用する
- 必要に応じて、バックグラウンドスレッドのすべてのアイテムをプリフェッチします。
- ListModelの熱心な実装のために、ListModelをデコレータにします(これが私がしたことです)
- 複数の<!> quot; big <!> quot;がある場合同時に表示可能なリストのListModelは、中央のリクエストキューを使用して、不足しているアイテムを取得します。
jgoodiesバインディングをご覧ください。私は彼らがあなたが望むことをするかどうかわかりません(私はそれらを使ったことはありません...私はプロジェクトを知っているだけです)。
Extend AbstractListModel 、 JListコンストラクターに渡すことができます。
実装では、リストサイズを必要なだけ大きくします(getSizeから返される値を使用)。リスト内のそのアイテムのデータが利用できない場合、空行を返します(getElementAtを使用)。データが利用可能になったら、 fireContentsChanged 更新された行に対して。
はは:レンダリングが問題ですが、その理由はよくわかりません。
記事高度なJListプログラミング。しかし、私はそれがなぜ機能するのか、これを行うことについての警告が何であるかを本当に理解していません....:/