Java 两个表视图的同步滚动条不能使用空表

Java 两个表视图的同步滚动条不能使用空表,java,javafx,Java,Javafx,在此使用此示例: 为求和行创建多个表非常有用。我还需要底部表格上的滚动条。但是,底部表格的滚动条不与主表格同步(首先是空内容)。有数据时,滚动条会正确同步 将数据添加到表中,然后删除数据时,请再次正确同步滚动条。所以我知道它们仍然可以与内容为空的表同步 下面是示例代码(顶部有两个按钮用于添加和清除项目) 包测试摘要; 导入java.text.Format; 导入java.time.LocalDate; 导入java.time.Month; 导入java.util.Set; 导入javafx.a

在此使用此示例:

为求和行创建多个表非常有用。我还需要底部表格上的滚动条。但是,底部表格的滚动条不与主表格同步(首先是空内容)。有数据时,滚动条会正确同步

将数据添加到表中,然后删除数据时,请再次正确同步滚动条。所以我知道它们仍然可以与内容为空的表同步

下面是示例代码(顶部有两个按钮用于添加和清除项目)

包测试摘要;
导入java.text.Format;
导入java.time.LocalDate;
导入java.time.Month;
导入java.util.Set;
导入javafx.application.application;
导入javafx.beans.property.ObjectProperty;
导入javafx.beans.property.SimpleDoubleProperty;
导入javafx.beans.property.SimpleObject属性;
导入javafx.beans.property.SimpleStringProperty;
导入javafx.collections.FXCollections;
导入javafx.collections.ObservableList;
导入javafx.event.ActionEvent;
导入javafx.geometry.Orientation;
导入javafx.geometry.Pos;
导入javafx.scene.Group;
导入javafx.scene.Node;
导入javafx.scene.scene;
导入javafx.scene.control.Button;
导入javafx.scene.control.ScrollBar;
导入javafx.scene.control.TableCell;
导入javafx.scene.control.TableColumn;
导入javafx.scene.control.TableView;
导入javafx.scene.control.cell.PropertyValueFactory;
导入javafx.scene.layout.BorderPane;
导入javafx.scene.layout.HBox;
导入javafx.scene.text.TextAlignment;
导入javafx.stage.stage;
导入javafx.util.Callback;
/**
*带有汇总表的表。汇总表是第二个表,如下所示:
*与主表同步。
*
*TODO:+始终显示主表和汇总表的竖线,
*否则,两个表的宽度将不相同+隐藏
*汇总表的水平滚动条
*
*/
公共类SummaryTableDemo扩展了应用程序
{
private TableView mainTable=new TableView();
private TableView sumTable=new TableView();
私有最终可观测列表数据
=FXCollections.observableArrayList();
//TODO:计算值
私人最终可观测sumData
=FXCollections.observableArrayList(
新SumData(“总和”,0.0,0.0,0.0),
新SumData(“最小值”、0.0、0.0、0.0),
新SumData(“最大值”、0.0、0.0、0.0)
);
最终HBox hb=新HBox();
公共静态void main(字符串[]args)
{
发射(args);
}
@凌驾
公众假期开始(阶段)
{
场景=新场景(新组());
//加载css
//scene.getStylesheets().addAll(getClass().getResource(“application.css”).toExternalForm());
stage.setTitle(“表格视图示例”);
舞台设置宽度(250);
舞台设置高度(550);
//设置表列
setupMainTableColumns();
setupSumTableColumns();
//用数据填充表格
mainTable.setItems(数据);
可汇总的集合项目(sumData);
//设置尺寸
可采高度(90);
//绑定/同步表
对于(int i=0;i
{
数据添加(新数据(LocalDate.of(2015年1月11日),40.0,50.0,60.0));
});
clearButton.setOnAction((ActionEvent c)->
{
data.clear();
});
HBox按钮BAR=新的HBox(清除按钮、添加按钮);
bp.setTop(按钮栏);
设置中心(主表);
bp.setBottom(sumTable);
//适合内容
bp.prefWidthProperty().bind(scene.widthProperty());
bp.prefHeightProperty().bind(scene.heightProperty());
((组)scene.getRoot()).getChildren().addAll(bp);
舞台场景;
stage.show();
//同步滚动条(必须在表格可见后进行)
ScrollBar mainTableHorizontalScrollBar=findScrollBar(mainTable,Orientation.HORIZONTAL);
ScrollBar sumTableHorizontalScrollBar=findScrollBar(sumTable,Orientation.HORIZONTAL);
mainTableHorizontalScrollBar.valueProperty().bindBidirectional(sumTableHorizontalScrollBar.valueProperty());
}
/**
*主表列映射。
*/
私有void setupMainTableColumns()
{
TableColumn dateCol=新的TableColumn(“日期”);
dateCol.setPrefWidth(120);
dateCol.setCellValueFactory(新属性ValueFactory(“日期”);
TableColumn value1Col=新的TableColumn(“值1”);
值1Col.setPrefWidth(90);
value1Col.setCellValueFactory(新属性ValueFactory(“value1”);
value1Col.setCellFactory(新格式化的TableCellFactory(TextAlignment.RIGHT));
TableColumn value2Col=新的TableColumn(“值2”);
值2Col.setPrefWidth(90);
value2Col.set
package testsummary;

import java.text.Format;
import java.time.LocalDate;
import java.time.Month;
import java.util.Set;

import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
import javafx.util.Callback;

/**
 * Table with a summary table. The summary table is a 2nd table which is
 * synchronized with the primary table.
 *
 * TODO: + always show vertical bars for both the primary and the summary table,
 * otherweise the width of both tables wouldn't be the same + hide the
 * horizontal scrollbar of the summary table
 *
 */
public class SummaryTableDemo extends Application
{

    private TableView<Data> mainTable = new TableView<>();
    private TableView<SumData> sumTable = new TableView<>();

    private final ObservableList<Data> data
            = FXCollections.observableArrayList();

    // TODO: calculate values
    private final ObservableList<SumData> sumData
            = FXCollections.observableArrayList(
                    new SumData("Sum", 0.0, 0.0, 0.0),
                    new SumData("Min", 0.0, 0.0, 0.0),
                    new SumData("Max", 0.0, 0.0, 0.0)
            );

    final HBox hb = new HBox();

    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage stage)
    {

        Scene scene = new Scene(new Group());

        // load css
        //  scene.getStylesheets().addAll(getClass().getResource("application.css").toExternalForm());
        stage.setTitle("Table View Sample");
        stage.setWidth(250);
        stage.setHeight(550);

        // setup table columns
        setupMainTableColumns();
        setupSumTableColumns();

        // fill tables with data
        mainTable.setItems(data);
        sumTable.setItems(sumData);

        // set dimensions
        sumTable.setPrefHeight(90);

        // bind/sync tables
        for (int i = 0; i < mainTable.getColumns().size(); i++)
        {

            TableColumn<Data, ?> mainColumn = mainTable.getColumns().get(i);
            TableColumn<SumData, ?> sumColumn = sumTable.getColumns().get(i);

            // sync column widths
            sumColumn.prefWidthProperty().bind(mainColumn.widthProperty());

            // sync visibility
            sumColumn.visibleProperty().bindBidirectional(mainColumn.visibleProperty());

        }

        // allow changing of column visibility
        //mainTable.setTableMenuButtonVisible(true);
        // hide header (variation of jewelsea's solution: http://stackoverflow.com/questions/12324464/how-to-javafx-hide-background-header-of-a-tableview)
        sumTable.getStyleClass().add("tableview-header-hidden");

        // hide horizontal scrollbar via styles
        //   sumTable.getStyleClass().add("sumtable");
        // create container
        BorderPane bp = new BorderPane();

        Button addButton = new Button("+");
        Button clearButton = new Button("X");

        addButton.setOnAction((ActionEvent c) ->
        {
            data.add(new Data(LocalDate.of(2015, Month.JANUARY, 11), 40.0, 50.0, 60.0));
        });
        clearButton.setOnAction((ActionEvent c) ->
        {
            data.clear();
        });

        HBox buttonBar = new HBox(clearButton, addButton);
        bp.setTop(buttonBar);
        bp.setCenter(mainTable);
        bp.setBottom(sumTable);

        // fit content
        bp.prefWidthProperty().bind(scene.widthProperty());
        bp.prefHeightProperty().bind(scene.heightProperty());

        ((Group) scene.getRoot()).getChildren().addAll(bp);

        stage.setScene(scene);
        stage.show();

        // synchronize scrollbars (must happen after table was made visible)
        ScrollBar mainTableHorizontalScrollBar = findScrollBar(mainTable, Orientation.HORIZONTAL);
        ScrollBar sumTableHorizontalScrollBar = findScrollBar(sumTable, Orientation.HORIZONTAL);
        mainTableHorizontalScrollBar.valueProperty().bindBidirectional(sumTableHorizontalScrollBar.valueProperty());

    }

    /**
     * Primary table column mapping.
     */
    private void setupMainTableColumns()
    {

        TableColumn<Data, LocalDate> dateCol = new TableColumn<>("Date");
        dateCol.setPrefWidth(120);
        dateCol.setCellValueFactory(new PropertyValueFactory<>("date"));

        TableColumn<Data, Double> value1Col = new TableColumn<>("Value 1");
        value1Col.setPrefWidth(90);
        value1Col.setCellValueFactory(new PropertyValueFactory<>("value1"));
        value1Col.setCellFactory(new FormattedTableCellFactory<>(TextAlignment.RIGHT));

        TableColumn<Data, Double> value2Col = new TableColumn<>("Value 2");
        value2Col.setPrefWidth(90);
        value2Col.setCellValueFactory(new PropertyValueFactory<>("value2"));
        value2Col.setCellFactory(new FormattedTableCellFactory<>(TextAlignment.RIGHT));

        TableColumn<Data, Double> value3Col = new TableColumn<>("Value 3");
        value3Col.setPrefWidth(90);
        value3Col.setCellValueFactory(new PropertyValueFactory<>("value3"));
        value3Col.setCellFactory(new FormattedTableCellFactory<>(TextAlignment.RIGHT));

        mainTable.getColumns().addAll(dateCol, value1Col, value2Col, value3Col);

    }

    /**
     * Summary table column mapping.
     */
    private void setupSumTableColumns()
    {

        TableColumn<SumData, String> textCol = new TableColumn<>("Text");
        textCol.setCellValueFactory(new PropertyValueFactory<>("text"));

        TableColumn<SumData, Double> value1Col = new TableColumn<>("Value 1");
        value1Col.setCellValueFactory(new PropertyValueFactory<>("value1"));
        value1Col.setCellFactory(new FormattedTableCellFactory<>(TextAlignment.RIGHT));

        TableColumn<SumData, Double> value2Col = new TableColumn<>("Value 2");
        value2Col.setCellValueFactory(new PropertyValueFactory<>("value2"));
        value2Col.setCellFactory(new FormattedTableCellFactory<>(TextAlignment.RIGHT));

        TableColumn<SumData, Double> value3Col = new TableColumn<>("Value 3");
        value3Col.setCellValueFactory(new PropertyValueFactory<>("value3"));
        value3Col.setCellFactory(new FormattedTableCellFactory<>(TextAlignment.RIGHT));

        sumTable.getColumns().addAll(textCol, value1Col, value2Col, value3Col);

    }

    /**
     * Find the horizontal scrollbar of the given table.
     *
     * @param table
     * @return
     */
    private ScrollBar findScrollBar(TableView<?> table, Orientation orientation)
    {

        // this would be the preferred solution, but it doesn't work. it always gives back the vertical scrollbar
        //      return (ScrollBar) table.lookup(".scroll-bar:horizontal");
        //      
        // => we have to search all scrollbars and return the one with the proper orientation
        Set<Node> set = table.lookupAll(".scroll-bar");
        for (Node node : set)
        {
            ScrollBar bar = (ScrollBar) node;
            if (bar.getOrientation() == orientation)
            {
                return bar;
            }
        }

        return null;

    }

    /**
     * Data for primary table rows.
     */
    public static class Data
    {

        private final ObjectProperty<LocalDate> date;
        private final SimpleDoubleProperty value1;
        private final SimpleDoubleProperty value2;
        private final SimpleDoubleProperty value3;

        public Data(LocalDate date, double value1, double value2, double value3)
        {

            this.date = new SimpleObjectProperty<LocalDate>(date);

            this.value1 = new SimpleDoubleProperty(value1);
            this.value2 = new SimpleDoubleProperty(value2);
            this.value3 = new SimpleDoubleProperty(value3);
        }

        public final ObjectProperty<LocalDate> dateProperty()
        {
            return this.date;
        }

        public final LocalDate getDate()
        {
            return this.dateProperty().get();
        }

        public final void setDate(final LocalDate date)
        {
            this.dateProperty().set(date);
        }

        public final SimpleDoubleProperty value1Property()
        {
            return this.value1;
        }

        public final double getValue1()
        {
            return this.value1Property().get();
        }

        public final void setValue1(final double value1)
        {
            this.value1Property().set(value1);
        }

        public final SimpleDoubleProperty value2Property()
        {
            return this.value2;
        }

        public final double getValue2()
        {
            return this.value2Property().get();
        }

        public final void setValue2(final double value2)
        {
            this.value2Property().set(value2);
        }

        public final SimpleDoubleProperty value3Property()
        {
            return this.value3;
        }

        public final double getValue3()
        {
            return this.value3Property().get();
        }

        public final void setValue3(final double value3)
        {
            this.value3Property().set(value3);
        }

    }

    /**
     * Data for summary table rows.
     */
    public static class SumData
    {

        private final SimpleStringProperty text;
        private final SimpleDoubleProperty value1;
        private final SimpleDoubleProperty value2;
        private final SimpleDoubleProperty value3;

        public SumData(String text, double value1, double value2, double value3)
        {

            this.text = new SimpleStringProperty(text);

            this.value1 = new SimpleDoubleProperty(value1);
            this.value2 = new SimpleDoubleProperty(value2);
            this.value3 = new SimpleDoubleProperty(value3);
        }

        public final SimpleStringProperty textProperty()
        {
            return this.text;
        }

        public final java.lang.String getText()
        {
            return this.textProperty().get();
        }

        public final void setText(final java.lang.String text)
        {
            this.textProperty().set(text);
        }

        public final SimpleDoubleProperty value1Property()
        {
            return this.value1;
        }

        public final double getValue1()
        {
            return this.value1Property().get();
        }

        public final void setValue1(final double value1)
        {
            this.value1Property().set(value1);
        }

        public final SimpleDoubleProperty value2Property()
        {
            return this.value2;
        }

        public final double getValue2()
        {
            return this.value2Property().get();
        }

        public final void setValue2(final double value2)
        {
            this.value2Property().set(value2);
        }

        public final SimpleDoubleProperty value3Property()
        {
            return this.value3;
        }

        public final double getValue3()
        {
            return this.value3Property().get();
        }

        public final void setValue3(final double value3)
        {
            this.value3Property().set(value3);
        }

    }

    /**
     * Formatter for table cells: allows you to align table cell values
     * left/right/center
     *
     * Example for alignment form
     * http://docs.oracle.com/javafx/2/fxml_get_started/fxml_tutorial_intermediate.htm
     *
     * @param <S>
     * @param <T>
     */
    public static class FormattedTableCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>>
    {

        private TextAlignment alignment = TextAlignment.LEFT;
        private Format format;

        public FormattedTableCellFactory()
        {
        }

        public FormattedTableCellFactory(TextAlignment alignment)
        {
            this.alignment = alignment;
        }

        public TextAlignment getAlignment()
        {
            return alignment;
        }

        public void setAlignment(TextAlignment alignment)
        {
            this.alignment = alignment;
        }

        public Format getFormat()
        {
            return format;
        }

        public void setFormat(Format format)
        {
            this.format = format;
        }

        @Override
        @SuppressWarnings("unchecked")
        public TableCell<S, T> call(TableColumn<S, T> p)
        {
            TableCell<S, T> cell = new TableCell<S, T>()
            {
                @Override
                public void updateItem(Object item, boolean empty)
                {
                    if (item == getItem())
                    {
                        return;
                    }
                    super.updateItem((T) item, empty);
                    if (item == null)
                    {
                        super.setText(null);
                        super.setGraphic(null);
                    } else if (format != null)
                    {
                        super.setText(format.format(item));
                    } else if (item instanceof Node)
                    {
                        super.setText(null);
                        super.setGraphic((Node) item);
                    } else
                    {
                        super.setText(item.toString());
                        super.setGraphic(null);
                    }
                }
            };
            cell.setTextAlignment(alignment);
            switch (alignment)
            {
                case CENTER:
                    cell.setAlignment(Pos.CENTER);
                    break;
                case RIGHT:
                    cell.setAlignment(Pos.CENTER_RIGHT);
                    break;
                default:
                    cell.setAlignment(Pos.CENTER_LEFT);
                    break;
            }
            return cell;
        }
    }

}
addButton.fire();
Platform.runLater(( ) -> {
    clearButton.fire();
});
public static class TweakedTableSkin<T> extends TableViewSkin<T> {

    private boolean forceNotEmpty = false;

    ChangeListener showingListener = (src, ov, nv) -> {
        initForceNotEmpty(src);
    };

    public TweakedTableSkin(TableView<T> control) {
        super(control);

        Window window = getSkinnable().getScene().getWindow();
        if (window != null)
            window.showingProperty().addListener(showingListener);
    }

    /**
     * Overridden to force a re-layout with faked itemCount after calling
     * super if the fake flag is true.
     */
    @Override
    protected void layoutChildren(double x, double y, double w, double h) {
        super.layoutChildren(x, y, w, h);
        if (forceNotEmpty) {
            forceNotEmptyLayout();
        }
    }

    /**
     * Callback from listener installed on window's showing property.
     * Implemented to set the forceNotEmpty flag and remove the listener.
     */
    private void initForceNotEmpty(ObservableValue src) {
        forceNotEmpty = true;
        src.removeListener(showingListener);
    }

    /**
     * Enforces a layout pass on the flow with at least one row.
     * Resets the forceNotEmpty flag and triggers a second
     * layout pass with the correct count.
     */
    private void forceNotEmptyLayout() {
        if (!forceNotEmpty) return;
        updateItemCount();
        forceNotEmpty = false;
        updateItemCount();
    }

    /**
     * Overridden to return at least 1 if forceNotEmpty is true.
     */
    @Override
    protected int getItemCount() {
        int itemCount = super.getItemCount();
        if (forceNotEmpty && itemCount == 0) {
            itemCount = 1;
        }
        return itemCount;
    }

}
private TableView<Data> mainTable = new TableView<>() {

    @Override
    protected Skin<?> createDefaultSkin() {
        return new TweakedTableSkin<>(this);
    }

};