JavaFX的TableView
具有placeholder属性,基本上是Node
,只要它为空,就会显示在TableView
中。如果此属性设置为null(其默认值),则它显示为Label
或其他一些基于Node
的文本,其中显示“表格中没有内容。”
但是如果表格中有任何数据行,那么占位符Node
就会消失,TableView
中的整个垂直空间会被行填充,包括空行(如果没有足够的话)数据填满整个表格。
这些空行是我想要的,即使表是空的。换句话说,我根本不想使用占位符。有谁知道我怎么能这样做?
我宁愿做一些事情,就像在TableView
中放一个空洞的行,只要它实际上是空的。
答案 0 :(得分:2)
我想我找到了解决方案。这绝对不是很好,因为它是以一种不想要的方式访问API,而且我可能也在不期望地使用了visibleProperty,但是在这里你去了:
您可以尝试破解TableViewSkin。基本上这样做可以检索被黑的皮肤:
public class ModifiedTableView<E> extends TableView<E> {
@Override
protected Skin<?> createDefaultSkin() {
final TableViewSkin<E> skin = new TableViewSkin<E>(this) {
// override method here
}
// modifiy skin here
return skin;
}
}
对于TableViewSkin,您需要覆盖以下方法:
@Override
protected VirtualFlow<TableRow<E>> createVirtualFlow() {
final VirtualFlow<TableRow<E>> flow = new VirtualFlow<TableRow<E>>();
// make the 'scroll-region' always visible:
flow.visibleProperty().addListener((invalidation) -> {
flow.setVisible(true);
});
return flow;
}
对于使用反射停止显示占位符的皮肤:
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);
});
也许你可以用相同的方法改变流的可见性,使代码更短......但我认为你得到的概念
答案 1 :(得分:2)
我找到了javafx8的解决方案。它利用了非公共api,但它没有使用反射(幸运的是)。基本上,您需要设置(或替换)TableView的外观,并在方法getItemCount()
中返回非零值。像这样:
(TableView)t.setSkin(new TableViewSkin<Object>(t)
{
@Override
public int getItemCount()
{
int r = super.getItemCount();
return r == 0 ? 1 : r;
}
});
此方法还可用于在最后一项的底部添加额外的行(例如,如果要包含添加按钮)。基本上总是返回一个高于实际项目数。
虽然这是一个老问题,但希望这对某人有帮助。
答案 2 :(得分:2)
不幸的是,old issue仍未在fx9和fx10中修复。所以在fx9的背景下重新审视了黑客攻击。有变化,好的和坏的:
在挖掘的时候,我发现黑客的故障很少(注意:我没有针对fx8运行它们,所以这些可能是因为fx8与fx9的差异!)
所以我决定采用可见性强制执行:轻微故障的原因是如果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).
* <p>
*
* Updated for fx9 plus ensure to update the layout of the flow as
* needed.
*
* @author Jeanette Winzenburg, Berlin
*/
public class NoPlaceHolderTableViewSkin<T> extends TableViewSkin<T>{
private VirtualFlow<?> flowAlias;
private TableHeaderRow headerAlias;
private Parent placeholderRegionAlias;
private ChangeListener<Boolean> visibleListener = (src, ov, nv) -> visibleChanged(nv);
private ListChangeListener<Node> childrenListener = c -> childrenChanged(c);
/**
* Instantiates the skin.
* @param table the table to skin.
*/
public NoPlaceHolderTableViewSkin(TableView<T> 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<? extends Node> addedSubList) {
if (placeholderRegionAlias != null)
throw new IllegalStateException("placeholder must not be installed more than once");
List<Node> 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} <p>
*
* 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<? extends Node> 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<Person> table = new TableView<>(Person.persons()) {
// initially empty
TableView<Person> table = new TableView<>() {
@Override
protected Skin<?> createDefaultSkin() {
return new NoPlaceHolderTableViewSkin<>(this);
}
};
TableColumn<Person, String> 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());
}
答案 3 :(得分:1)
这是执行任务的棘手方法,
HBox box = new HBox();
box.setDisable(true);
for (TableColumn column : patientsTable.getColumns()) {
ListView<String> listView = new ListView<>();
listView.getItems().add("");
listView.setPrefWidth(column.getWidth());
box.getChildren().add(listView);
}
tableView.setPlaceholder(box);
答案 4 :(得分:1)
如果除了别人提供的解决方案之外,任何人仍在寻找替代解决方案,以下是我与之合作的解决方案。就我而言,这是我可以使用的最简单的方法(没有自定义外观,没有API调整以及没有像ListView这样的繁琐控件)。
使用类似于备用行颜色的自定义CSS设置StackPane。
StackPane placeHolder = new StackPane();
placeHolder.setStyle("-fx-background-color:linear-gradient(from 50px 14px to 50px 40px , repeat, #e8e8e8 49% , #f7f7f7 12% );");
tableView.setPlaceholder(placeHolder);
以下是实现的快速参考。左表带有数据,右表没有数据显示自定义占位符。
如果您还特定于显示列线,则可以按照@Shreyas Dave方法构建带有边框实现的StackPane的HBox。
HBox placeHolder = new HBox();
tableView.getColumns().forEach(tc->{
StackPane colHolder = new StackPane();
colHolder.getStyleClass().add("table-view-place-holder");
colHolder.prefWidthProperty().bind(tc.widthProperty());
placeHolder.getChildren().add(colHolder);
});
tableView.setPlaceholder(placeHolder);
CSS实现如下:
.table-view-place-holder{
-fx-background-color:linear-gradient(from 50px 14px to 50px 40px , repeat, #232A2D 49% , #3D4549 12% );
-fx-border-width: 0px 1px 0px 0px;
-fx-border-color: linear-gradient(from 50px 14px to 50px 40px , repeat, #3D4549 49% , #232a2d 12% );
}
我要求对行背景具有对比色边框颜色。使用上面的方法为占位符列添加边框颜色,我可以轻松实现这一点。