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.
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.
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.
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With