编辑选项卡上的下一行

时间:2016-06-06 06:51:37

标签: user-interface javafx

当我将所有代码放入SSCCE时,它按预期工作,即第一个和第三个单元格是可编辑的。当最后一列上的标签时,转到下一行。

    import java.text.NumberFormat;
    import java.text.ParseException;
    import java.text.ParsePosition;
    import java.util.ArrayList;
    import java.util.List;
    import javafx.application.Application;
    import static javafx.application.Application.launch;
    import javafx.application.Platform;
    import javafx.beans.property.ListProperty;
    import javafx.beans.property.SimpleListProperty;
    import javafx.beans.value.ChangeListener;
    import javafx.beans.value.ObservableValue;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.event.ActionEvent;
    import javafx.event.EventHandler;
    import javafx.geometry.Insets;
    import javafx.scene.Group;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.ContentDisplay;
    import javafx.scene.control.TableCell;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableColumn.CellEditEvent;
    import javafx.scene.control.TablePosition;
    import javafx.scene.control.TableView;
    import javafx.scene.control.TextField;
    import javafx.scene.control.cell.PropertyValueFactory;
    import javafx.scene.input.KeyCode;
    import javafx.scene.input.KeyEvent;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    import javafx.util.Callback;

  /*
   * To change this license header, choose License Headers in Project Properties.
   * To change this template file, choose Tools | Templates
   * and open the template in the editor.
  */

/**
  *
  * @author Yunus
*/
public class CollectionForm  extends Application{
     private TableView table = new TableView();
     private ObservableList<Collection> collectionList = FXCollections.<Collection>observableArrayList();
     ListProperty<Collection> collectionListProperty = new SimpleListProperty<>();
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
       launch(args);
}

@Override
public void start(Stage stage) {
    // single cell selection mode
    table.getSelectionModel().setCellSelectionEnabled(true);

    //Create a custom cell factory so that cells can support editing.
    Callback<TableColumn, TableCell> editableFactory = new Callback<TableColumn, TableCell>() {
        @Override
        public TableCell call(TableColumn p) {
            return new EditableTableCell();
        }
    };
    //A custom cell factory that creates cells that only accept numerical input.
    Callback<TableColumn, TableCell> numericFactory = new Callback<TableColumn, TableCell>() {
        @Override
        public TableCell call(TableColumn p) {
            return new NumericEditableTableCell();
        }
    };

    Button b = createSaveCollectionBtn();
    //Create columns
    TableColumn colMNO = createMNOColumn(editableFactory);
    TableColumn colName = createNameColumn(editableFactory);
    TableColumn colQty = createQuantityColumn(numericFactory);
    table.getColumns().addAll(colMNO, colName, colQty);            

    //Make the table editable
table.setEditable(true);

    collectionListProperty.set(collectionList);
    table.itemsProperty().bindBidirectional(collectionListProperty);

    collectionList.add(new Collection());
    collectionList.add(new Collection());

    Scene scene = new Scene(new Group());
    stage.setTitle("Table View Sample");
    final VBox vbox = new VBox();
    vbox.setSpacing(5);
    vbox.getChildren().addAll(b, table);
    vbox.setPadding(new Insets(10, 0, 0, 10));
    ((Group) scene.getRoot()).getChildren().addAll(vbox);
    stage.setScene(scene);
stage.show();
}

private void handleCollection(ActionEvent event){
    for (Collection collection : collectionList) {
        System.out.println("MNO: "+collection.getMno()+" Quantity: "+collection.getQuantity());
    }
}

private Button createSaveCollectionBtn(){
    Button btn = new Button("Save Collection");
    btn.setId("btnSaveCollection");
    btn.setOnAction(this::handleCollection);
    return btn;
}

private TableColumn createQuantityColumn(Callback<TableColumn, TableCell> editableFactory) {
    TableColumn colQty = new TableColumn("Quantity");
    colQty.setMinWidth(25);
    colQty.setId("colQty");
    colQty.setCellValueFactory(new PropertyValueFactory("quantity"));
    colQty.setCellFactory(editableFactory);
    colQty.setOnEditCommit(new EventHandler<CellEditEvent<Collection, Long>>() {
        @Override
        public void handle(CellEditEvent<Collection, Long> t) {
            ((Collection) t.getTableView().getItems().get(t.getTablePosition().getRow())).setQuantity(t.getNewValue());
        }
    });
    return colQty;
}

private TableColumn createMNOColumn(Callback<TableColumn, TableCell> editableFactory) {
    TableColumn colMno = new TableColumn("M/NO");
    colMno.setMinWidth(25);
    colMno.setId("colMNO");
    colMno.setCellValueFactory(new PropertyValueFactory("mno"));
    colMno.setCellFactory(editableFactory);
    colMno.setOnEditCommit(new EventHandler<CellEditEvent<Collection, String>>() {
            @Override
            public void handle(CellEditEvent<Collection, String> t) {
                ((Collection) t.getTableView().getItems().get(t.getTablePosition().getRow())).setMno(t.getNewValue());
            }
    });
    return colMno;
}

private TableColumn createNameColumn(Callback<TableColumn, TableCell> editableFactory) {
    TableColumn colName = new TableColumn("Name");
    colName.setEditable(false);
    colName.setMinWidth(100);
    colName.setId("colName");
    colName.setCellValueFactory(new PropertyValueFactory<Collection, String>("name"));
    colName.setCellFactory(editableFactory);
    //Modifying the firstName property
    colName.setOnEditCommit(new EventHandler<CellEditEvent<Collection, String>>() {
            @Override
            public void handle(CellEditEvent<Collection, String> t) {
                    ((Collection) t.getTableView().getItems().get(t.getTablePosition().getRow())).setName(t.getNewValue());
            }
    });
    return colName;
}


 /**
   *
   * @author Graham Smith
 */

  public class EditableTableCell<S extends Object, T extends String> extends AbstractEditableTableCell<S, T> {
public EditableTableCell() {
}
@Override
protected String getString() {
        return getItem() == null ? "" : getItem().toString();
}
@Override
protected void commitHelper( boolean losingFocus ) {
        commitEdit(((T) textField.getText()));
}
    }

  /**
    *
    * @author Graham Smith
  */

    public class NumericEditableTableCell<S extends Object, T extends Number> extends AbstractEditableTableCell<S, T> {
private final NumberFormat format;
private boolean emptyZero;
private boolean completeParse;
/**
 * Creates a new {@code NumericEditableTableCell} which treats empty strings as zero,
 * will parse integers only and will fail if is can't parse the whole string.
 */
public NumericEditableTableCell() {
    this( NumberFormat.getInstance(), true, true, true );
}

/**
 * The integerOnly and completeParse settings have a complex relationship and care needs
 * to be take to get the correct result. 
 * <ul>
 * <li>If you want to accept only integers and you want to parse the whole string then 
 * set both integerOnly and completeParse to true. Strings such as 1.5 will be rejected
 * as invalid. A string such as 1000 will be accepted as the number 1000.</li>
 * <li>If you only want integers but don't care about parsing the whole string set
 * integerOnly to true and completeParse to false. This will parse a string such as
 * 1.5 and provide the number 1. The downside of this combination is that it will accept 
 * the string 1x and return the number 1 also.</li>
 * <li>If you want to accept decimals and want to parse the whole string set integerOnly
 * to false and completeParse to true. This will accept a string like 1.5 and return
 * the number 1.5. A string such as 1.5x will be rejected.</li>
 * <li>If you want to accept decimals and don't care about parsing the whole string set
 * both integerOnly and completeParse to false. This will accept a string like 1.5x and
 * return the number 1.5. A string like x1.5 will be rejected because ti doesn't start
 * with a number. The downside of this combination is that a string like 1.5x3 will 
 * provide the number 1.5.</li>
 * </ul>
 * 
 * @param format the {@code NumberFormat} to use to format this cell.
 * @param emptyZero if true an empty cell will be treated as zero.
 * @param integerOnly if true only the integer part of the string is parsed.
 * @param completeParse  if true an exception will be thrown if the whole string given can't be parsed.
 */
public NumericEditableTableCell( NumberFormat format, boolean emptyZero, boolean integerOnly, boolean completeParse ) {
    this.format = format;
    this.emptyZero = emptyZero;
    this.completeParse = completeParse;
    format.setParseIntegerOnly(integerOnly);
}
@Override
protected String getString() {
    return getItem() == null ? "" : format.format(getItem());
}

/**
 * Parses the value of the text field and if matches the set format 
 * commits the edit otherwise it returns the cell to it's previous value.
 */
@Override
protected void commitHelper( boolean losingFocus ) {
    if( textField == null ) {
        return;
    }

    try {
        String input = textField.getText();
        if (input == null || input.length() == 0) {
            if(emptyZero) {
                setText( format.format(0) );
                commitEdit( (T)new Integer( 0 ));
            }
            return;
        }

        int startIndex = 0;
        ParsePosition position = new ParsePosition(startIndex);
        Number parsedNumber = format.parse(input, position);

        if (completeParse && position.getIndex() != input.length()) {
            throw new ParseException("Failed to parse complete string: " + input, position.getIndex());
        }

        if (position.getIndex() == startIndex ) {
            throw new ParseException("Failed to parse a number from the string: " + input, position.getIndex());
        }
        commitEdit( (T)parsedNumber );
    } catch (ParseException ex) {
        //Most of the time we don't mind if there is a parse exception as it
        //indicates duff user data but in the case where we are losing focus
        //it means the user has clicked away with bad data in the cell. In that
        //situation we want to just cancel the editing and show them the old
        //value.
        if( losingFocus ) {
            cancelEdit();
        }
    }
}
    }

   /**
     * Provides the basis for an editable table cell using a text field. Sub-classes can provide formatters for display and a
     * commitHelper to control when editing is committed.
     *
     * @author Graham Smith
     */
    public abstract class AbstractEditableTableCell<S, T> extends TableCell<S, T> {
protected TextField textField;
public AbstractEditableTableCell() {
}
/**
 * Any action attempting to commit an edit should call this method rather than commit the edit directly itself. This
 * method will perform any validation and conversion required on the value. For text values that normally means this
 * method just commits the edit but for numeric values, for example, it may first parse the given input. <p> The only
 * situation that needs to be treated specially is when the field is losing focus. If you user hits enter to commit the
 * cell with bad data we can happily cancel the commit and force them to enter a real value. If they click away from the
 * cell though we want to give them their old value back.
 *
 * @param losingFocus true if the reason for the call was because the field is losing focus.
 */
protected abstract void commitHelper(boolean losingFocus);
/**
 * Provides the string representation of the value of this cell when the cell is not being edited.
 */
protected abstract String getString();
@Override
public void startEdit() {
    super.startEdit();
    if (textField == null) {
        createTextField();
    }
    setGraphic(textField);
    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
    Platform.runLater(new Runnable() {
        @Override
        public void run() {
            textField.selectAll();
            textField.requestFocus();
        }
    });
}
@Override
public void cancelEdit() {
    super.cancelEdit();
    setText(getString());
    setContentDisplay(ContentDisplay.TEXT_ONLY);
    //Once the edit has been cancelled we no longer need the text field
    //so we mark it for cleanup here. Note though that you have to handle
    //this situation in the focus listener which gets fired at the end
    //of the editing.
    textField = null;
}
@Override
public void updateItem(T item, boolean empty) {
    super.updateItem(item, empty);
    if (empty) {
        setText(null);
        setGraphic(null);
    } else {
        if (isEditing()) {
            if (textField != null) {
                textField.setText(getString());
            }
            setGraphic(textField);
            setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        } else {
            setText(getString());
            setContentDisplay(ContentDisplay.TEXT_ONLY);
        }
    }
}
private void createTextField() {
    textField = new TextField(getString());
    textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
    textField.setOnKeyPressed(new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent t) {
            if (t.getCode() == KeyCode.ENTER) {
                commitHelper(false);
            } else if (t.getCode() == KeyCode.ESCAPE) {
                cancelEdit();
            } else if (t.getCode() == KeyCode.TAB) {
                commitHelper(false);

                TableColumn nextColumn = getNextColumn(!t.isShiftDown());

                                    TablePosition focusedCellPosition = getTableView().getFocusModel().getFocusedCell();
                if (nextColumn != null) {


                                        //if( focusedCellPosition.getColumn() ){}focusedCellPosition.getTableColumn()
                                        System.out.println("Column: "+focusedCellPosition.getColumn());

                                        System.out.println("nextColumn.getId();: "+nextColumn.getId());
                                        if( nextColumn.getId().equals("colMNO") ){
                                            collectionList.add(new Collection());

                                            getTableView().edit((getTableRow().getIndex())+1,getTableView().getColumns().get(0) ); 
                                            getTableView().layout();
                                        } else {
                                            getTableView().edit(getTableRow().getIndex(), nextColumn);
                                        }
                }else{
                                        getTableView().edit((getTableRow().getIndex())+1,getTableView().getColumns().get(0) ); 
                                    }
            }
        }
    });
    textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
        @Override
        public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
            //This focus listener fires at the end of cell editing when focus is lost
            //and when enter is pressed (because that causes the text field to lose focus).
            //The problem is that if enter is pressed then cancelEdit is called before this
            //listener runs and therefore the text field has been cleaned up. If the
            //text field is null we don't commit the edit. This has the useful side effect
            //of stopping the double commit.
            if (!newValue && textField != null) {
                commitHelper(true);
            }
        }
    });
}
/**
 *
 * @param forward true gets the column to the right, false the column to the left of the current column
 * @return
 */
private TableColumn<S, ?> getNextColumn(boolean forward) {
    List<TableColumn<S, ?>> columns = new ArrayList<>();
    for (TableColumn<S, ?> column : getTableView().getColumns()) {
        columns.addAll(getLeaves(column));
    }
    //There is no other column that supports editing.
    if (columns.size() < 2) {
        return null;
    }
    int currentIndex = columns.indexOf(getTableColumn());
    int nextIndex = currentIndex;
    if (forward) {
        nextIndex++;
        if (nextIndex > columns.size() - 1) {
            nextIndex = 0;
        }
    } else {
        nextIndex--;
        if (nextIndex < 0) {
            nextIndex = columns.size() - 1;
        }
    }
    return columns.get(nextIndex);
}
private List<TableColumn<S, ?>> getLeaves(TableColumn<S, ?> root) {
    List<TableColumn<S, ?>> columns = new ArrayList<>();
    if (root.getColumns().isEmpty()) {
        //We only want the leaves that are editable.
        if (root.isEditable()) {
            columns.add(root);
        }
        return columns;
    } else {
        for (TableColumn<S, ?> column : root.getColumns()) {
            columns.addAll(getLeaves(column));
        }
        return columns;
    }
}
    }

   public class Collection {
    private int id;
    private String mno;
    private String name;
    private float quantity;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getMno() {
        return mno;
    }

    public void setMno(String mno) {
        this.mno = mno;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getQuantity() {
        return quantity;
    }

    public void setQuantity(float quantity) {
        this.quantity = quantity;
    }
}
   }

问题是当我将相同的代码添加到控制器并以编程方式添加此表时,不能像以前那样工作:它跳转到下一行并转到第三行。

enter image description here

1 个答案:

答案 0 :(得分:3)

在要求TableView编辑单元格之前,确保它具有焦点,相关单元格在视图中以及视图布局是最新的是非常重要的。这可能是因为TableView使用虚拟单元格的方式。

在<{1}} 的任何调用之前添加这三行

TableView#edit

这为我解决了这个问题。