Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make ListView width fit width of its cells

I have a ListView with custom cell factory. I compute maximum over cells minWidth and set it as minWidth for entire ListView. So the ListView constrained by other regions in the layout is shrunk exactly to minWidth. But cells it holds are resized to preferred width, not minimal. As a result nodes do not fit to containing viewport and horizontal scrollbar is shown.

It's nasty. I'd like to make each cell exactly fit to ListView, and get rid of scrollbar. More precise. When ListView is reduced to its minimal width, all its cells should also be reduced to this minimal width.

I'm searching for some controlling property that switches cell resizing policy of for some hack to achieve described behavior in case there are no predefined switches.


update: added graphic example

I created SplitPane to simplify testing layouts. I can define layout restrictions by moving its separator with mouse.

First shot shows the initial problem: actual dimensions (min, max, pref) of cell nodes are not respected. The ListView always use prefWidth even if its greater than viewport width. So horizontal scroll is used when it may be avoided.

bad

After that I tried to manually set preferred width smaller. So cell now is completely visible. This is not the solution. I give this shot to prove that the cell have sufficiently small minWidth to fit inside the ListView.

ugly

The last shot is ugly too. It leaves free space that may be filled with cell content. It's is natural: if viewport is larger than preferred width and smaller than maximum width, than it should be used for resizing the cell content.

The issue may be suppressed by switching to static sizes of elements specified explicitly. But doing so means to discard all benefits of dynamic layouts: programmer would be obliged to change size constants each time new skin is applied with CSS and user would be prohibited to resize window or move separator with mouse.

And this is bad solution too. What I need - to make layout engine of javafx work. Minimal width of the ListView should be biggest minimal width of its cells. Maximum width of the ListView should be lowest maximum width of its cells. Preferred width of the ListView should biggest preferred width of its cells. And than actual width of ListView should be set by its parent (SplitPane in this example) with respects to all three layout bounds. And after that this size should be passed to cells.

Horizontal scrollbar should be displayed only in case some cell minimal width is too large to fit in viewport.

like image 476
ayvango Avatar asked Apr 26 '15 23:04

ayvango


1 Answers

Just bind the cell's prefWidthProperty to the ListView's widthProperty. I subtract a bit for borders and such.

With strings, you'll get the ellipsis ... instead of a scroll bar.

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;

public class CellSize extends Application {

    @Override
    public void start(Stage stage) {
        ObservableList<String> items = FXCollections.observableArrayList(
                "hello world", "This is a test", "this is not a drill","vroom...this IS a drill");

        ListView<String> lv = new ListView(items);
        final VBox leftBox  = new VBox(lv,new Button("Unused"));

        lv.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {

            @Override
            public ListCell<String> call(ListView<String> param) {
                ListCell lc = new ListCell<String>() {

                    @Override
                    protected void updateItem(String item, boolean empty) {
                        super.updateItem(item, empty);
                        if (empty) {
                            setText(null);
                            setGraphic(null);
                        } else {
                            setGraphic(null);
                            if (item == null) setText("null");
                            else {
                                setText(item);
                                double minWidth = 5 * item.length();
                                double maxWidth = 10 * item.length();
                                leftBox.setMinWidth(Math.max(minWidth, leftBox.getMinWidth()));
                                leftBox.setMaxWidth(maxWidth);
                            }
                        }
                    }
                };
                lc.prefWidthProperty().bind(lv.widthProperty().subtract(4));
                return lc;
            }
        });

        SplitPane root = new SplitPane();
        root.getItems().addAll(leftBox,new VBox(new Label("Hello")));
        Scene scene = new Scene(root, 300, 250);

        stage.setScene(scene);
        stage.show();

    }

    public static void main(String[] args) {
        launch(args);
    }

}
like image 193
brian Avatar answered Sep 21 '22 13:09

brian