Pergunta

Estou fazendo um ListCellRender personalizado. Eu sei que você pode ter dimensões diferentes para cada célula individual. Mas agora quero ter uma dimensão diferente para a célula selecionada. De alguma forma, o JLIST está armazenando em cache a dimensão de cada célula individual na primeira vez em que ele deve calcular os limites para cada célula. Este é o meu código:

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);
    }
}

Nos comentários, você pode ver o que eu já tentei.

Eu já procurei bastante e encontrei muitos artigos inúteis, alguns deles tocam a coisa da ListCellRenderer/Altura Dinâmica, mas eles só funcionam porque a altura permanece a mesma para as células individuais. Minhas alturas estão mudando, então como faço isso?

Foi útil?

Solução 5

Graças a Rastislav Komara, pude resolver isso com bastante facilidade:

Criei uma classe interna que estende o BasicListui e criei um método público que é chamado no 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();
    }
}

O método updatelayoutstate é normalmente acionado quando a altura do JLIST muda. A única coisa "insana" que estou fazendo aqui é que meu renderizador precisa saber qual é o índice selecionado. Isso ocorre porque o método UpdateLayoutState não usa o índice selecionado nos cálculos de altura. De alguma forma, usando list.getSelectedIndex () Inside getListCellRendeRerComponent não funciona bem.

Editar:
Verifique também o Anser de Nevster e Kleopatra, eles parecem muito mais inteligentes, experimente -os primeiro ...

Outras dicas

Basicamente, existem dois aspectos do problema, ambos localizados no delegado da interface do usuário

  • não consegue configurar o renderizador para o seu real Estado ao medir, isso ignora completamente a seleção (e o foco)
  • É notoriamente teimoso contra ser forçado a recalcular os tamanhos das células em cache: não tem API pública para fazê-lo e só faz voluntariamente as mudanças de modelo.

O remédio para corrigir o primeiro é de fato o renderizador: implemente para ignorar a bandeira selecionada e consultar a lista para a seleção real, conforme descrito por @andy. No código, usando os componentes do 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);

Para corrigir o segundo, um delegado de interface do usuário personalizado (conforme sugerido em outras respostas também) é uma solução possível. Embora alguns trabalhem no caso geral, se o suporte a vários LAFs for necessário.

Um método menos intrusivo, mas um pouco sujo, para forçar a interface do usuário a atualizar voluntariamente seu cache, é enviar uma lista falsa no seleção:

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);

Btw, jxlist do Swingx O Project tem um delegado de interface do usuário personalizado - principalmente para apoiar a classificação/filtragem - com a API pública para recalcular o cache, então o ListSelectionListener acima seria simplificado (e limpo :-) para

    ListSelectionListener l = new ListSelectionListener() {
        @Override
        public void valueChanged(ListSelectionEvent e) {
            ((JXList) e.getSource()).invalidateCellSizeCache();
        }
    };
    list.addListSelectionListener(l);

Acabei de implementar esse recurso. O problema é que o renderizador de células é solicitado duas vezes para renderizar uma célula. Na primeira rodada, todas as entradas da lista são renderizadas sem seleção, as células selecionadas são renderizadas novamente usando a seleção. Portanto, se você fornecer um tamanho preferido na primeira rodada, ele será armazenado em cache e também usado para a segunda rodada.

O truque é ignorar o isSelected parâmetro booleano no getListCellRendererComponent e descobrir o estado de seleção, verificando se list.getSelectedIndices() contém o índice fornecido.

Mas, ainda tenho o problema, que, depois que a lista se torna visível, a altura dos componentes renderizados às vezes é grande/pequeno. Depois de redimensionar a lista pelo mouse, tudo está bem novamente. Eu brinquei com validar/revalidar, repintar, redefinir as alturas em cache, mas nada funcionou. O balanço às vezes é um pouco estranho ...

O JLIST não tem capacidade de alterar o tamanho da célula, dependendo da seleção ou qualquer outra coisa. A lista usa tamanhos "armazenados em cache". Se houver um novo CellRender, desde que esses tamanhos sejam recontados e aplicados em todas as células na lista. Eu acho que o motivo é o desempenho da lista com muitas entradas. A solução possível é escrever a própria implementação Listui, capaz de usar tamanhos diferentes para células selecionadas e não selecionadas. Isso também traz possibilidade de ajustar o tamanho das células em torno da seleção por logaritmo ou outra interpolação. Espero que você tenha uma grande razão para fazer isso. É muito trabalho!

Eu estive rasgando meu cabelo sobre esse estúpido problema de altura da linha jlist. Eu tenho um renderizador de células que define uma altura de linha variável para cada linha - o problema é que o JLIST mantém um cache das alturas.

Usando as outras respostas, acho que atingi o Santo Graal. Aqui está:

Use uma versão simplificada do BasicListui, como criado por JAAP:

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

Então, quando você cria um jlist - estenda -o assim:

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);

Pode ser necessário colocar uma guarda ao redor do tatgerupdate durante a criação, dependendo das suas circunstâncias.

O JLIST provavelmente está "cache" seu renderizador de células. Tente anexar um ListSelectionListener e defina o renderizador novamente quando a seleção for alterada.

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

Esta é uma solução simples:

public class VariableHeightListUI extends BasicListUI {

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

Claro que você precisa escrever sua própria implementação de ListCellRenderer, e de acordo com o elemento de listar de diferentes seleções, você pode definir diferentes altura preferencial do componente retornado.

Apenas um problema precisa continuar é: quando você seleciona um elemento da lista pela primeira vez, não desenha corretamente. Mas depois disso, tudo funciona bem.

Espero que isso possa ajudá -lo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top