Pregunta

Estoy haciendo un certificado de lista personalizado. Sé que puedes tener diferentes dimensiones para cada célula individual. Pero ahora quiero tener una dimensión diferente para la celda seleccionada. De alguna manera, la Jlist está almacenando en caché la dimensión para cada celda individual la primera vez que tiene que calcular los límites para cada celda. Este es mi 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);
    }
}

En los comentarios puedes ver lo que ya he probado.

Ya he buscado bastante tiempo y encontré muchos artículos inútiles, algunos de ellos tocan el certilizador de lister/altura dinámica, pero solo funcionan porque la altura se mantiene igual para las celdas individuales. Mis alturas están cambiando, entonces, ¿cómo hago esto?

¿Fue útil?

Solución 5

Gracias a Rastislav Komara, he podido resolver esto con bastante facilidad:

He creado una clase interna que extiende BasicListui y creé un método público que se llama en ListelectionListener.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();
    }
}

El método UpdateLayOutState normalmente se activa cuando cambia la altura de JList. Lo único "loco" que estoy haciendo aquí es que mi renderizador necesita saber cuál es el índice seleccionado. Esto se debe a que el método UpdateLayOutState no utiliza el índice seleccionado en sus cálculos de altura. De alguna manera usando list.getSelectedIndex () dentro de GetListCellRendererComponent no funciona bien.

Editar:
Verifique también el Anser de Nevster y Kleopatra, se ven mucho más inteligentes, pruébalos primero ...

Otros consejos

Básicamente, hay dos aspectos del problema, ambos ubicados en el delegado de la interfaz de usuario

  • no puede configurar el renderizador a su real Estado al medir, eso es ignorar la selección (y el enfoque) por completo
  • Es notoriamente terco contra ser obligado a volver a calcular los tamaños de células en caché: no tiene una API pública para hacerlo y solo lo hace voluntariamente sobre los cambios en el modelo.

El remedio para arreglar el primero es el renderizador: implementar para ignorar el indicador seleccionado y consultar la lista para la selección real, como se describe por @Andy. En código, utilizando los componentes del 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 arreglar el segundo, un delegado de UI personalizado (como se sugiere también en otras respuestas) es una posible solución. Aunque algunos funcionan en el caso general, si se necesita admitir múltiples LAF.

Un método menos intrusivo pero ligeramente sucio para obligar a la interfaz de usuario a actualizar voluntariamente su caché es enviar un DataSeVent falso en SelectionChange:

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

Por cierto, jxlist de la Swingx El proyecto tiene un delegado de interfaz de usuario personalizado, principalmente para admitir la clasificación/filtrado, con API pública para volver a calcular el caché, luego se simplificaría el ListelectiveRener anterior (y limpie :-)

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

Acabo de implementar esta función. El problema es que el renderizador de la celda se solicita dos veces para representar una celda. En la primera ronda, todas las entradas de la lista se representan sin selección, luego las celdas seleccionadas se vuelven nuevamente utilizando la selección. Entonces, si proporciona un tamaño preferido en la primera ronda, se almacena en caché y también se usa para la segunda ronda.

El truco es ignorar el isSelected parámetro booleano en el getListCellRendererComponent y para descubrir el estado de selección verificando si list.getSelectedIndices() contiene el índice dado.

Pero, todavía tengo el problema de que después de que la lista se haga visible, la altura de los componentes renderizados a veces es demasiado grande/pequeño. Después de cambiar el tamaño de la lista de Mouse, todo está bien nuevamente. Jugué con Validate/Revalidate, Repinting, Restining of Altights en caché, pero nada funcionó. Swing a veces es un poco extraño ...

La JList no tiene capacidad para cambiar el tamaño de la celda dependiendo de la selección o lo que sea. La lista usa tamaños "almacenados en caché". Si hay un nuevo CellRenderer siempre que este tamaño se cuente y se aplique dentro de todas las celdas de la lista. Creo que la razón es el rendimiento de la lista con muchas entradas. La posible solución es escribir su propia implementación en Listui que pueda usar diferentes tamaños para celdas seleccionadas y no seleccionadas. Esto también brinda la posibilidad de ajustar el tamaño de las celdas alrededor de la selección por logaritmo u otra interpolación. Espero que tengas una gran razón por la cual hacer esto. ¡Es mucho trabajo!

He estado arrancando mi cabello sobre este estúpido problema de altura de la fila de Jlist. Tengo un renderizador de celdas que establece una altura de fila variable para cada fila; el problema es que JList mantiene un caché de las alturas.

Usando las otras respuestas, creo que he golpeado en el Santo Grial. Aquí está:

Use una versión simplificada del BasicListui creado por JAAP:

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

Luego, cuando creas una Jlist, extiéndala así:

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

Es posible que deba poner un guardia alrededor del gatillo durante la creación dependiendo de sus circunstancias.

La Jlist probablemente esté "almacenando en caché" a su renderizador de células. Intente adjuntar un ListSelectionListener y configure el renderizador nuevamente cuando se cambie la selección.

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

Esta es una solución simple:

public class VariableHeightListUI extends BasicListUI {

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

Por supuesto que necesita escribir su propia implementación de ListCellRenderer, y de acuerdo con el estado de selección diferente del elemento de lista, puede establecer una altura preferida diferente del componente devuelto.

Solo un problema debe continuar es: cuando selecciona un elemento de la lista por primera vez, no se dibuja correctamente. Pero después de eso, todos funcionan bien.

Espero que esto le pueda ayudar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top