JavaFx ListView CellLayout scaling

alex

I want to have a ListView having a custom ListCell factory (for simplicity sake) with a Label scaling to the size of the parent (ListView). My workaround solution at the moment is to pass the size of the ListView to the ListCell to set the width of each list cell's Label. This seems to be not optimal, as on resizing the ListView the ListCells are not resized (without further code like a resizeliste etc.).

How can I improve my layout to have growing Labels (to the bounds of my ListView) in each ListCell?


SSCE CODE

Main.java

    package sample;

    import javafx.application.Application;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.stage.Stage;

    public class Main extends Application {

        public static final ObservableList names = FXCollections.observableArrayList();
        public static final ObservableList data = FXCollections.observableArrayList();

        @Override
        public void start(Stage primaryStage) throws Exception {
            Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
            primaryStage.setTitle("Hello World");
            primaryStage.setScene(new Scene(root, 600, 500));
            primaryStage.show();
        }

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

    }

Controller.java

    package sample;

    import java.net.URL;
    import java.util.ResourceBundle;

    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.fxml.FXML;
    import javafx.fxml.FXMLLoader;
    import javafx.fxml.Initializable;
    import javafx.scene.Node;
    import javafx.scene.control.Labeled;
    import javafx.scene.control.ListCell;
    import javafx.scene.control.ListView;
    import javafx.scene.layout.Pane;

    public class Controller implements Initializable {
        public static final ObservableList data = FXCollections.observableArrayList();

        @FXML
        public ListView listView;

        public Controller() {

        }

        @Override
        public void initialize(URL location, ResourceBundle resources) {
            listView.setItems(data);
            listView.setCellFactory(i -> new ListView_ListCellRenderer((int) (listView.getWidth())));
            data.addAll(
                    "Test1: This text is a bit loongggggggeeerrrr, lorem ipsulm dolorems diergsadr",
                    "Test2: Less long text",
                    "Test c: Short text."
            );
        }


        class ListView_ListCellRenderer extends ListCell<String> {

            private Pane rootPane;
            private int widthListView = 0;

            public ListView_ListCellRenderer(int widthCell) {
                this.widthListView = widthCell;

            }

            private boolean loadItemLayout(String layoutName) {
                FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(layoutName));
                try {
                    rootPane = fxmlLoader.load();
                } catch (Exception exception) {
                    return false;
                }
                fxmlLoader.setController(this);
                return true;
            }

            @Override
            public void updateItem(String cellData, boolean empty) {
                super.updateItem(cellData, empty);
                setText(null);
                if (empty) {
                    clearContent();
                } else {
                    if (loadItemLayout("ListView_ListCell_Layout.fxml")) {
                        addContent(cellData);
                    } else {
                        clearContent();
                    }
                }
            }

            private void clearContent() {
                setGraphic(null);
            }

            private void addContent(String cellData) {
                if (rootPane != null) {
                    for (Node node : rootPane.getChildren()) {
                        String id = node.getId();
                        if ("LABEL_MESSAGE".equals(id)) {
                            try {
                                ((Labeled) node).setText(cellData);
                                ((Labeled) node).setPrefWidth(widthListView - 20);
                            } catch (Exception e) { }
                        }
                    }
                }
                setGraphic(rootPane);
            }
        }
    }   

sample.fxml

    <?xml version="1.0" encoding="UTF-8"?>

    <?import javafx.scene.control.*?>
    <?import java.lang.*?>
    <?import javafx.scene.layout.*?>
    <?import javafx.geometry.Insets?>
    <?import javafx.scene.layout.GridPane?>
    <?import javafx.scene.control.Button?>
    <?import javafx.scene.control.Label?>

    <GridPane alignment="center" hgap="10" vgap="10" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
       <columnConstraints>
          <ColumnConstraints hgrow="ALWAYS" />
       </columnConstraints>
       <rowConstraints>
          <RowConstraints vgrow="ALWAYS" />
       </rowConstraints>
       <children>
          <ListView fx:id="listView" prefHeight="200.0" prefWidth="200.0" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS" />
       </children>
    </GridPane>

ListView_ListCell_Layout.fxml

    <?xml version="1.0" encoding="UTF-8"?>

    <?import javafx.geometry.*?>
    <?import javafx.scene.shape.*?>
    <?import javafx.scene.text.*?>
    <?import java.lang.*?>
    <?import javafx.scene.control.*?>
    <?import javafx.scene.layout.*?>

    <GridPane fx:id="rootPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
       <children>
          <Label id="LABEL_MESSAGE" fx:id="LABEL_MESSAGE" style="-fx-background-color: #9999FF;" text="Label" wrapText="true" />
       </children>
       <columnConstraints>
          <ColumnConstraints />
          <ColumnConstraints />
       </columnConstraints>
       <rowConstraints>
          <RowConstraints />
       </rowConstraints>
    </GridPane>
Uluk Biy

The default list cell utilizes Labeled to render cell value, so ideally it would be enough to set its setWrapText(true); in overriden updateItem() method, but in this case the skin code fails to calculate the virtual flow's height.

The other option, which is to use some layout and setting it as graphic node for list cell, needs using of binding. Since almost all layout panes take into account the children preferred sizes, they just will not wrap/shrink the long text that the cell item has. Thus we need to manage the width of either the long text or the layout pane ourselves.

@Override
public void updateItem( String item, boolean empty )
{
    super.updateItem( item, empty );

    // With this, skin fails to calculate virtual flow's height
    // setWrapText( true );

    setText( null );

    if ( item != null )
    {
        // Ex-1: Manage the text width
        Text text = new Text( item );
        text.wrappingWidthProperty().bind( getListView().widthProperty().subtract( 20 ) );
        setGraphic( text );


        // Ex-2: Manage the pane width
        Label label = new Label( item );
        label.setWrapText( true );
        VBox pane = new VBox( label );
        pane.prefWidthProperty().bind( getListView().widthProperty().subtract( 20 ) );
        setGraphic( pane );
    }
    else
    {
        setGraphic( null );
    }
}

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related