TableView:调整可见行数

时间:2014-10-10 11:26:50

标签: javafx javafx-2 tableview javafx-8

我使用此表在表视图中显示数据:

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

当我使用组合框更改行数时,只更改表行中的数据。表高度不变。

enter image description here

有没有办法删除空行?

6 个答案:

答案 0 :(得分:26)

更改tableview的高度并删除“空”行是两回事。请明确点。

要删除行,请参阅this tutorial

要更改高度,首先设置表格视图的fixedCellSizeProperty,然后在绑定中使用它:

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

为tableview的标题添加30px。

答案 1 :(得分:14)

不幸的是,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进行简单覆盖,但没有尝试 - 没有风险也没有乐趣: - )

答案 2 :(得分:2)

这是我的解决方案,为了不依赖于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;
}

答案 3 :(得分:0)

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

我玩了一点点,我没有找到任何快速的解决方案来正确计算桌子的大小,所以我没有任何代码给你,但那是什么你需要做的。你也可以&#39; style&#39;该表没有交替的配色方案,这会使得数据看起来空白的行下面是空的。即使会有一些空白。

祝你好运!

答案 4 :(得分:0)

只需使用css

更改空行的背景颜色
.table-row-cell:empty {
-fx-background-color: white;
-fx-border-color: white;
} 

并根据组合框修改行数。

答案 5 :(得分:0)

如果您没有结合绑定,一种简单的方法是根据固定的单元格大小计算所需的高度(参见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());