javafx-TableView嵌套UI元素在动态添加行时释放引用数据对象
我正在尝试创建一个API,它可以在tableview的每个单元格中使用不同的嵌套UI组件动态填充tableview。我能够用表将数据集绑定到模型对象。 问题是,当我尝试动态添加并尝试启用编辑时,对象引用似乎混乱了。 供参考: 正如你们所看到的,我在最后一列中有4个按钮,分别是添加、编辑、删除和重置功能。单击添加-克隆当前行,单击编辑-启用Coulmn类别的组合框,单击删除-删除当前行 我面对的是,在添加多个条目时,我确实会动态添加行,但随后单击第一行编辑按钮-然后启用多个javafx-TableView嵌套UI元素在动态添加行时释放引用数据对象,javafx,java-8,javafx-8,javafx-2,Javafx,Java 8,Javafx 8,Javafx 2,我正在尝试创建一个API,它可以在tableview的每个单元格中使用不同的嵌套UI组件动态填充tableview。我能够用表将数据集绑定到模型对象。 问题是,当我尝试动态添加并尝试启用编辑时,对象引用似乎混乱了。 供参考: 正如你们所看到的,我在最后一列中有4个按钮,分别是添加、编辑、删除和重置功能。单击添加-克隆当前行,单击编辑-启用Coulmn类别的组合框,单击删除-删除当前行 我面对的是,在添加多个条目时,我确实会动态添加行,但随后单击第一行编辑按钮-然后启用多个组合框,这不是预期用途
组合框
,这不是预期用途。用例是当前行的组合框
只能启用
实现:我编写了自定义API,它扩展了TableView
。
以下代码段可能会有所帮助:
//column category
final ClumpElement< ConstraintsDataModel, String > categoryElement =
new ClumpElement<>( ClumpType.COMBOBOX, true, getCategoryData() );
categoryElement.setClumpTableCellValue( data -> data.categoryProperty() );
categoryElement.setClumpTableNodeAction( ( control, data ) -> {
final ComboBox< String > comboBox = (ComboBox< String >)control;
comboBox.disableProperty().bind( data.disableProperty() );
} );
clumpTableView.addNewColumn( "Category", categoryElement );
// column Action
final ClumpElement< ConstraintsDataModel, String > buttonsElement =
new ClumpElement<>( ClumpType.GROUP_BUTTONS, 4, "+", "✎", "X", "↻" );
buttonsElement.setClumpTableNodeAction( ( control, data ) -> {
final Button button = (Button)control;
switch( button.getText() ) {
case "+":
final ConstraintsDataModel ref =
clumpTableView.getItems().get( clumpTableView.getItems().size() - 1 );
if( ConstraintsDataModel.isValidModel( ref ) )
clumpTableView.getItems().add( new ConstraintsDataModel( data ) );
else
System.out.println( "ERROR: Finish previous constraints" );
break;
case "✎":
data.setDisableValue( false );
button.setText( "✔" );
break;
case "✔":
data.setDisableValue( true );
button.setText( "✎" );
break;
default:
//NOTHING
break;
}
} );
clumpTableView.addNewColumn( "Action", buttonsElement );
clumpTableView.setItems( getData() );
在我的定制API中,它将调用一个定制的TableCell
,它具有ComboBox
符合文档要求的标准实现。在这里,它位于一个选择监听器中,我发现当单元格渲染时,只调用这个选择监听器
public abstract class AbstractClumpTableCell< S, T > extends TableCell< S, T > {
public AbstractClumpTableCell() {
setContentDisplay( ContentDisplay.GRAPHIC_ONLY );
setAlignment(Pos.CENTER);
}
public abstract void renewItem( T item );
@Override
protected void updateItem( T item, boolean empty ) {
super.updateItem( item, empty );
if( empty ) {
setGraphic( null );
} else {
renewItem( item );
}
}
}
public class ClumpComboBoxTableCell< S, T > extends AbstractClumpTableCell< S, T > {
private final ComboBox< T > comboBox;
@SuppressWarnings( "unchecked" )
public ClumpComboBoxTableCell( final boolean isDisable, final ObservableList< T > item ) {
super();
this.comboBox = new ComboBox<>( item );
this.comboBox.setDisable( isDisable );
this.comboBox.valueProperty().addListener( ( obs, oVal, nVal ) -> {
ObservableValue< T > property = getTableColumn().getCellObservableValue( getIndex() );
if( property instanceof WritableValue ) {
((WritableValue< T >)property).setValue( nVal );
}
} );
}
@Override
public void renewItem( T item ) {
comboBox.setValue( item );
setGraphic( comboBox );
}
public ComboBox< T > getComboBox() {
return comboBox;
}
protected void selectionListener( final ClumpElement< S, T > element ) {
this.comboBox.getSelectionModel().selectedItemProperty().addListener( ( obs, oVal, nVal ) -> {
if( element.getClumpTableNodeAction() != null
&& getIndex() < getTableView().getItems().size() ) {
element.getClumpTableNodeAction().act( this.comboBox,
getTableView().getItems().get( getIndex() ) );
}
} );
}
}
公共抽象类AbstractClumpTableCell扩展了TableCell{
公共抽象可编程单元(){
setContentDisplay(仅限ContentDisplay.GRAPHIC_);
设置对齐(位置中心);
}
公开摘要项(T项);
@凌驾
受保护的void updateItem(T项,布尔值为空){
super.updateItem(项,空);
if(空){
设置图形(空);
}否则{
更新项目(项目);
}
}
}
公共类ClumpComboxTableCell扩展了AbstractClumpTableCell{
私有最终组合框组合框;
@抑制警告(“未选中”)
公共clumpcomboxTableCell(最终布尔值为禁用,最终可观察列表项){
超级();
this.comboBox=新的组合框(项目);
this.comboBox.setDisable(isDisable);
this.comboBox.valueProperty().addListener((obs、oVal、nVal)->{
observeValueproperty=getTableColumn().getCellObservableValue(getIndex());
if(可写值的属性实例){
((WritableValue)属性);
}
} );
}
@凌驾
公共项目(T项目){
comboBox.setValue(项);
设置图形(组合框);
}
公共组合框getComboBox(){
返回组合框;
}
受保护的void selectionListener(最终聚集元素元素){
this.comboBox.getSelectionModel().SelectEditeProperty().addListener((obs、oVal、nVal)->{
if(element.getClumpTableNodeAction()!=null
&&getIndex()
我的数据模型有一个SimpleStringProperty
,它相应地绑定到列
那么,如何在
TableView
中正确绑定嵌套的UI元素?我的方法是正确的还是有其他选择?我会尝试回答,但正如我所说的,代码对我来说很难遵循(特别是因为它是局部的,所以我只能假设某些方法的目的)
如评论中所述,问题是TableView
中的节点虚拟化。你不能绕开它,你真的不想绕开它——这是一种极大地提高性能的方法,因为你不需要成百上千的UI节点(它们“沉重”并且降低了性能),但只需要填满表中显示的部分,从而支持更大的数据集
就我所见,问题在于,行的某些属性(当前是否可编辑)需要反映在某些列中。更具体地说,您希望组合框的disable
属性始终反映它所属行的disable
属性,因此在updateItem
中,您必须执行以下操作:
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(T, empty);
if (empty) {
setGraphic(null);
} else {
renewItem(item);
// since the disable property if given by the row value, not only the column value
// we need to get the row value. The cast is needed due to a design oversight
// in JavaFX 8, which is fixed in newer versions. See https://bugs.openjdk.java.net/browse/JDK-8144088
ConstraintsDataModel data = ((TableRow<ConstraintsDataModel>)getTableRow())
.getItem();
combobox.disableProperty().unbind();
combobox.disableProperty().bind(data.disableProperty());
}
}
@覆盖
受保护的void updateItem(T项,布尔值为空){
super.updateItem(T,空);
if(空){
设置图形(空);
}否则{
更新项目(项目);
//由于disable属性如果由行值给定,则不只是列值
//我们需要获得行值。由于设计疏忽,需要强制转换
//在JavaFX8中,这在较新版本中已修复。请参阅https://bugs.openjdk.java.net/browse/JDK-8144088
ConstraintsDataModel数据=((TableRow)getTableRow())
.getItem();
combobox.disableProperty().unbind();
combobox.disableProperty().bind(data.disableProperty());
}
}
这是假设您的行数据类型确实是ConstantDataModel
,我无法完全理解
另一个可能更优雅的选项是使用行的
编辑
属性-将组合框的禁用
属性绑定到行的编辑
属性的否定,并在开始和结束编辑时使用开始编辑
和取消编辑
提交。这样,您就不必重新绑定组合框的disable属性,因为它总是引用正确的行 是否确实为每个单元格使用不同的TableCell实例?我的问题听起来好像你在专栏中有一个常见的实例。这就是为什么在其他单元格中会出现奇怪的行为。@mrmcwolf是的,我已经为每个实例创建了新对象。我不确定的是,在调用时,update(T项)
中是否使用了相同的UI组件?@mrmcwolfClumpElement
对象是一个自定义对象,它应该
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(T, empty);
if (empty) {
setGraphic(null);
} else {
renewItem(item);
// since the disable property if given by the row value, not only the column value
// we need to get the row value. The cast is needed due to a design oversight
// in JavaFX 8, which is fixed in newer versions. See https://bugs.openjdk.java.net/browse/JDK-8144088
ConstraintsDataModel data = ((TableRow<ConstraintsDataModel>)getTableRow())
.getItem();
combobox.disableProperty().unbind();
combobox.disableProperty().bind(data.disableProperty());
}
}