簡體   English   中英

Java Swing:JList與ListCellRenderer選擇的項目高度不同

[英]Java Swing: JList with ListCellRenderer selected item different height

我正在制作一個自定義的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 /動態高度的東西,但它們只能起作用,因為單個細胞的高度保持不變。 我的高度在變,所以我該怎么做?

基本上,問題有兩個方面,都位於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);

要修復第二個問題,自定義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);

我剛剛實現了這個功能。 問題是,細胞渲染器被要求兩次渲染細胞。 在第一輪中,所有列表條目都在沒有選擇的情況下呈現,然后使用選擇再次呈現所選單元格。 因此,如果您在第一輪中提供首選大小,它將被緩存並用於第二輪。

訣竅是忽略getListCellRendererComponentisSelected boolean參數,並通過檢查list.getSelectedIndices()包含給定索引來確定選擇狀態。

但是,我仍有問題,在列表可見后,渲染組件的高度有時會變大/變小。 用鼠標調整列表大小后,一切都很好。 我玩了驗證/重新驗證,重新繪制,重置緩存高度,但沒有任何效果。 搖擺有時候有點奇怪......

JList無法根據選擇或其他任何方式更改單元格的大小。 該列表使用“緩存”大小。 如果提供了新的cellRenderer,則會重新計算此大小並將其應用於列表中的所有單元格中。 我認為原因是具有大量條目的列表的性能。 可能的解決方案是編寫自己的ListUI實現,該實現能夠對選定和未選擇的單元使用不同的大小。 這也帶來了通過對數或其他插值來調整選擇周圍的單元尺寸的可能性。 我希望你有一個很大的理由為什么這樣做。 這是很多工作!

關於這個愚蠢的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設置警衛。

感謝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();
    }
}

通常在JList高度更改時觸發updateLayoutState方法。 我在這里做的唯一“瘋狂”事情是我的渲染器需要知道所選索引是什么。 這是因為updateLayoutState方法在其高度計算中不使用所選索引。 以某種方式在getListCellRendererComponent中使用list.getSelectedIndex()不能很好地工作。

編輯:
檢查nevster和kleopatra的anser,他們看起來更聰明,先嘗試一下......

JList可能正在“緩存”您的單元格渲染器。 嘗試附加ListSelectionListener,並在更改選擇時再次設置渲染器。

...
addListSelectionListener(new ListSelectionListener() {  
  public void valueChanged(ListSelectionEvent event) { 
    if(event.getValueIsAdjusting() == false) {
      list.setCellRenderer(new MyRenderer());
    }
  }
)
...

這是一個簡單的解決方案:

public class VariableHeightListUI extends BasicListUI {

  @Override
  public void paint(Graphics g, JComponent c) {
    updateLayoutState();
    super.paint(g, c); 
  }
}

當然你需要編寫自己的ListCellRenderer實現,並根據list元素的不同選擇狀態,可以設置返回Component的不同首選高度。

只需要進行一個問題:當您選擇List FIRST時間的元素時,不能正確繪制。 但在那之后,一切順利。

希望這可以幫到你。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM