如何使ListView宽度适应其单元格的宽度

8
我有一个带有自定义单元格工厂的ListView。我计算单元格最小宽度的最大值,并将其设置为整个ListView的最小宽度。因此,受布局中其他区域的限制的ListView会缩小到最小宽度。但是,它所包含的单元格被调整为首选宽度,而不是最小宽度。结果是节点不适合包含视口,水平滚动条会显示出来。
这很讨厌。我想使每个单元格完全适合ListView,并摆脱滚动条。更精确地说,当ListView缩小到其最小宽度时,所有单元格也应缩小到该最小宽度。
我正在寻找一些控制属性,可以切换单元格的调整大小策略,或者在没有预定义的开关的情况下实现所描述的行为的某些黑客。
更新:添加了图形示例
我创建了SplitPane以简化布局测试。我可以通过移动其分隔符来定义布局限制。
第一张图片显示了初始问题:不尊重单元格节点的实际尺寸(最小、最大、首选)。即使其首选宽度大于视口宽度,ListView始终使用prefWidth。因此,当可以避免时,使用水平滚动。
之后,我尝试手动将首选宽度设置得更小。因此,单元格现在完全可见。这不是解决方案。我提供这个截图来证明单元格具有足够小的minWidth以适合ListView。
最后一张照片也很丑陋。它留下了可以填充单元格内容的空白空间。这是自然的:如果视口大于首选宽度且小于最大宽度,则应用于调整单元格内容的大小。
可以通过切换到显式指定的元素的静态大小来抑制该问题。但是这样做意味着放弃动态布局的所有好处:程序员将被迫每次使用CSS应用新皮肤时更改大小常量,并且用户将无法调整窗口大小或使用鼠标移动分隔符。
这也是不好的解决方案。我所需要的是使JavaFX的布局引擎工作。ListView的最小宽度应为其所有单元格的最大最小宽度。 ListView的最大宽度应为其所有单元格的最低最大宽度。 ListView的首选宽度应为其所有单元格的最大首选宽度。然后,ListView的实际宽度应由其父级(例如SplitPane)设置,考虑到所有三个布局边界。之后,应将此大小传递给单元格。
只有在某些单元格的最小宽度太大而无法适合视口时,才应显示水平滚动条。

2
请问您能否添加您已经尝试过的代码?如果可能的话,附上一张图片。 - ItachiUchiha
我实际上是用Scala编写的。将Scala代码发布到JavaFX问题中是否合适? - ayvango
在添加了“scala”标签后,可以将其转换为Java,但可能会有不同的行为。 - Uluk Biy
1个回答

4

只需将单元格的prefWidthProperty绑定到ListView的widthProperty即可。我会减去一些边框等内容。

对于字符串,您将获得省略号...而不是滚动条。

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);
    }

}

那会不必要地增加首选宽度。父级(例如 SplitPane)可能使用首选宽度在子节点之间公平分配屏幕空间。 - ayvango
我将单元格的prefWidth设置为与ListView相同的大小,这样不会浪费空间,但它的大小是正确的,因此没有滚动条和未填充的空间。我注意到您更新了问题,我将编辑示例以添加最小值、最大值和SplitPane - brian
这会导致一个错误:索引超过了maxCellCount。请检查大小计算。我猜测这是因为一旦ListCell不再使用,绑定就没有被释放。 - kerner1000
item.prefWidthProperty().bind(listView.widthProperty().subtract(20)); 这样写会更好。 - kerner1000

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接