javaFX tableView中嵌入
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javaFX tableView中嵌入相关的知识,希望对你有一定的参考价值。
有哪位大神搞过javaFX表格中嵌入控件,且解决了嵌入控件后模型数据与表格数据的绑定(即修改表格数据后自动更新模型数据。) 搞了好几天了 继承tablecell类中的commitedit方法,没有用····尝试了自己重写commitedit方法··· 好像也没搞定。求指导
每一列都是一个TableColumn,我们可以直接创建也可以在JavaFX Scene Builder中创建好。TableView的数据填充,需要一个ObservableList。其中需要一个类来做数据填充。 参考技术A TableView表
TableColumn列
构建一个表主要有TableView,TableColumn,ObservableList,Bean。
添加列table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
ObservableList里面是存放的数据
table.setItems(observableList);添加数据
observableList里面一般是存放的Bean,列与Bean之间建立联系,从而获取值。
列与Bean之间建立联系:
emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));通过cell值工厂建立与Bean的联系。它这里并不需要知道你是传了什么Bean,它只需要通过“email”反射成getEmail()方法去Bean里面获得值,所以Bean属性定义的名字不需要与它相同,只需要有getEmail()方法。
Java代码
firstNameCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, String>, ObservableValue<String>>()
@Override
public ObservableValue<String> call(CellDataFeatures<Person, String> arg0)
// return new
// SimpleStringProperty(arg0.getValue(),"sd",arg0.getValue().getFirstName());
// //bean, bean的名称,值
return new SimpleStringProperty(arg0.getValue().getFirstName());
// 这样你可以不建立值与对象的映射关系。
);
arg0.getValue()等于这里的person。若是你observableList.add(list),则这arg0.getValue()等于list。
SimpleStringProperty(arg0.getValue(),"sd",arg0.getValue().getFirstName());
这里的意思既是arg0.getValue()既是你observableList.add的值,“sd”为bean取得名字,arg0.getValue().getFirstName()既是你该列想要获得的值。如果是list则arg0.getValue().get(j)则为该列的每行赋值了。
cell里面不仅只存放文字,还可以存放其它Node:
Java代码
firstNameCol.setCellFactory(new Callback<TableColumn<Person, String>, TableCell<Person, String>>()
@Override
public TableCell<Person, String> call( // 单元格内容
TableColumn<Person, String> arg0)
return new TableCell<Person, String>() @Override
protected void updateItem(final String str,boolean arg1)
super.updateItem(str, arg1);
if (arg1) setText(null);
setGraphic(null);
else setText(str);
setGraphic(new CheckBox());
);
和TreeCell使用一样,可以对cell里面弄重新构造。
lastNameCol.setCellFactory(TextFieldTableCell.forTableColumn());有一些默认的构造,就不需要自己去new TableCell了。
TableColumn设置sort的3个方法
firstNameCol.setSortNode(new Text("a")); // 默认是表头上的小图标三角形,可以改变
firstNameCol.setSortable(true); // 设置可排序
firstNameCol.setSortType(SortType.DESCENDING);设置升降序
若要在一个column中包含多个column,则可以调用TableColumn的getColumns().setAll(TableColumn...);
Java代码
firstNameColumn = new TableColumn<Person, String>("First");
firstNameColumn.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
// firstNameColumn.setCellFactory(TextFieldCellFactory.<Person>forTableColumn());
lastNameColumn = new TableColumn<Person, String>("Last");
lastNameColumn.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
// lastNameColumn.setCellFactory(TextFieldCellFactory.<Person>forTableColumn());
nameColumn = new TableColumn<Person, String>("Name");
nameColumn.getColumns().setAll(firstNameColumn, lastNameColumn);
table的单元之间有明显的横线分割,可以通过css去掉。
去掉行横线
Java代码
.table-view .table-row-cell
-fx-background-insets: 0;
若想同时去掉没有数据的竖线
Java代码
.table-row-cell:empty .table-cell
-fx-border-width: 0px;
若想对行进行操作,可以通过setRowFactory。如下面对行的双击进行操作
Java代码
tableView.setRowFactory(new Callback<TableView<T>, TableRow<T>>()
@Override
public TableRow<T> call(TableView<T> param)
return new TableRowControl();
);
class TableRowControl extends TableRow<T>
public TableRowControl()
super();
this.setOnMouseClicked(new EventHandler<MouseEvent>()
@Override
public void handle(MouseEvent event)
if (event.getButton().equals(MouseButton.PRIMARY)
&& event.getClickCount() == 2
&& TableRowControl.this.getIndex() < tableView.getItems().size())
//doSomething
);
往table中插入数据,table中的数据显示,是根据你的itemlist来的,list里面的数据排什么序,那table也就排什么序。若添加一条新数据,直接往list里面加。而list又提供按位置加,那么table显示就是按位置加了。
Java代码
tableView.getItems().add(selectedRow, newRecord);
newRecord一个新的对象,没赋值。
自定义TableCell一般都是重写updateItem方法。如果有需要在编辑做操作,可以重写startEdit,cancelEdit
Java代码
@Override
public void startEdit()
if (!this.getTableRow().isVisible())
return;
super.startEdit();
if (checkBox == null)
createCheckBox();
setText(null);
setGraphic(checkBox);
@Override
public void cancelEdit()
super.cancelEdit();
setText(getItem().toString());
setGraphic(null);
可以看到,一旦点击编辑状态,则改变Cell里面的内容。一离开编辑就换成原本cell里面的内容。这样就可以显示的时候就是字符串,而编辑的时候就可以弄一个控件,如日历。
获取选中的TableColumn
Java代码
table.getSelectionModel().getSelectedCells().get(0).getTableColumn()
table自带方法可以过滤column,也就是只显示哪些column
Java代码
table.setTableMenuButtonVisible(true);
设置为true后,会出现一个加号的column,它可以对column进行过滤
table默认是只能选着一行的,如果想选着多行,设置SelectionMode,此时可以对选中的多个进行监听。
Java代码
ListChangeListener<Person> indicesListener = new ListChangeListener<Person>()
@Override public void onChanged(Change<? extends Person> c)
while (c.next())
selectionUpdated(c.getAddedSubList(), c.getRemoved());
;
tableView.getSelectionModel().getSelectedItems().addListener(indicesListener);
tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
tableView.getSelectionModel()得到的是个抽象类SelectionModel,它有二个子类MultipleSelectionModel, SingleSelectionModel。它们主要处理选择事件,可以看它们的方法:
Java代码
getSelectedIndex()
getSelectedItem()
selectedIndexProperty()
selectedItemProperty()
获取选中的item和索引。一个是获取其值,另一个是获取封装属性,用于bind变化。
Java代码
select(int index)
select(T obj)
selectFirst()
selectLast()
...
clearSelection()
clearSelection(int index)
这些方法都是操作选中。
Java代码
setSelectionMode(SelectionMode.MULTIPLE);
selectIndices(int index, int... indices)
selectRange(int start, int end)
MultipleSelectionModel则提供多选功能,并且提供多选的一些方法。
Java代码
select(int row, TableColumn<S,?> column)
selectAboveCell()
selectBelowCell()
selectLeftCell()
selectRightCell()
setCellSelectionEnabled(boolean value)
TableView.TableViewSelectionModel<S>是继承了MultipleSelectionModel,主要针对table的选中事件提供了一些方法。追问
首先很感谢你第一时间回答我的问题
我现在把抽象问题具体化一下,如果我在tableview的某一列中加一个button,该button的响应事件为点击button,让button的text值在yes 和 no之间切换,我切换表格中内容的时候发现对应的ObservableList中button对应的数据并没有产生对应的修改,
如何绕过 JavaFX 的 TableView “占位符”?
【中文标题】如何绕过 JavaFX 的 TableView “占位符”?【英文标题】:How can I bypass the JavaFX's TableView "placeholder"? 【发布时间】:2013-06-04 06:20:26 【问题描述】:JavaFX 的TableView
有一个placeholder 属性,它基本上是一个Node
,只要它为空就会显示在TableView
中。如果此属性设置为 null(其默认值),它会显示为 Label
或其他基于 Node
的文本,表示“表格中没有内容”。
但是如果表格中有任何数据行,那么占位符 Node
就会消失,TableView
中的整个垂直空间都会被行填充,如果没有足够的数据来填充整张桌子。
这些空行是我想要的,即使表格是空的。换句话说,我根本不想使用占位符。有谁知道我该怎么做?
我宁愿不做一些笨拙的事情,比如在TableView
中放置一个看起来空的行,只要它应该是空的。
【问题讨论】:
+1 我也在寻找一种方法来做到这一点。在对其进行编码时,您可以手动设置 placeHolder,我通常将其设置为图像:table.setPlaceholder(new ImageView(new Image(new FileInputStream("someImage.png"))));
但我想让它只显示空行......也是一种在 FXML 中实现的方法是方便
JavaFX 问题跟踪器中的功能请求:RT-31086 Don't force me to use the default 'placeholder' in an empty TableView.
openjdk 代表中的错误坐标:bugs.openjdk.java.net/browse/JDK-8090949 - 在 fx9/10 中仍未解决
【参考方案1】:
不幸的是,old issue 在 fx9 和更高版本(包括 fx18)中仍未修复。
fx9 的原始皮肤修改(见水平线下方)似乎仍在 fx18 中工作。虽然由于 fx12 引入了对流和 tableHeaderRow 的访问,但可以稍微改进。
在尝试将其调整为更新的 api 时,我想出了一个 - 同样肮脏且未经测试的 - 方法。这个想法是覆盖实际的布局方法,即在布置表格的各个子项时调用的方法,注意占位符并将其替换为流:
public class NoPlaceHolderSkin<T> extends TableViewSkin<T>
public NoPlaceHolderSkin(TableView<T> control)
super(control);
@Override
protected void layoutInArea(Node child, double areaX, double areaY, double areaWidth,
double areaHeight, double areaBaselineOffset, HPos halignment, VPos valignment)
if (child.getStyleClass().contains("placeholder"))
child.setVisible(false);
child = getVirtualFlow();
child.setVisible(true);
super.layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, halignment, valignment);
因此,我们在 fx9 的背景下重新审视了这些黑客行为。有好有坏:
皮肤移入了一个公共包,现在允许在不访问隐藏类的情况下对其进行子类化(好) 移动 introduced a bug 不允许安装自定义 VirtualFlow(已在 fx10 中修复) 将来某个时候将强烈禁止对隐藏成员的反射访问(阅读:不可能)在挖掘过程中,我注意到这些 hack 出现了非常轻微的故障(注意:我没有针对 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://***.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());
【讨论】:
伪造项目计数时“行在箭头导航上消失”只能是 fx9/fx10 错误,因为我无法在 fx8 中重现它。无论如何,关于这个问题/错误的一个很好的概述!【参考方案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);
);
也许你可以用同样的方法改变流的可见性,使代码更短……但我想你明白了
【讨论】:
【参考方案3】:我找到了 javafx8 的解决方案。它使用了非公共 api,但它不使用反射(幸运的是)。基本上你需要设置(或替换)TableView 的外观并在方法getItemCount()
中返回一个非零值。像这样:
(TableView)t.setSkin(new TableViewSkin<Object>(t)
@Override
public int getItemCount()
int r = super.getItemCount();
return r == 0 ? 1 : r;
);
此方法还可用于在最后一项的底部添加额外的行(例如,如果您想包含添加按钮)。基本上返回总是比实际项目数高一。
尽管这是一个老问题,但希望这对某人有所帮助。
【讨论】:
已验证这在 fx8 中看起来不错 - 我注意到的故障只是 fx9。 +1 一个简单的解决方案:)【参考方案4】:如果有人仍在寻找与其他人提供的解决方案不同的替代解决方案,以下是我使用的解决方案。就我而言,这是我可以采用的最简单的方法(没有自定义皮肤,没有 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% );
我要求边框颜色与行背景形成对比。通过上述方法,我可以轻松地为占位符列设置边框颜色。
【讨论】:
【参考方案5】:这是执行任务的一种棘手方法,
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);
【讨论】:
哈!那是偷偷摸摸的。不幸的是,它在我的表中看起来不太“正确”,它有很多可调整大小的列。不过,对于具有静态列的简单表,这可能会起作用。以上是关于javaFX tableView中嵌入的主要内容,如果未能解决你的问题,请参考以下文章
如何在 JavaFx TableView 中添加 TableView 页脚
JavaFX 9/10 不再可能覆盖 TableView.resizeColumnToFitContent