web-dev-qa-db-fra.com

Comment obtenez-vous JavaFX ListView à la hauteur de ses éléments?

Si je crée un ListView:

new ListView<>(FXCollections.observableArrayList("1", "2", "3"))

Je m'attendrais à ce qu'il crée un ListView avec 3 lignes. Mais ce n'est pas le cas. Il crée un ListView de 17 lignes environ. Y a-t-il un moyen de dire à ListView de toujours avoir la hauteur, de sorte que tous les éléments qui y figurent sont toujours affichés, mais aucune ligne vide?

La largeur automatique serait également utile, elle est donc toujours aussi large que la rangée la plus large.

Un des buts de ceci est qu’il puisse alors être utilisé dans un ScrollPane. Je sais qu'il a ses propres barres de défilement, mais elles n'offrent pas un contrôle suffisant.

21
mentics

Malheureusement, il n’existe pas de propriété Nice, de taille propre, d’une liste ObservableList à laquelle nous pouvons nous lier. Au lieu de cela, c'est faisable en ajoutant un ListChangeListener à la liste, du moins, c'est ce que j'ai fait par le passé. Par défaut, la taille de chaque ligne doit être de 24 pixels et nous avons besoin d'un pixel supplémentaire en haut et en bas pour les bords du ListView. Sinon, la barre de défilement de ListView est toujours affichée. Premièrement, nous allons créer ObservableList et ListView, puis définir la hauteur initiale de ListView:

/*
 * Each row in a ListView should be 24px tall.  Also, we have to add an extra
 * two px to account for the borders of the ListView.
 */
final int ROW_HEIGHT = 24;
final ObservableList items = FXCollections.observableArrayList("1", "2", "3");
final ListView list = new ListView(items);

// This sets the initial height of the ListView:
list.setPrefHeight(items().size() * ROW_HEIGHT + 2);

Nous devons maintenant ajouter un ListChangeListener à ObservableList. Lorsque la liste change, nous modifions simplement la hauteur définie du ListView pour correspondre au nouveau nombre de lignes. Si nous savons que nous n'allons jamais ajouter ou supprimer d'éléments de la liste Observable qui sauvegarde le ListView, nous pouvons exclure le programme d'écoute. Encore une fois, la hauteur correspond au nombre de lignes multiplié par la hauteur par ligne plus deux pixels supplémentaires pour les bordures:

/*
 * This listener will resize the ListView when items are added or removed
 * from the ObservableList that is backing the ListView:
 */
items.addListener(new ListChangeListener() {
    @Override
    public void onChanger(ListChangeListener.Change change) {
        list.setPrefHeight(items.size() * ROW_HEIGHT + 2);
    }
});

Références: Documentation JavaFX ListView , Documentation JavaFX ObservableList

7
Paul Marshall

Je viens de découvrir que la réponse de Paul Marshall peut être réduite à une seule ligne à l'aide de Bindings.size qui crée une propriété numérique jfx représentant la taille d'une ObservableList:

listView.prefHeightProperty().bind(Bindings.size(itemListProperty).multiply(LIST_CELL_HEIGHT));

La taille des cellules de la liste doit malheureusement toujours être codée en dur, autant que je sache.

5
Alexandre Mazari

Recherchez-vous cela:

.list-cell:empty {
    -fx-opacity: 0;
}

Cela cachera les cellules vides.

3
tonimaroni

J'ai trouvé une solution relativement facile, bien que légèrement légèrement hacky, qui fonctionne en supposant que toutes les cellules non vides ont la même hauteur: au lieu d'analyser css ou quelque chose du genre, ajoutez un InvalidationListener à votre listView.getItems(); la première fois que votre liste d'éléments devient non vide, vous parcourez récursivement les enfants ListViews jusqu'à ce que vous trouviez une instance de ListCell!cell.isEmpty() et que vous stockiez la valeur de cell.getHeight(). Veillez à envelopper le code dans une Platform.runLater() afin qu’il soit exécuté une fois la mise en page ListView terminée. Une fois que vous avez cette hauteur, vous la multipliez par listView.getItems().size() et appelez listView.setMaxHeight avec la valeur résultante (toujours dans la InvalidationListener).

3
warakawa

Le système de réputation de StackOverflow m'empêche de commenter la réponse de Paul Marshall, mais je voulais ajouter à tous ceux qui la regardaient que son estimation de 24px pour les lignes est "généralement" confirmée par la documentation officielle de JavaFX - voir "fixedCellSize" à http: //download.Java.net/jdk8/jfxdocs/javafx/scene/control/ListView.html :

Généralement les cellules sont autour de 24px ...

Donc, bien que je sois d’accord pour dire que "définir la hauteur des rangées, la largeur des bordures, etc., le style ou les choses difficiles est en désordre, mais le seul moyen de le faire" peut être vrai, en commençant par une hypothèse sauvegardée. par la documentation officielle est un bon point de départ, et il semble que mes tests sur ListView (en utilisant Java 7) donnent une apparence décente.

2
Nate Simpson

Après avoir travaillé dur et basé sur la réponse @warakawa, j'ai trouvé la solution. Comme nous le savons tous, l’implémentation de Listview est source de nombreux maux de tête. C’est pourquoi j’ai écrit deux classes qui corrigent le problème de "PREF_SIZE" (qui a une constante de 400 pour la hauteur et la largeur est calculée en fonction de la hauteur). La classe d'apparence que j'ai écrite calcule la taille comme prévu et évite également la barre horizontale laide lorsque la nouvelle propriété "fillWidth" est remplacée par "true". Cette propriété entraîne une croissance horizontale des cellules autant que possible. Cordialement.

ListViewFixed.Java

package javafxapplication;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.Skin;

public class ListViewFixed<T> extends javafx.scene.control.ListView<T>
{
    // <editor-fold defaultstate="collapsed" desc="Properties">
    private final BooleanProperty fillWidth = new SimpleBooleanProperty(this, "fillWidth");

    public final BooleanProperty fillWidthProperty()
    {
        return fillWidth;
    }

    public final boolean isFillWidth()
    {
        return fillWidth.get();
    }

    public final void setFillWidth(boolean fillWidth)
    {
        this.fillWidth.set(fillWidth);
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Methods">
    @Override
    protected Skin createDefaultSkin()
    {
        return new ListViewFixedSkin(this);
    }
    // </editor-fold>
}

ListViewFixedSkin.Java

package javafxapplication;

import Java.util.Set;
import javafx.beans.Observable;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.Region;

public class ListViewFixedSkin extends com.Sun.javafx.scene.control.skin.ListViewSkin
{
    // <editor-fold defaultstate="collapsed" desc="Fields">
    private ListViewFixed listView;
    private ScrollBar scrollBarHorizontal;
    private ScrollBar scrollBarVertical;
    private boolean fillWidthCache;
    private double prefWidthCache;
    private Region placeholderRegion;
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Constructors">
    public ListViewFixedSkin(ListViewFixed listView)
    {
        super(listView);

        this.listView = listView;

        registerChangeListener(listView.fillWidthProperty(), "FILL_WIDTH");
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Methods">
    private void updateFillWidth()
    {
        if (scrollBarHorizontal != null && scrollBarVertical != null && fillWidthCache != listView.isFillWidth())
        {
            if (listView.isFillWidth() && !fillWidthCache)
            {
                scrollBarHorizontal.visibleProperty().addListener(this::updateCellsPrefWidth);
                scrollBarVertical.visibleProperty().addListener(this::updateCellsPrefWidth);
            }
            else
            {
                scrollBarHorizontal.visibleProperty().removeListener(this::updateCellsPrefWidth);
                scrollBarVertical.visibleProperty().removeListener(this::updateCellsPrefWidth);
            }

            fillWidthCache = listView.isFillWidth();
        }
    }

    private void updateCellsPrefWidth(Observable o)
    {
        final Insets insets = getSkinnable().getInsets();
        final double prefWidth = getSkinnable().getWidth() + insets.getLeft() + insets.getRight() - scrollBarVertical.getWidth();

        if (prefWidth != prefWidthCache)
        {
            for (int i = 0; i < flow.getCellCount(); i++)
            {
                final IndexedCell cell = flow.getCell(i);

                if (!cell.isEmpty())
                {
                    cell.setPrefWidth(prefWidth);
                }
            }

            prefWidthCache = prefWidth;
        }
    }

    private boolean showingPlaceHolder()
    {
        checkState();

        if (getItemCount() == 0)
        {
            if (placeholderRegion == null)
            {
                updatePlaceholderRegionVisibility();

                final Object obj = getChildren().get(getChildren().size() - 1);
                if (obj instanceof Node && ((Region) obj).getStyleClass().contains("placeholder"))
                {
                    placeholderRegion = (Region) obj;
                }
            }

            if (placeholderRegion != null)
            {
                return true;
            }
        }

        return false;
    }

    @Override
    protected void handleControlPropertyChanged(String p)
    {
        super.handleControlPropertyChanged(p);
        if ("FILL_WIDTH".equals(p))
        {
            updateFillWidth();
        }
    }

    @Override
    protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset)
    {
        if (showingPlaceHolder())
        {
            return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
        }
        else
        {
            double computedHeight = topInset + bottomInset;

            for (int i = 0; i < flow.getCellCount(); i++)
            {
                final IndexedCell cell = flow.getCell(i);

                if (!cell.isEmpty())
                {
                    computedHeight += cell.getHeight();
                }
            }

            return computedHeight;
        }
    }

    @Override
    protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset)
    {
        double computedWidth = 0;

        if (showingPlaceHolder())
        {
            computedWidth += placeholderRegion.getLayoutBounds().getWidth();
        }
        else
        {
            for (int i = 0; i < flow.getCellCount(); i++)
            {
                final IndexedCell cell = flow.getCell(i);

                if (!cell.isEmpty() && cell.getWidth() > computedWidth)
                {
                    computedWidth = cell.getWidth();
                }
            }

            if (scrollBarVertical != null && scrollBarVertical.isVisible())
            {
                computedWidth += scrollBarVertical.getWidth();
            }
        }

        if (computedWidth != 0)
        {
            return computedWidth + leftInset + rightInset;
        }
        else
        {
            return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset);
        }
    }

    @Override
    protected void layoutChildren(double x, double y, double w, double h)
    {
        super.layoutChildren(x, y, w, h);

        if (scrollBarHorizontal == null || scrollBarVertical == null)
        {
            final Set<Node> nodes = getSkinnable().lookupAll(".scroll-bar");

            nodes.stream().forEach((node) ->
            {
                if (node instanceof ScrollBar)
                {
                    final ScrollBar scrollBar = (ScrollBar) node;

                    if (scrollBar.getOrientation() == Orientation.HORIZONTAL)
                    {
                        scrollBarHorizontal = scrollBar;
                    }
                    else
                    {
                        scrollBarVertical = scrollBar;
                    }
                }
            });

            updateFillWidth();
        }
    }

    @Override
    public void dispose()
    {
        if (fillWidthCache)
        {
            scrollBarHorizontal.visibleProperty().removeListener(this::updateCellsPrefWidth);
            scrollBarVertical.visibleProperty().removeListener(this::updateCellsPrefWidth);
        }

        listView = null;

        super.dispose();
    }
    // </editor-fold>
}
0
Rogerio Souza

Essayez de définirPrefHeight (double) dans la sous-classe de ListCell. Par exemple dans mon code

@Override
public void updateItem(File item, boolean empty)
{
  super.updateItem(item, empty);
  if (empty) {
    setText(null);
    setGraphic(null);
  } else {
    setText(item.getName());
    setGraphic(null);
    setPrefHeight(getSize(item)/getsize(folder));
  }
}
0
Alexmelyon