简体   繁体   中英

JavaFX Custom TableCell Listening

The TextFieldTableCell, for example, responds and calls commitEdit() when you hit the enter key.

If I use a custom node to display, then how can I listen for ways to confirm an edit? For example, I have a DateTimePicker which extends a VBox. I can't add a KeyListener to a VBox.

What can I do to watch for confirmation of in-table edits? Here is what I have attempted:

class TableCellDateTimePicker extends TableCell<CHILD, LocalDateTime> {

    public TableCellDateTimePicker() {
        super();
    }



    @Override
    public void startEdit() {
        super.startEdit();
        DateTimePicker picker = new DateTimePicker();
        picker.getDatePickerField().setOnKeyPressed((KeyEvent event) -> {
            System.out.println("PRESS");
            if (event.getCode() == KeyCode.ENTER) {
                commitEdit(picker.getValue());
            }
        });
        picker.setValue(getItem());
        setGraphic(picker);
    }



    @Override
    public void commitEdit(LocalDateTime newValue) {
        super.commitEdit(newValue);
        setText(newValue.toString());
    }



    @Override
    public void cancelEdit() {
        super.cancelEdit();
    }

}

DateTimePicker:

/**
 * A custom DateTimePicker that lets a user select a LocalDateTime.
 *
 * @author Toni-Tran
 */
public class DateTimePicker extends VBox {

    @FXML
    private DatePicker datePicker;
    @FXML
    private Button showTimePickerBtn;

    private NumberPicker hourField;
    private NumberPicker minuteField;

    private ObjectProperty<LocalDateTime> currentLocalDateTimeValue;



    public DateTimePicker() {
        FXMLLoader loader = new FXMLLoader(this.getClass().getResource(
                ScreenPaths.DATE_TIME_PICKER));
        loader.setController(this);
        loader.setRoot(this);
        try {
            loader.load();
        } catch (IOException e) {
        }
    }



    @FXML
    private void initialize() {
        showTimePickerBtn.setOnAction(event -> this.showTimePicker());

        hourField = new NumberPicker();
        hourField.setBounds(0, 23);
        hourField.setWrapAround(true);

        minuteField = new NumberPicker();
        minuteField.setBounds(0, 59);
        minuteField.setWrapAround(true);

        currentLocalDateTimeValue = new SimpleObjectProperty<>();

        datePicker.valueProperty().addListener((ObservableValue<? extends LocalDate> observable,
                LocalDate oldValue, LocalDate newValue) -> {
                    LocalDateTime newLDT;
                    if (newValue != null) {
                        newLDT = LocalDateTime.of(newValue.getYear(),
                                newValue.getMonth(), newValue.getDayOfMonth(),
                                hourField.getValue(), minuteField.getValue());
                        currentLocalDateTimeValue.set(newLDT);
                    } else {
                        currentLocalDateTimeValue.set(null);
                    }
                });

        hourField.valueProperty().addListener((ObservableValue<? extends Number> observable,
                Number oldValue, Number newValue) -> {
                    if (currentLocalDateTimeValue.getValue() != null) {
                        LocalDateTime newLDT = LocalDateTime.of(currentLocalDateTimeValue.getValue().getYear(),
                                currentLocalDateTimeValue.getValue().getMonth(), currentLocalDateTimeValue.getValue().getDayOfMonth(),
                                hourField.getValue(), minuteField.getValue());
                        currentLocalDateTimeValue.set(newLDT);
                    } else {
                        currentLocalDateTimeValue.set(null);
                    }
                });

        minuteField.valueProperty().addListener((ObservableValue<? extends Number> observable,
                Number oldValue, Number newValue) -> {
                    if (currentLocalDateTimeValue.getValue() != null) {
                        LocalDateTime newLDT = LocalDateTime.of(currentLocalDateTimeValue.getValue().getYear(),
                                currentLocalDateTimeValue.getValue().getMonth(), currentLocalDateTimeValue.getValue().getDayOfMonth(),
                                hourField.getValue(), minuteField.getValue());
                        currentLocalDateTimeValue.set(newLDT);
                    } else {
                        currentLocalDateTimeValue.set(null);
                    }
                });
    }



    /**
     * Manually set a LocalDateTime value for this control.
     *
     * @param val The {@link LocalDateTime} value for this control.
     */
    public void setValue(LocalDateTime val) {
        if (val == null) {
            return;
        }
        LocalDate day = LocalDate.of(val.getYear(), val.getMonthValue(),
                val.getDayOfMonth());
        datePicker.setValue(day);
        hourField.setValue(val.getHour());
        minuteField.setValue(val.getMinute());
    }



    /**
     * Returns the currently set LocalDateTime.
     *
     * @return the {@link LocalDateTime} value of this control.
     */
    public LocalDateTime getValue() {
        return currentLocalDateTimeValue.getValue();
    }



    /**
     * Returns the Observable property of the DateTimePicker's current
     * LocalDateTime value.
     *
     * @return the Observable property of the DateTimePicker's current
     * LocalDateTime value.
     */
    public ObjectProperty<LocalDateTime> valueProperty() {
        return currentLocalDateTimeValue;
    }



    /**
     * This is triggered when the user wants to view the screen for setting a
     * certain time for their LocalDateTime.
     */
    public void showTimePicker() {
        HBox topContainer = new HBox();
        topContainer.getChildren().addAll(hourField, new Label(":"),
                minuteField);
        topContainer.setSpacing(3);
        DialogPopup.showNodeApplicationlModal(topContainer);
    }

}

Typically, an editable cell will display the default text when in non-editing mode and an editing control (your DateTimePicker ) when in editing mode. Your custom cell implementation needs to handle the transition between those as well as making sure the text and value for the editing control are properly updated if the cell's item is updated.

So you should override updateItem(...) to update the value of the DateTimePicker if in editing mode ( isEditing() ) or the text if not. Override startEdit() to switch to show the DateTimePicker instead of the text, and cancelEdit() to switch back.

You might not need to override commitEdit() at all. If your column is bound to a property, the default commitEdit() should update that property (or you can do this in an onEditCommit handler registered with the column).

The tutorial has an example of a fully-implemented editable table cell: see listing 13.11 near the bottom.

However, you will need to invoke commitEdit(...) at the appropriate time. The semantics of this are a bit tricky: you probably want to commit the edit if the user presses Enter when any of the individual controls inside your DateTimePicker have focus, and perhaps when the focus moves away from the DateTimePicker entirely.

I would recommend creating an onAction property in your DateTimePicker class. This is pretty easy to do, you just have to call setEventHander(...) when the property changes. There's a nice example in Richard Bair's MoneyField (there's a lot of stuff in that link, just look at where he creates the onAction handler). You can then (internally to the DateTimePicker ) check for action events (or perhaps Enter key presses) on the individual controls and call fireEvent(new ActionEvent(...)) when that happens. Then your table cell implementation can register an onAction with the DateTimePicker and call commitEdit(...) .

Committing the edit on focus loss shouldn't be much harder. Maybe expose a ReadOnlyBooleanProperty hasFocus() in DateTimePicker :

private final ReadOnlyBooleanWrapper hasFocus = new ReadOnlyBooleanWrapper(this, "hasFocus");
hasFocus.bind(
     hourField.focusedProperty()
    .or(minuteField.focusedProperty())
    .or(datePicker.focusedProperty())
    .or(showTimePickerButton.focusedProperty()));

// ...
public ReadOnlyBooleanProperty hasFocusProperty() {
    return hasFocus.getReadOnlyProperty();
}

Then your cell implementation can observe that property and commit the edit if it changes to false . You may have to experiment with that a bit to get it to work satisfactorily.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM