Reputation: 5684
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
?
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>
Upvotes: 2
Views: 1702
Reputation: 49185
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 );
}
}
Upvotes: 3