如何绕过JavaFX的TableView“占位符”?

JavaFX的TableView有一个占位符属性,它基本上是一个Node ,只要它是空的就会在TableView显示。 如果此属性设置为null(其默认值),则它将显示为Label或其他基于文本的Node ,其中显示“表中没有内容”。

但是如果表中有任何数据行,则占位符Node消失,并且TableView的整个垂直空间将填充行,如果没有足够的数据填充整个表,则包括空行。

即使表是空的 ,这些空行也是我想要的。 换句话说,我根本不想使用占位符。 有谁知道我怎么做到这一点?

我宁愿不做什么事情就像在TableView放一个看起来很空的行一样。

我想我找到了解决方案。 这绝对不是很好,因为它以一种不想要的方式访问API,而且我可能也在不期望地使用了visibleProperty,但是你在这里:

您可以尝试破解TableViewSkin。 基本上这样做来检索被黑的皮肤:

 public class ModifiedTableView extends TableView { @Override protected Skin createDefaultSkin() { final TableViewSkin skin = new TableViewSkin(this) { // override method here } // modifiy skin here return skin; } } 

对于TableViewSkin,您需要覆盖以下方法:

 @Override protected VirtualFlow> createVirtualFlow() { final VirtualFlow> flow = new VirtualFlow>(); // make the 'scroll-region' always visible: flow.visibleProperty().addListener((invalidation) -> { flow.setVisible(true); }); return flow; } 

对于使用reflection停止显示占位符的皮肤:

 final Field privateFieldPlaceholderRegion = TableViewSkinBase.class.getDeclaredField("placeholderRegion"); privateFieldPlaceholderRegion.setAccessible(true); final StackPane placeholderRegion = (StackPane) privateFieldPlaceholderRegion.get(skin); // make the 'placeholder' never visible: placeholderRegion.visibleProperty().addListener((invalidation) -> { placeholderRegion.setVisible(false); }); 

也许你可以用同样的方法改变流的可见性,使代码更短……但我认为你得到的概念

我找到了javafx8的解决方案。 它利用了非公共API,但它没有使用reflection(幸运的是)。 基本上,您需要设置(或替换)TableView的外观,并在方法getItemCount()返回非零值。 像这样:

 (TableView)t.setSkin(new TableViewSkin(t) { @Override public int getItemCount() { int r = super.getItemCount(); return r == 0 ? 1 : r; } }); 

此方法还可用于在最后一项的底部添加额外的行(例如,如果要包含添加按钮)。 基本上返回总是高于实际项目数。

虽然这是一个老问题,但希望这对某人有帮助。

不幸的是, 旧问题仍然没有在fx9和fx10中修复。 所以在fx9的背景下重新审视了黑客攻击。 有变化,好的和坏的:

  • 皮肤移动到一个公共包,现在允许子类化它们而不访问隐藏的类(好)
  • 此举引入了一个不允许安装自定义VirtualFlow 的bug (在fx10中修复)
  • 将来某个时候强烈禁止对隐藏成员的反思性访问(阅读:不可能)

在挖掘的过程中,我注意到黑客攻击时出现了轻微的故障(注意:我没有针对fx8运行它们,所以这些可能是由于fx8与fx9的差异!)

  • 占位符/流的强制输入/可见性工作正常,除非在启动时使用空表(显示占位符)并在空时放大表(“新”区域看起来为空)
  • 伪造itemCount为非空可以让行在按下导航键时消失(这可能不是一个大问题,因为用户往往不会导航空表) – 这在fx9中肯定是介绍的,在fx8中运行正常

因此我决定采用可见性强制执行:轻微故障的原因是如果layoutChildren认为占位符可见,则不会布局流。 如果super没有,那么通过在布局中包含流来处理。

自定义皮肤:

 /** * TableViewSkin that doesn't show the placeholder. * The basic trick is keep the placeholder/flow in-/visible at all * times (similar to https://stackoverflow.com/a/27543830/203657). * 

* * Updated for fx9 plus ensure to update the layout of the flow as * needed. * * @author Jeanette Winzenburg, Berlin */ public class NoPlaceHolderTableViewSkin extends TableViewSkin{ private VirtualFlow flowAlias; private TableHeaderRow headerAlias; private Parent placeholderRegionAlias; private ChangeListener visibleListener = (src, ov, nv) -> visibleChanged(nv); private ListChangeListener childrenListener = c -> childrenChanged(c); /** * Instantiates the skin. * @param table the table to skin. */ public NoPlaceHolderTableViewSkin(TableView table) { super(table); flowAlias = (VirtualFlow) table.lookup(".virtual-flow"); headerAlias = (TableHeaderRow) table.lookup(".column-header-background"); // startet with a not-empty list, placeholder not yet instantiatet // so add alistener to the children until it will be added if (!installPlaceholderRegion(getChildren())) { installChildrenListener(); } } /** * Searches the given list for a Parent with style class "placeholder" and * wires its visibility handling if found. * @param addedSubList * @return true if placeholder found and installed, false otherwise. */ protected boolean installPlaceholderRegion( List addedSubList) { if (placeholderRegionAlias != null) throw new IllegalStateException("placeholder must not be installed more than once"); List parents = addedSubList.stream() .filter(e -> e.getStyleClass().contains("placeholder")) .collect(Collectors.toList()); if (!parents.isEmpty()) { placeholderRegionAlias = (Parent) parents.get(0); placeholderRegionAlias.visibleProperty().addListener(visibleListener); visibleChanged(true); return true; } return false; } protected void visibleChanged(Boolean nv) { if (nv) { flowAlias.setVisible(true); placeholderRegionAlias.setVisible(false); } } /** * Layout of flow unconditionally. * */ protected void layoutFlow(double x, double y, double width, double height) { // super didn't layout the flow if empty- do it now final double baselineOffset = getSkinnable().getLayoutBounds().getHeight() / 2; double headerHeight = headerAlias.getHeight(); y += headerHeight; double flowHeight = Math.floor(height - headerHeight); layoutInArea(flowAlias, x, y, width, flowHeight, baselineOffset, HPos.CENTER, VPos.CENTER); } /** * Returns a boolean indicating whether the flow should be layout. * This implementation returns true if table is empty. * @return */ protected boolean shouldLayoutFlow() { return getItemCount() == 0; } /** * {@inheritDoc}

* * Overridden to layout the flow always. */ @Override protected void layoutChildren(double x, double y, double width, double height) { super.layoutChildren(x, y, width, height); if (shouldLayoutFlow()) { layoutFlow(x, y, width, height); } } /** * Listener callback from children modifications. * Meant to find the placeholder when it is added. * This implementation passes all added sublists to * hasPlaceHolderRegion for search and install the * placeholder. Removes itself as listener if installed. * * @param c the change */ protected void childrenChanged(Change c) { while (c.next()) { if (c.wasAdded()) { if (installPlaceholderRegion(c.getAddedSubList())) { uninstallChildrenListener(); return; } } } } /** * Installs a ListChangeListener on the children which calls * childrenChanged on receiving change notification. * */ protected void installChildrenListener() { getChildren().addListener(childrenListener); } /** * Uninstalls a ListChangeListener on the children: */ protected void uninstallChildrenListener() { getChildren().removeListener(childrenListener); } }

用法示例:

 public class EmptyPlaceholdersInSkin extends Application { private Parent createContent() { // initially populated //TableView table = new TableView<>(Person.persons()) { // initially empty TableView table = new TableView<>() { @Override protected Skin createDefaultSkin() { return new NoPlaceHolderTableViewSkin<>(this); } }; TableColumn first = new TableColumn<>("First Name"); first.setCellValueFactory(new PropertyValueFactory<>("firstName")); table.getColumns().addAll(first); Button clear = new Button("clear"); clear.setOnAction(e -> table.getItems().clear()); clear.disableProperty().bind(Bindings.isEmpty(table.getItems())); Button fill = new Button("populate"); fill.setOnAction(e -> table.getItems().setAll(Person.persons())); fill.disableProperty().bind(Bindings.isNotEmpty(table.getItems())); BorderPane pane = new BorderPane(table); pane.setBottom(new HBox(10, clear, fill)); return pane; } @Override public void start(Stage stage) throws Exception { stage.setScene(new Scene(createContent())); stage.show(); } public static void main(String[] args) { Application.launch(args); } @SuppressWarnings("unused") private static final Logger LOG = Logger .getLogger(EmptyPlaceholdersInSkin.class.getName()); } 

这是执行任务的棘手方法,

  HBox box = new HBox(); box.setDisable(true); for (TableColumn column : patientsTable.getColumns()) { ListView listView = new ListView<>(); listView.getItems().add(""); listView.setPrefWidth(column.getWidth()); box.getChildren().add(listView); } tableView.setPlaceholder(box);