在此使用此示例:
https://stackoverflow.com/a/30509195
非常有用,可以为累加行创建多个表。我还需要在底部表格上可见的滚动条。但是,底部表格的滚动条不与主表格同步(起初为空内容)。有数据时,滚动条将正确同步。
将数据添加到表中后,再删除数据,滚动条将再次正确同步。因此,我知道它们仍然可以与内容为空的表同步。
这是示例代码(顶部有两个按钮,用于添加和清除项目)
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;
}
}
}
答案 0 :(得分:1)
没有解决方案(可能需要在VirtualFlow和/或TableViewSkin的肠道中进行一些实际工作),但有一个肮脏的窍门:在连接滚动条后添加/删除数据
addButton.fire();
Platform.runLater(( ) -> {
clearButton.fire();
});
缺点是短暂但可察觉的闪烁...
更新
经过一番挖掘(“ geht-nicht-gibt's-nicht”-不知道英语习语,对不起),我找到了一种方法,即使没有任何物品,也可以将VirtualFlow强制进入初始布局通道:基本思想是即使没有项目,也要临时设置流的cellCount> 0。棘手的部分是在皮肤生命中的正确时间做到这一点:仅一次,早一点,但仅在正常布局发生之后。
下面的实现
仍然很脏,但更有趣:)
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;
}
}
通过使用重写的createDefaultSkin扩展TableView的用法:
private TableView<Data> mainTable = new TableView<>() {
@Override
protected Skin<?> createDefaultSkin() {
return new TweakedTableSkin<>(this);
}
};