【问题标题】:How do you get JavaFX ListView to be the height of its items?你如何让 JavaFX ListView 成为其项目的高度?
【发布时间】:2020-07-26 03:34:00
【问题描述】:

如果我创建一个 ListView:

new ListView<>(FXCollections.observableArrayList("1", "2", "3"))

我希望它创建一个包含 3 行的 ListView。但事实并非如此。它创建了一个大约 17 行的 ListView。有没有办法告诉 ListView 始终是高度,以便始终显示其中的任何项目但没有空白行?

让它自动宽度也很有用,所以它总是和最宽的行一样宽。

这样做的一个目的是可以在 ScrollPane 中使用它。我知道它有自己的滚动条,但它们不能提供足够的控制。

【问题讨论】:

  • 您可以使用css so that additional blank rows in a ListView are not visible。这与将 ListView 本身的大小调整为其项目的高度并不完全相同,但也许对您来说已经足够了。
  • @jewelsea 我看到了这样的例子,但这不是我需要的。
  • 根据您的要求,我建议在ScrollPane 中使用VBox 而不是ListView
  • 我也见过这样的例子。有点令人沮丧的是,ListView 问题的第一个解决方案是转储它并自己滚动。
  • taotree - 以后我建议在你的问题中总结你在问题空间上所做的先前研究的结果。

标签: javafx-2


【解决方案1】:

我刚刚发现 Paul Marshall 的答案可以使用Bindings.size 简化为单行,它创建一个表示 ObservableList 大小的数字 jfx 属性:

listView.prefHeightProperty().bind(Bindings.size(itemListProperty).multiply(LIST_CELL_HEIGHT));

遗憾的是,列表单元格的高度仍然必须是硬编码的 AFAIK。

【讨论】:

  • 什么是 itemListProperty?
  • 想通了。虽然我不得不使用 maxHeightProperty
  • 你能解释一下你的发现吗?
【解决方案2】:

不幸的是,我们没有可以绑定到 ObservableList 的漂亮、干净的大小属性。相反,通过在列表中添加一个 ListChangeListener 是可行的,至少,我过去就是这样做的。默认情况下,每行的大小应该是 24px,我们需要在 ListView 的边缘的顶部和底部添加一个额外的像素。否则,我们仍然会显示 ListView 的滚动条。首先我们将创建 ObservableList 和 ListView,并设置 ListView 的初始高度:

/*
 * Each row in a ListView should be 24px tall.  Also, we have to add an extra
 * two px to account for the borders of the ListView.
 */
final int ROW_HEIGHT = 24;
final ObservableList items = FXCollections.observableArrayList("1", "2", "3");
final ListView list = new ListView(items);

// This sets the initial height of the ListView:
list.setPrefHeight(items().size() * ROW_HEIGHT + 2);

现在我们必须向 ObservableList 添加一个 ListChangeListener。当列表发生变化时,我们只需更改 ListView 的设置高度以匹配新的行数。如果我们知道我们永远不会从支持 ListView 的 ObservableList 中添加或删除项目,那么我们可以排除监听器。同样,高度是行数乘以每行高度加上两个额外的边框像素:

/*
 * This listener will resize the ListView when items are added or removed
 * from the ObservableList that is backing the ListView:
 */
items.addListener(new ListChangeListener() {
    @Override
    public void onChanger(ListChangeListener.Change change) {
        list.setPrefHeight(items.size() * ROW_HEIGHT + 2);
    }
});

参考资料: JavaFX ListView Documentation, JavaFX ObservableList Documentation

【讨论】:

  • 将行高硬编码为 24 是不可接受的,而且关于边框的假设也不可靠。以某种方式从样式或棘手的东西中找出行高、边框宽度等是很麻烦的,但可能是唯一的方法。谢谢!
  • 为什么不list.lookup(".list-cell").getHeight()?这允许您在 css 中设置大小。
【解决方案3】:

你在寻找那个吗:

.list-cell:empty {
    -fx-opacity: 0;
}

这将隐藏空单元格。

【讨论】:

    【解决方案4】:

    我找到了一个相对简单但仍然有点 hacky 的解决方案,它在所有非空单元格具有相同高度的假设下工作:而不是解析 css 或类似的,添加一个 InvalidationListener 到您的 listView.getItems();当您的项目列表第一次变为非空时,您递归地遍历ListViews 子项,直到找到ListCell 的实例,其中!cell.isEmpty(),并存储cell.getHeight() 的值。请务必将该代码包装在 Platform.runLater() 中,以便在完成 ListView 布局后执行。获得该高度后,将其与listView.getItems().size() 相乘,并使用结果值调用listView.setMaxHeight(仍在InvalidationListener 内)。

    【讨论】:

    • 其实不用递归,只要lookup(".list-cell")就搞定了。
    【解决方案5】:

    StackOverflow 的声誉系统阻止我对 Paul Marshall 的回答发表评论,但我想为其他看到此问题的人补充一点,官方 JavaFX 文档“通常”确认了他对行的 24px 估计 - 请参阅@@的“fixedCellSize” 987654321@:

    一般单元格在 24px 左右...

    因此,虽然我同意“[f] 以某种方式从样式或棘手的东西中找出行高、边框宽度等是混乱的,但可能是唯一的方法”可能是正确的,从假设开始由官方文档支持是一个很好的起点,并且似乎在我对 ListView 的测试中产生了看起来不错的列表(使用 Java 7)。

    【讨论】:

    • 该大小可以直接设置,由样式表修改,根据外观和感觉、平台(android?)等而不同,所以无论是否存在默认值,它都不是可以指望的。
    • 对于任何具有可以修改的默认值的东西都是如此,并且不会删除首先知道默认值是什么的实用程序。
    【解决方案6】:

    在努力工作并基于@warakawa 答案后,我找到了解决方案。众所周知,Listview的实现让很多人头疼,因为这个我写了两个类来解决“PREF_SIZE”的问题(它有一个常数400的高度和宽度是根据高度计算的)。我编写的皮肤类会按照我们的预期计算大小,并且当新属性“fillWidth”更改为“true”时还可以防止丑陋的水平条,该属性使单元格尽可能水平地增长。问候。

    ListViewFixed.java

    package javafxapplication;
    
    import javafx.beans.property.BooleanProperty;
    import javafx.beans.property.SimpleBooleanProperty;
    import javafx.scene.control.Skin;
    
    public class ListViewFixed<T> extends javafx.scene.control.ListView<T>
    {
        // <editor-fold defaultstate="collapsed" desc="Properties">
        private final BooleanProperty fillWidth = new SimpleBooleanProperty(this, "fillWidth");
    
        public final BooleanProperty fillWidthProperty()
        {
            return fillWidth;
        }
    
        public final boolean isFillWidth()
        {
            return fillWidth.get();
        }
    
        public final void setFillWidth(boolean fillWidth)
        {
            this.fillWidth.set(fillWidth);
        }
        // </editor-fold>
    
        // <editor-fold defaultstate="collapsed" desc="Methods">
        @Override
        protected Skin createDefaultSkin()
        {
            return new ListViewFixedSkin(this);
        }
        // </editor-fold>
    }
    

    ListViewFixedSkin.java

    package javafxapplication;
    
    import java.util.Set;
    import javafx.beans.Observable;
    import javafx.geometry.Insets;
    import javafx.geometry.Orientation;
    import javafx.scene.Node;
    import javafx.scene.control.IndexedCell;
    import javafx.scene.control.ScrollBar;
    import javafx.scene.layout.Region;
    
    public class ListViewFixedSkin extends com.sun.javafx.scene.control.skin.ListViewSkin
    {
        // <editor-fold defaultstate="collapsed" desc="Fields">
        private ListViewFixed listView;
        private ScrollBar scrollBarHorizontal;
        private ScrollBar scrollBarVertical;
        private boolean fillWidthCache;
        private double prefWidthCache;
        private Region placeholderRegion;
        // </editor-fold>
    
        // <editor-fold defaultstate="collapsed" desc="Constructors">
        public ListViewFixedSkin(ListViewFixed listView)
        {
            super(listView);
    
            this.listView = listView;
    
            registerChangeListener(listView.fillWidthProperty(), "FILL_WIDTH");
        }
        // </editor-fold>
    
        // <editor-fold defaultstate="collapsed" desc="Methods">
        private void updateFillWidth()
        {
            if (scrollBarHorizontal != null && scrollBarVertical != null && fillWidthCache != listView.isFillWidth())
            {
                if (listView.isFillWidth() && !fillWidthCache)
                {
                    scrollBarHorizontal.visibleProperty().addListener(this::updateCellsPrefWidth);
                    scrollBarVertical.visibleProperty().addListener(this::updateCellsPrefWidth);
                }
                else
                {
                    scrollBarHorizontal.visibleProperty().removeListener(this::updateCellsPrefWidth);
                    scrollBarVertical.visibleProperty().removeListener(this::updateCellsPrefWidth);
                }
    
                fillWidthCache = listView.isFillWidth();
            }
        }
    
        private void updateCellsPrefWidth(Observable o)
        {
            final Insets insets = getSkinnable().getInsets();
            final double prefWidth = getSkinnable().getWidth() + insets.getLeft() + insets.getRight() - scrollBarVertical.getWidth();
    
            if (prefWidth != prefWidthCache)
            {
                for (int i = 0; i < flow.getCellCount(); i++)
                {
                    final IndexedCell cell = flow.getCell(i);
    
                    if (!cell.isEmpty())
                    {
                        cell.setPrefWidth(prefWidth);
                    }
                }
    
                prefWidthCache = prefWidth;
            }
        }
    
        private boolean showingPlaceHolder()
        {
            checkState();
    
            if (getItemCount() == 0)
            {
                if (placeholderRegion == null)
                {
                    updatePlaceholderRegionVisibility();
    
                    final Object obj = getChildren().get(getChildren().size() - 1);
                    if (obj instanceof Node && ((Region) obj).getStyleClass().contains("placeholder"))
                    {
                        placeholderRegion = (Region) obj;
                    }
                }
    
                if (placeholderRegion != null)
                {
                    return true;
                }
            }
    
            return false;
        }
    
        @Override
        protected void handleControlPropertyChanged(String p)
        {
            super.handleControlPropertyChanged(p);
            if ("FILL_WIDTH".equals(p))
            {
                updateFillWidth();
            }
        }
    
        @Override
        protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset)
        {
            if (showingPlaceHolder())
            {
                return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
            }
            else
            {
                double computedHeight = topInset + bottomInset;
    
                for (int i = 0; i < flow.getCellCount(); i++)
                {
                    final IndexedCell cell = flow.getCell(i);
    
                    if (!cell.isEmpty())
                    {
                        computedHeight += cell.getHeight();
                    }
                }
    
                return computedHeight;
            }
        }
    
        @Override
        protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset)
        {
            double computedWidth = 0;
    
            if (showingPlaceHolder())
            {
                computedWidth += placeholderRegion.getLayoutBounds().getWidth();
            }
            else
            {
                for (int i = 0; i < flow.getCellCount(); i++)
                {
                    final IndexedCell cell = flow.getCell(i);
    
                    if (!cell.isEmpty() && cell.getWidth() > computedWidth)
                    {
                        computedWidth = cell.getWidth();
                    }
                }
    
                if (scrollBarVertical != null && scrollBarVertical.isVisible())
                {
                    computedWidth += scrollBarVertical.getWidth();
                }
            }
    
            if (computedWidth != 0)
            {
                return computedWidth + leftInset + rightInset;
            }
            else
            {
                return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset);
            }
        }
    
        @Override
        protected void layoutChildren(double x, double y, double w, double h)
        {
            super.layoutChildren(x, y, w, h);
    
            if (scrollBarHorizontal == null || scrollBarVertical == null)
            {
                final Set<Node> nodes = getSkinnable().lookupAll(".scroll-bar");
    
                nodes.stream().forEach((node) ->
                {
                    if (node instanceof ScrollBar)
                    {
                        final ScrollBar scrollBar = (ScrollBar) node;
    
                        if (scrollBar.getOrientation() == Orientation.HORIZONTAL)
                        {
                            scrollBarHorizontal = scrollBar;
                        }
                        else
                        {
                            scrollBarVertical = scrollBar;
                        }
                    }
                });
    
                updateFillWidth();
            }
        }
    
        @Override
        public void dispose()
        {
            if (fillWidthCache)
            {
                scrollBarHorizontal.visibleProperty().removeListener(this::updateCellsPrefWidth);
                scrollBarVertical.visibleProperty().removeListener(this::updateCellsPrefWidth);
            }
    
            listView = null;
    
            super.dispose();
        }
        // </editor-fold>
    }
    

    警告 ListViewFixedSkin.java 类有一些修复工作要做。在我直接通过引用传递方法的地方,它应该先存储在变量中,然后在添加/附件/删除/分离方法中传递变量。否则会导致内存问题。

    【讨论】:

    • 您将如何在您的应用程序中使用它? (尤其是使用场景构建器)
    • 您将用作任何其他自定义组件。问候。
    【解决方案7】:

    在 ListCell 的子类中尝试 setPrefHeight(double)。例如在我的代码中

    @Override
    public void updateItem(File item, boolean empty)
    {
      super.updateItem(item, empty);
      if (empty) {
        setText(null);
        setGraphic(null);
      } else {
        setText(item.getName());
        setGraphic(null);
        setPrefHeight(getSize(item)/getsize(folder));
      }
    }
    

    【讨论】:

      【解决方案8】:

      Example Image to set ListView Height

      如果你有静态列表,可以使用ListView类setPrefHeight自带的setter方法设置列表视图高度

      ListView.setPrefHeight(140);
      

      【讨论】:

        【解决方案9】:

        我的回答基于@scribbleslims 和@Alexandre Mazari 提供的答案

        listView.setPrefHeight(listView.getItems().size() * LIST_CELL_HEIGHT);

        你必须自己定义LIST_CELL_HEIGHT

        就我而言,我使用的是 29.501。

        【讨论】:

          【解决方案10】:

          解决方案是强制 JavaFX 线程在能够计算单元格高度之前完成绘制 ListView 中的项目。

          public void loadItems(ObservableList<String> items) {
              Platform.runLater(() -> {
                  listView.setItems(items);
                  listView.layout();
                  listView.applyCss();
                          
                  double cellHeight = ((LabelCell) listView.lookup(".list-cell")).getHeight();
                  double items = listView.getItems().size();
                  listView.setPrefHeight(cellHeight * items);
              });
          } // loadItems
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-11-25
            • 1970-01-01
            • 2021-01-20
            • 1970-01-01
            • 2011-08-25
            • 2019-07-31
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多