TableView:调整可见行数

Posted

技术标签:

【中文标题】TableView:调整可见行数【英文标题】:TableView: adjust number of visible rows 【发布时间】:2014-12-05 13:25:27 【问题描述】:

我正在使用这个表格在表格视图中显示数据:

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Pagination;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;

public class MainApp extends Application

    IntegerProperty intP = new SimpleIntegerProperty(5);
    AnchorPane anchor = new AnchorPane();
    Scene scene;
    ObservableList<Integer> options
        = FXCollections.observableArrayList(
            5,
            10,
            15,
            20);
    final ComboBox comboBox = new ComboBox(options);
    final ObservableList<Person> data = FXCollections.observableArrayList(
        new Person("1", "Joe", "Pesci"),
        new Person("2", "Audrey", "Hepburn"),
        new Person("3", "Gregory", "Peck"),
        new Person("4", "Cary", "Grant"),
        new Person("5", "De", "Niro"),
        new Person("6", "Katharine", "Hepburn"),
        new Person("7", "Jack", "Nicholson"),
        new Person("8", "Morgan", "Freeman"),
        new Person("9", "Elizabeth", "Taylor"),
        new Person("10", "Marcello", "Mastroianni"),
        new Person("11", "Innokenty", "Smoktunovsky"),
        new Person("12", "Sophia", "Loren"),
        new Person("13", "Alexander", "Kalyagin"),
        new Person("14", "Peter", "OToole"),
        new Person("15", "Gene", "Wilder"),
        new Person("16", "Evgeny", "Evstegneev"),
        new Person("17", "Michael", "Caine"),
        new Person("18", "Jean-Paul", "Belmondo"),
        new Person("19", " Julia", "Roberts"),
        new Person("20", "James", "Stewart"),
        new Person("21", "Sandra", "Bullock"),
        new Person("22", "Paul", "Newman"),
        new Person("23", "Oleg", "Tabakov"),
        new Person("24", "Mary", "Steenburgen"),
        new Person("25", "Jackie", "Chan"),
        new Person("26", "Rodney", "Dangerfield"),
        new Person("27", "Betty", "White"),
        new Person("28", "Eddie", "Murphy"),
        new Person("29", "Amitabh", "Bachchan"),
        new Person("30", "Nicole", "Kidman"),
        new Person("31", "Adriano", "Celentano"),
        new Person("32", "Rhonda", " Fleming's"),
        new Person("32", "Humphrey", "Bogart"));
    private Pagination pagination;

    public static void main(String[] args) throws Exception
    
        launch(args);
    

    public int itemsPerPage()
    
        return 1;
    

    public int rowsPerPage()
    
        return intP.get();
    

    public VBox createPage(int pageIndex)
    
        int lastIndex = 0;
        int displace = data.size() % rowsPerPage();
        if (displace > 0)
        
            lastIndex = data.size() / rowsPerPage();
        
        else
        
            lastIndex = data.size() / rowsPerPage() - 1;
        
        VBox box = new VBox();
        int page = pageIndex * itemsPerPage();
        for (int i = page; i < page + itemsPerPage(); i++)
        
            TableView<Person> table = new TableView<>();

            TableColumn numCol = new TableColumn("ID");
            numCol.setCellValueFactory(new PropertyValueFactory<>("num"));
            numCol.setMinWidth(20);
            TableColumn firstNameCol = new TableColumn("First Name");
            firstNameCol.setCellValueFactory(new PropertyValueFactory<>("firstName"));

            firstNameCol.setMinWidth(160);
            TableColumn lastNameCol = new TableColumn("Last Name");
            lastNameCol.setCellValueFactory(new PropertyValueFactory<>("lastName"));
            lastNameCol.setMinWidth(160);
            table.getColumns().addAll(numCol, firstNameCol, lastNameCol);
            if (lastIndex == pageIndex)
            
                table.setItems(FXCollections.observableArrayList(data.subList(pageIndex * rowsPerPage(), pageIndex * rowsPerPage() + displace)));
            
            else
            
                table.setItems(FXCollections.observableArrayList(data.subList(pageIndex * rowsPerPage(), pageIndex * rowsPerPage() + rowsPerPage())));
            

            box.getChildren().addAll(table);

        
        return box;
    

    @Override
    public void start(final Stage stage) throws Exception
    
        scene = new Scene(anchor, 450, 450);
        comboBox.valueProperty().addListener(new ChangeListener<Number>()
        
            @Override
            public void changed(ObservableValue o, Number oldVal, Number newVal)
            
                //System.out.println(newVal.intValue());
                intP.set(newVal.intValue());
                paginate();
            
        );
        paginate();
        stage.setScene(scene);
        stage.setTitle("Table pager");
        stage.show();
    

    public void paginate()
    
        pagination = new Pagination((data.size() / rowsPerPage() + 1), 0);
        //   pagination = new Pagination(20 , 0);
//        pagination.setStyle("-fx-border-color:red;");
        pagination.setPageFactory(new Callback<Integer, Node>()
        
            @Override
            public Node call(Integer pageIndex)
            
                if (pageIndex > data.size() / rowsPerPage() + 1)
                
                    return null;
                
                else
                
                    return createPage(pageIndex);
                
            
        );

        AnchorPane.setTopAnchor(pagination, 10.0);
        AnchorPane.setRightAnchor(pagination, 10.0);
        AnchorPane.setBottomAnchor(pagination, 10.0);
        AnchorPane.setLeftAnchor(pagination, 10.0);

        AnchorPane.setBottomAnchor(comboBox, 40.0);
        AnchorPane.setLeftAnchor(comboBox, 12.0);
        anchor.getChildren().clear();
        anchor.getChildren().addAll(pagination, comboBox);
    

    public static class Person
    
        private final SimpleStringProperty num;
        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;

        private Person(String id, String fName, String lName)
        
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.num = new SimpleStringProperty(id);
        

        public String getFirstName()
        
            return firstName.get();
        

        public void setFirstName(String fName)
        
            firstName.set(fName);
        

        public String getLastName()
        
            return lastName.get();
        

        public void setLastName(String fName)
        
            lastName.set(fName);
        

        public String getNum()
        
            return num.get();
        

        public void setNum(String id)
        
            num.set(id);
        
    

当我使用组合框更改行数时,只会更改表行中的数据。桌子高度没有改变。

有没有办法删除空行?

【问题讨论】:

更改 tableview 高度和删除空行是两件不同的事情。请明确点。删除行见fxexperience.com/2011/11/… 【参考方案1】:

更改 tableview 的高度和删除“空”行是两件不同的事情。请明确点。

要删除行,请参阅this tutorial。

改变高度,先设置table view的fixedCellSizeProperty,然后在绑定中使用:

table.setFixedCellSize(25);
table.prefHeightProperty().bind(Bindings.size(table.getItems()).multiply(table.getFixedCellSize()).add(30));

添加 30px 是为 tableview 的标题。

【讨论】:

@PeterPenzov。将这 2 行代码放在问题上方代码中的 TableView&lt;Person&gt; table = new TableView&lt;&gt;(); 行之后。【参考方案2】:

不幸的是,TableView 不支持 visibleRowCount 的配置(您可以考虑在 fx' jira 中提交功能请求 - 不需要,already done years ago)。而且,让视图基于这样的偏好返回 prefHeight 并不是完全简单的:我们需要测量“真实”单元格的大小要求,并且以某种方式隐藏在肠道内。

只是为了好玩,尝试扩展整个协作者堆栈:

具有 visibleRowCount 属性的自定义 tableView 监听该属性的自定义皮肤会根据它计算其 prefHeight 某种访问“真实”单元高度的方法 - 唯一具有所有信息来测量它的类是 VirtualFlow。由于相关方法受到保护,因此这需要公开该方法的自定义 VirtualFlow 或反射访问。

代码:

/**
 * TableView with visibleRowCountProperty.
 * 
 * @author Jeanette Winzenburg, Berlin
 */
public class TableViewWithVisibleRowCount<T> extends TableView<T> 

    private IntegerProperty visibleRowCount = new SimpleIntegerProperty(this, "visibleRowCount", 10);
    
    
    public IntegerProperty visibleRowCountProperty() 
        return visibleRowCount;
    
    
    @Override
    protected Skin<?> createDefaultSkin() 
        return new TableViewSkinX<T>(this);
    
    
    /**
     * Skin that respects table's visibleRowCount property.
     */
    public static class TableViewSkinX<T> extends TableViewSkin<T> 

        public TableViewSkinX(TableViewWithVisibleRowCount<T> tableView) 
            super(tableView);
            registerChangeListener(tableView.visibleRowCountProperty(), "VISIBLE_ROW_COUNT");
            handleControlPropertyChanged("VISIBLE_ROW_COUNT");
        
        
        @Override
        protected void handleControlPropertyChanged(String p) 
            super.handleControlPropertyChanged(p);
            if ("VISIBLE_ROW_COUNT".equals(p)) 
                needCellsReconfigured = true;
                getSkinnable().requestFocus();
            
        

        /**
         * Returns the visibleRowCount value of the table.
         */
        private int getVisibleRowCount() 
            return ((TableViewWithVisibleRowCount<T>) getSkinnable()).visibleRowCountProperty().get();
        
        
        /**
         * Calculates and returns the pref height of the 
         * for the given number of rows.
         * 
         * If flow is of type MyFlow, queries the flow directly
         * otherwise invokes the method.
         */
        protected double getFlowPrefHeight(int rows) 
            double height = 0;
            if (flow instanceof MyFlow) 
                height = ((MyFlow) flow).getPrefLength(rows);
            
            else 
                for (int i = 0; i < rows && i < getItemCount(); i++) 
                    height += invokeFlowCellLength(i);
                
                
            return height + snappedTopInset() + snappedBottomInset();

        
        
        /**
         * Overridden to compute the sum of the flow height and header prefHeight.
         */
        @Override
        protected double computePrefHeight(double width, double topInset,
                double rightInset, double bottomInset, double leftInset) 
            // super hard-codes to 400 .. doooh
            double prefHeight = getFlowPrefHeight(getVisibleRowCount());
            return prefHeight + getTableHeaderRow().prefHeight(width);
        
        
        /**
         * Reflectively invokes protected getCellLength(i) of flow.
         * @param index the index of the cell.
         * @return the cell height of the cell at index.
         */
        protected double invokeFlowCellLength(int index) 
            double height = 1.0;
            Class<?> clazz = VirtualFlow.class;
            try 
                Method method = clazz.getDeclaredMethod("getCellLength", Integer.TYPE);
                method.setAccessible(true);
                return ((double) method.invoke(flow, index));
             catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) 
                e.printStackTrace();
            
            return height;
        

        /**
         * Overridden to return custom flow.
         */
        @Override
        protected VirtualFlow createVirtualFlow() 
            return new MyFlow();
        
        
        /**
         * Extended to expose length calculation per a given # of rows.
         */
        public static class MyFlow extends VirtualFlow 

            protected double getPrefLength(int rowsPerPage) 
                double sum = 0.0;
                int rows = rowsPerPage; //Math.min(rowsPerPage, getCellCount());
                for (int i = 0; i < rows; i++) 
                    sum += getCellLength(i);
                
                return sum;
            

        
        
    

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger.getLogger(TableViewWithVisibleRowCount.class
            .getName());

请注意,当您拥有固定单元格大小时,您可能会直接覆盖表格的 prefHeight,但不要尝试 - 没有风险,没有乐趣 :-)


更新:javafx15 中的自定义皮肤 - 基本相同,只是一些访问细节发生了变化(双向;)

/**
 * Skin that respects table's visibleRowCount property.
 */
public class TableViewSkinX<T> extends TableViewSkin<T> 

    public TableViewSkinX(TableViewWithVisibleRowCount<T> tableView) 
        super(tableView);
        registerChangeListener(tableView.visibleRowCountProperty(), e -> visibleRowCountChanged());
    
    
    private void visibleRowCountChanged() 
        getSkinnable().requestLayout();
    
    
    /**
     * Returns the visibleRowCount value of the table.
     */
    private int getVisibleRowCount() 
        return ((TableViewWithVisibleRowCount<T>) getSkinnable()).visibleRowCountProperty().get();
    
    
    /**
     * Calculates and returns the pref height of the for the given number of
     * rows.
     */
    protected double getFlowPrefHeight(int rows) 
        double height = 0;
        for (int i = 0; i < rows && i < getItemCount(); i++) 
            height += invokeFlowCellLength(i);
        
        return height + snappedTopInset() + snappedBottomInset();
    
    
    /**
     * Overridden to compute the sum of the flow height and header prefHeight.
     */
    @Override
    protected double computePrefHeight(double width, double topInset,
            double rightInset, double bottomInset, double leftInset) 
        // super hard-codes to 400 .. doooh
        double prefHeight = getFlowPrefHeight(getVisibleRowCount());
        return prefHeight + getTableHeaderRow().prefHeight(width);
    
    
    /**
     * Reflectively invokes protected getCellLength(i) of flow.
     * @param index the index of the cell.
     * @return the cell height of the cell at index.
     */
    protected double invokeFlowCellLength(int index) 
        // note: use your own utility method to reflectively access internal fields/methods
        return (double) FXUtils.invokeGetMethodValue(VirtualFlow.class, getVirtualFlow(), 
                "getCellLength", Integer.TYPE, index);
    


【讨论】:

这很好用,非常感谢您发布它。如果其他人正在尝试它 - 这是它需要的导入:import com.sun.javafx.scene.control.skin.*; import javafx.scene.control.*; import javafx.scene.control.cell.PropertyValueFactory; import javafx.beans.property.*; import java.lang.reflect.*; 注意这些导入在 Java 9 中是不同的,com.sun.javafx.scene.control.skinjavafx.scene.control.skin 替换。 (这意味着它们是 Java 9 的公共 API。)我还没有在那个版本中测试过这个解决方案。 太棒了!我只是对您的代码进行了轻微更改: requestLayout 而不是 RequestFocus 以便在更改 visibleRow 属性时更新 tableView。 @FlatEric 嗯 .. 有什么问题?除了皮肤添加监听器之外应该是相同的(regisgterXX 现在接受调用方法更新东西的消费者,只需在类似的属性监听中跟随引导)。无论如何,很快就会尝试为当前的 fx 添加更新:) 感谢您的快速响应。一个问题是needCellsReconfigured 字段不再可访问。【参考方案3】:

只需使用 css 更改空行的背景颜色

.table-row-cell:empty 
-fx-background-color: white;
-fx-border-color: white;
 

并根据组合框修改行数。

【讨论】:

简洁明了!【参考方案4】:

这是我的解决方案,为了不过度依赖table.getFixedCellSize(我们在 FX 初始化期间仍然依赖它,而 CSS 尚未计算/应用)。

注意,我们还需要添加一些像素(不明白为什么)。

public static <S> void ensureDisplayingRows(@NotNull TableView<S> table, @Null Integer rowCount) 
    DoubleProperty headerRowHeightProperty = new SimpleDoubleProperty();
    table.skinProperty().addListener((observable, oldValue, newValue) -> 
        if (!Objects.equals(oldValue, newValue)) 
            TableHeaderRow headerRow = headerRow(table);
            // TableHeaderRow not defined until CSS is applied.
            if (headerRow == null) 
                assert table.getFixedCellSize() > 0.0 : "TableView '" + table.getId() + "' is not 'fixedCellSize'."; // TODO Find a better way to control.
                headerRowHeightProperty.setValue(table.getFixedCellSize()); // Approximation. // TODO Find a better approximation.
             else 
                headerRowHeightProperty.bind(headerRow.heightProperty());
            
        
    );

    IntegerBinding itemsCountBinding = Bindings.size(table.getItems()); // NB: table.getItems() may not (yet) contains all/"new" items, may contain the "old" items.
    IntegerBinding maxRowsCountBinding = (rowCount == null) ? itemsCountBinding :
            (IntegerBinding) Bindings.min(
                    rowCount,
                    itemsCountBinding
            );
    IntegerBinding rowCountBinding = (IntegerBinding) Bindings.max(
            1, // Ensure to display at least 1 row, for JavaFX "No contents" message when table.items.isEmpty.
            maxRowsCountBinding
    );

    DoubleBinding tableHeightBinding = headerRowHeightProperty
            .add(rowCountBinding.multiply(table.getFixedCellSize()))
            .add(10); // TODO Understand why we need to add a dozen of pixels.

    table.minHeightProperty().bind(tableHeightBinding);
    table.prefHeightProperty().bind(tableHeightBinding);
    table.maxHeightProperty().bind(tableHeightBinding);


@Null
public static TableHeaderRow headerRow(@NotNull TableView<?> table) 
    TableHeaderRow tableHeaderRow = (TableHeaderRow) table.lookup("TableHeaderRow");
    return tableHeaderRow;

【讨论】:

额外的 10 个像素来自表格插图。 当然,但是对于什么图形?线?空间?为什么是 10 像素?它总是10像素吗?这个值是否取决于其他时间可能会发生变化的东西? 这取决于样式,因此您最好使用来自table.getInsets() 的顶部/底部值而不是固定值。 (参见my answer below 示例。) 工作正常,谢谢!... ... 除非显示水平滚动条:-( 有没有办法在显示滚动条时动态调整表格大小?【参考方案5】:

如果您不喜欢绑定,一个简单的方法是根据固定的单元格大小(参见Fred Danna's answer)计算所需的高度,并使用表数据上的侦听器对其进行更新。

static void setTableHeightByRowCount(TableView table, ObservableList data) 
  int rowCount = data.size();
  TableHeaderRow headerRow = (TableHeaderRow) table.lookup("TableHeaderRow");
  double tableHeight = (rowCount * table.getFixedCellSize())
    // add the insets or we'll be short by a few pixels
    + table.getInsets().getTop() + table.getInsets().getBottom()
    // header row has its own (different) height
    + (headerRow == null ? 0 : headerRow.getHeight())
    ;

  table.setMinHeight(tableHeight);
  table.setMaxHeight(tableHeight);
  table.setPrefHeight(tableHeight);

start(Stage)中,我们创建表并添加ListChangeListener

TableView<String> table = new TableView<>();
table.setFixedCellSize(24);
table.getItems().addListener((ListChangeListener<String>) c ->
  setTableHeightByRowCount(table, c.getList()));

// init scene etc...

stage.show();
table.getItems().addAll("Stacey", "Kristy", "Mary Anne", "Claudia");

注意表头行直到stage.show()之后才存在,所以最简单的做法就是等到那时才设置表数据。 或者,我们可以在建表时设置数据,然后显式调用setTableHeightByRowCount()

TableView<String> table = new TableView<>(
  FXCollections.observableArrayList("Stacey", "Kristy", "Mary Anne", "Claudia")
);

// add listener, init scene etc...

stage.show();
setTableHeightByRowCount(table, table.getItems());

【讨论】:

【参考方案6】:

有没有办法做到这一点...是的,您需要做的是在创建表格时(因为每次选择新数字时都会重新创建表格),您需要计算表格的高度table 是当前条目数,然后使用 TableView 的setPrefHeight() 属性使表更小以仅占那些行。

我玩弄了一点,但我没有找到任何快速解决方案来正确计算表格的大小,所以我没有任何代码给你,但这就是你需要做的.您还可以“样式化”表格,使其不具有交替配色方案,这会使具有数据的行下方的行看起来“空”,即使会有一些空白。

祝你好运!

【讨论】:

以上是关于TableView:调整可见行数的主要内容,如果未能解决你的问题,请参考以下文章

调整 TableView 菜单按钮

TableView 的屏幕截图不仅适用于可见单元格,而且适用于所有可用单元格

TableView Height 无法在 IOS 中调整大小

Swift:tableView 行的高度,其 tableView 单元格具有动态行数的嵌套 tableView

JavaFX - 使 TableView 高度适应行数

Xcode Swift 4 调用 self.tableView.reload() 以减少单元格行数