4

カスタム ListCellRenderer を作成しています。個々のセルごとに異なる次元を持つことができることを私は知っています。しかし今、選択したセルに別の次元を持たせたいと思っています。どういうわけか、JList は、最初に各セルの境界を計算する必要があるときに、個々のセルのディメンションをキャッシュしています。これは私のコードです:

public class Test {

    static class Oh extends JPanel {

        public Oh() {
            setPreferredSize(new Dimension(100, 20));
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }

    static class Yeah extends JPanel {
        private boolean isSelected;

        public Yeah(boolean isSelected) {
            setPreferredSize(new Dimension(100, 100));
            this.isSelected = isSelected;
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            //setSize(100, 100); // doesn't change the bounds of the component
            //setBounds(0, 0, 100, 100); // this doesn't do any good either.
            if (isSelected) g.setColor(Color.GREEN);
            else g.setColor(Color.BLACK);
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }

    public static void main(String[] args) {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.setSize(800, 500);
        Vector<Integer> ints = new Vector<Integer>();
        for (int i = 0; i < 100; i++) {
            ints.add(i);
        }
        JList list = new JList(ints);
        list.setCellRenderer(new ListCellRenderer() {
            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                if (isSelected || ((Integer) value) == 42) return new Yeah(isSelected);
                else return new Oh();
            }
        });
        //list.setPrototypeCellValue(null);
        //list.setFixedCellHeight(-1);
        f.add(new JScrollPane(list));
        f.setVisible(true);
    }
}

コメントで、私がすでに試したことを確認できます。

私はすでにかなり長い間検索しており、多くの役に立たない記事を見つけました。そのうちのいくつかは ListCellRenderer/動的な高さに触れていますが、個々のセルの高さが同じままであるためのみ機能します。身長が変わるのですが、どうすればいいですか?

4

7 に答える 7

8

基本的に、問題には 2 つの側面があり、どちらも ui デリゲートにあります。

  • 測定時にレンダラーを実際の状態に構成できません。つまり、選択 (およびフォーカス) を完全に無視します。
  • キャッシュされたセル サイズの再計算を強制されることに対して頑固であることで有名です。そうするための公開 API はなく、モデルの変更時に自発的にのみ実行します。

最初のものを修正するための救済策は、実際にはレンダラーです。@Andy によって概説されているように、指定された選択されたフラグを無視し、実際の選択のリストをクエリするように実装します。コードで、OP のコンポーネントを使用する

ListCellRenderer renderer = new ListCellRenderer() {
    Yeah yeah = new Yeah(false);
    Oh oh = new Oh();

    @Override
    public Component getListCellRendererComponent(JList list,
            Object value, int index, boolean isSelected,
            boolean cellHasFocus) {
        // ignore the given selection index, query the list instead
        if (list != null) {
            isSelected = list.isSelectedIndex(index);
        }
        if (isSelected || ((Integer) value) == 42) {
            yeah.isSelected = isSelected;
            return yeah;

        }
        return oh;
    }
};
list.setCellRenderer(renderer);

2番目を修正するには、カスタムUIデリゲート(他の回答でも提案されているように)が可能な解決策です。複数の LAF をサポートする必要がある場合は、一般的なケースで機能するものもあります。

UI に自発的にキャッシュを更新させるための、あまり邪魔にならないが少し汚い方法は、selectionChange で偽の ListDataEvent を送信することです。

ListSelectionListener l = new ListSelectionListener() {
    ListDataEvent fake = new ListDataEvent(list, ListDataEvent.CONTENTS_CHANGED, -1, -1);
    @Override
    public void valueChanged(ListSelectionEvent e) {
        JList list = (JList) e.getSource();
        ListDataListener[] listeners = ((AbstractListModel) list.getModel())
                .getListDataListeners();
        for (ListDataListener l : listeners) {
            if (l.getClass().getName().contains("ListUI")) {
                l.contentsChanged(fake);
                break;
            }
        }
    }
};
list.addListSelectionListener(l);

ところで、SwingXプロジェクトの JXList には、主に並べ替え/フィルタリングをサポートするためのカスタム ui デリゲートがあり、キャッシュを再計算するためのパブリック API を使用すると、上記の ListSelectionListener は単純化され (そしてクリーン :-)、

    ListSelectionListener l = new ListSelectionListener() {
        @Override
        public void valueChanged(ListSelectionEvent e) {
            ((JXList) e.getSource()).invalidateCellSizeCache();
        }
    };
    list.addListSelectionListener(l);
于 2012-11-20T12:51:03.057 に答える
5

この機能を実装したばかりです。問題は、セル レンダラーがセルをレンダリングするために 2 回要求されることです。最初のラウンドでは、すべてのリスト エントリが選択なしでレンダリングされ、選択されたセルが再び選択を使用してレンダリングされます。したがって、最初のラウンドで優先サイズを指定すると、それがキャッシュされ、2 回目のラウンドでも使用されます。

isSelectedトリックは、のブール値パラメーターを無視し、指定されたインデックスが含まれているgetListCellRendererComponentかどうかを確認して選択状態を把握することです。list.getSelectedIndices()

しかし、リストが表示された後、レンダリングされたコンポーネントの高さが時々大きい/小さいという問題があります。マウスでリストのサイズを変更すると、すべてが正常に戻ります。キャッシュされた高さの検証/再検証、再描画、リセットをいじりましたが、何も機能しませんでした。スイングは時々少し変です...

于 2012-11-20T10:49:02.203 に答える
4

JList には、選択などに応じてセルのサイズを変更する機能がありません。リストは「キャッシュされた」サイズを使用します。新しい cellRenderer が提供されている場合、このサイズが再計算され、リスト内のすべてのセルに適用されます。その理由は、エントリ数の多いリストに対するパフォーマンスだと思います。可能な解決策は、選択されたセルと選択されていないセルに異なるサイズを使用できる独自の ListUI 実装を作成することです。これにより、対数またはその他の補間によって選択範囲の周囲のセルのサイズを調整する可能性ももたらされます。これを行う大きな理由があることを願っています。大変な作業です!

于 2009-08-16T08:04:26.523 に答える
2

私は、この愚かな JList の行の高さの問題について頭を悩ませてきました。行ごとに可変の行の高さを設定するセルレンダラーがあります-問題は、JListが高さのキャッシュを保持していることです。

他の答えを使用して、私は聖杯を打ったと思います。ここにあります:

Jaap によって作成された BasicListUI の簡略化されたバージョンを使用します。

public class BetterListUI extends BasicListUI {
    public void triggerUpdate() {
        updateLayoutState();
    }
}

次に、JList を作成するときに、次のように拡張します。

betterListUI = new BetterListUI();
myJList = new JList() {
    @Override
    public void repaint(long tm, int x, int y, int width, int height) {
        betterListUI.triggerUpdate();
        super.repaint(tm, x, y, width, height);
    }
};
myJList.setUI(betterListUI);

状況によっては、作成時に triggerUpdate の周りにガードを配置する必要がある場合があります。

于 2011-01-30T07:33:40.783 に答える
1

Rastislav Komara のおかげで、これを非常に簡単に解決できました。

BasicListUI を拡張する内部クラスを作成し、ListSelectionListener.valueChanged で呼び出されるパブリック メソッドを作成しました。

private class MyRenderer implements ListCellRenderer {
    public int listSelectedIndex = -1;

    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
            boolean cellHasFocus) {
        if (index == listSelectedIndex)
            return new Yeah(isSelected);
        else
            return new Oh();
    }
}
MyRenderer lcr = new MyRenderer();
private class MyListUI extends BasicListUI {

    public void triggerUpdate() {
        lcr.listSelectedIndex = list.getSelectedIndex();
        updateLayoutState();
        list.revalidate();
    }
}

updateLayoutState メソッドは通常、JList の高さが変更されるとトリガーされます。ここで私が行っている唯一の「常軌を逸した」ことは、レンダラーが選択されたインデックスが何であるかを知る必要があるということです。これは、 updateLayoutState メソッドが高さの計算で選択されたインデックスを使用しないためです。どういうわけか、getListCellRendererComponent 内で list.getSelectedIndex() を使用するとうまく機能しません。

編集:
nevster と kleopatra による anser も確認してください。

于 2009-08-16T12:57:22.800 に答える
0

JList はおそらく、セル レンダラーを「キャッシュ」しています。ListSelectionListener をアタッチして、選択が変更されたときにレンダラーを再設定してみてください。

...
addListSelectionListener(new ListSelectionListener() {  
  public void valueChanged(ListSelectionEvent event) { 
    if(event.getValueIsAdjusting() == false) {
      list.setCellRenderer(new MyRenderer());
    }
  }
)
...
于 2009-08-16T07:10:24.187 に答える