javafx-TableView嵌套UI元素在动态添加行时释放引用数据对象

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的每个单元格中使用不同的嵌套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组件?@mrmcwolf
ClumpElement
对象是一个自定义对象,它应该
@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()); 
    }
}