javafx - TableView嵌套的UI元素在动态添加行时会丢失引用数据对象

时间:2017-11-29 07:56:27

标签: javafx java-8 javafx-8 javafx-2

我正在尝试创建一个API,它可以在tableview的每个单元格中使用不同的嵌套UI组件动态填充tableview。我可以使用表将数据集绑定到Model对象。 问题是当我尝试动态添加并尝试启用编辑时,对象引用似乎搞砸了。 供参考: enter image description here

如您所见,我的最后一栏有4个按钮,即添加,编辑,删除,重置功能。点击Add - 它克隆当前行,点击编辑 - 它启用Coulmn类别的window.location.href,点击删除 - 它删除当前行。

我面临的是,在添加多个条目后,我确实动态添加了行,但是然后单击第一行编辑按钮 - 然后启用了多个window.location.href = "welcome.html"; ,这不是用途。用例是当前行的ComboBox必须只能启用。

实施:我编写了自定义API,扩展了ComboBox。 以下代码段可能有所帮助:

ComboBox

这是我的TableView<S>课程:

//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中,该API将根据文档调用自定义CustomTableView,该public < T > void addNewColumn( final String columnName, final ClumpElement< S, T > element ) { final TableColumn< S, T > column = new TableColumn<>( columnName ); getColumns().add( column ); if( element.getClumpTableCellValue() != null ) { column.setCellValueFactory( param -> element.getClumpTableCellValue() .act( param.getValue() ) ); } clumpCellCall( columnName, element, column ); } private < T > void clumpCellCall( final String colName, final ClumpElement< S, T > element, final TableColumn< S, T > column ) { switch( element.getUiNode() ) { case COMBOBOX: if( element.getItems() != null && !element.getItems().isEmpty() ) { column.setCellFactory( param -> { final ClumpComboBoxTableCell< S, T > clumpComboBoxTableCell = new ClumpComboBoxTableCell<>( element.isDisable(), element.getItems() ); clumpComboBoxTableCell.prefWidthProperty().bind( column.widthProperty() ); clumpComboBoxTableCell.selectionListener( element ); return clumpComboBoxTableCell; } ); } break; case GROUP_BUTTONS: column.setCellFactory( param -> { final ClumpButtonsTableCell< S, T > clumpButtonsTableCell = new ClumpButtonsTableCell<>( element.getNoOfElements() ); clumpButtonsTableCell.prefWidthProperty().bind( column.widthProperty() ); IntStream.range( 0, element.getNoOfElements() ).forEach( item -> { final Button button = clumpButtonsTableCell.getButtons().get( item ); button.setText( element.getNames().get( item ) ); button.setOnAction( event -> { if( element.getClumpTableNodeAction() != null && clumpButtonsTableCell.getIndex() < getItems().size() ) { element.getClumpTableNodeAction() .act( button, getItems().get( clumpButtonsTableCell.getIndex() ) ); } } ); } ); return clumpButtonsTableCell; } ); break; default: column.setCellFactory( params -> { final TextFieldTableCell< S, T > textFieldTableCell = new TextFieldTableCell<>(); textFieldTableCell.setConverter( new StringConverter< T >() { @Override public String toString( final T object ) { return (String)object; } @Override public T fromString( final String string ) { return (T)string; } } ); return textFieldTableCell; } ); break; } } 具有TableCell<S,T>非常标准的实现。这里是一个选择监听器,我发现当单元格呈现时,只调用这个选择监听器。

ComboBox<T>

我的数据模型有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() ) ); } } ); } } ,因此绑定到列。

那么,如何在SimpleStringProperty内正确地绑定嵌套的UI元素呢?我的方法是正确的还是有替代方案?

1 个答案:

答案 0 :(得分:2)

我会尝试回答,但正如我所说,代码对我来说很难遵循(特别是因为它是部分的,所以有些方法我只能假设其目的)。

正如评论中所述,问题是TableView中的节点虚拟化。你不能绕过它,而你真的不想 - 它是一种大大提高性能的手段,因为你不需要数百或数千个UI节点(&#34) ;沉重的&#34;并降低性能),但仅足以填充表格的显示部分,从而支持更大的数据集。

问题,据我所知,因为你有行的某些属性(目前是否可编辑),你需要在某些列中反映出来/ em>的。更具体地说,您希望组合框的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()); 
    }
}

这假设您的行数据类型确实是ConstaintDataModel,我无法完全遵循。

另一个可能更优雅的选项是使用行的editing属性 - 将组合框的disable属性绑定到否定editing属性该行,并在您开始和结束编辑时使用startEditcancelEdit / commitEdit。这样您就不必重新绑定组合框的disable属性,因为它总是引用正确的行。