从一个单元格编辑到另一个单元格时触发有效取消 CellEditEvent 的问题
Issue firing a valid cancel CellEditEvent when editing from one cell to another
我正在实现一个可编辑的 TableView
,它依赖于 CellEditEvents
来取消、启动和提交事件。
在下面的例子中,城市列是可编辑的,相应的事件被触发时:
- 取消:在文本字段中按转义键或文本字段失去焦点时。
- 提交:在文本字段中按回车键。
当我从编辑单元格移动到 RadioButton
时,开始和取消事件正确触发。但是从一个单元格遍历到另一个单元格时会抛出错误。
请检查下面的 gif(步骤)和控制台输出。
On City edit start :: TableDataObj{firstName=First Name 0, lastName=Last Name 0, city=City 0}
On City edit cancel :: TableDataObj{firstName=First Name 0, lastName=Last Name 0, city=City 0}
On City edit start :: TableDataObj{firstName=First Name 1, lastName=Last Name 1, city=City 1}
On City edit cancel :: TableDataObj{firstName=First Name 1, lastName=Last Name 1, city=City 1}
On City edit start :: TableDataObj{firstName=First Name 0, lastName=Last Name 0, city=City 0}
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at javafx.scene.control.TableColumn$CellEditEvent.getTableView(TableColumn.java:772)
at javafx.scene.control.TableColumn$CellEditEvent.getRowValue(TableColumn.java:829)
at com.thales.javafx.tableview.CancelTableEditDemo.lambda$buildTable(CancelTableEditDemo.java:84)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.control.TableCell.cancelEdit(TableCell.java:400)
at com.thales.javafx.tableview.CancelTableEditDemo$EditingCell.cancelEdit(CancelTableEditDemo.java:105)
at javafx.scene.control.TableCell.updateEditing(TableCell.java:565)
at javafx.scene.control.TableCell.lambda$new(TableCell.java:142)
at javafx.beans.WeakInvalidationListener.invalidated(WeakInvalidationListener.java:83)
at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:349)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.ReadOnlyObjectWrapper$ReadOnlyPropertyImpl.fireValueChangedEvent(ReadOnlyObjectWrapper.java:176)
at javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:142)
at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
at javafx.scene.control.TableView.setEditingCell(TableView.java:1145)
at javafx.scene.control.TableView.edit(TableView.java:1457)
at com.sun.javafx.scene.control.behavior.TableCellBehavior.edit(TableCellBehavior.java:106)
at com.sun.javafx.scene.control.behavior.TableCellBehavior.edit(TableCellBehavior.java:38)
at com.sun.javafx.scene.control.behavior.CellBehaviorBase.handleClicks(CellBehaviorBase.java:269)
at com.sun.javafx.scene.control.behavior.TableCellBehaviorBase.simpleSelect(TableCellBehaviorBase.java:218)
at com.sun.javafx.scene.control.behavior.TableCellBehaviorBase.doSelect(TableCellBehaviorBase.java:148)
at com.sun.javafx.scene.control.behavior.CellBehaviorBase.mousePressed(CellBehaviorBase.java:150)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase.handle(BehaviorSkinBase.java:95)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase.handle(BehaviorSkinBase.java:89)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
at javafx.scene.Scene$MouseHandler.access00(Scene.java:3485)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:352)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:275)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent5(GlassViewEventHandler.java:388)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:387)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:937)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null9(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)
我期望的是:当从 Cell-0 遍历到 Cell-1 时,它必须在开始编辑 Cell-1 之前为 Cell-0 触发有效的 Cancel 事件。
你们谁能帮我弄清楚 where/what 我失踪了吗?
下面是问题的完整工作代码:
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class CancelTableEditDemo extends Application {
public static void main(String... a) {
Application.launch(a);
}
@Override
public void start(final Stage primaryStage) throws Exception {
final ObservableList<TableDataObj> items = FXCollections.observableArrayList();
final int no = 2;
for (int i = 0; i < no; i++) {
final String firstName = "First Name " + i;
final String lastName = "Last Name " + i;
final String city = "City " + i;
items.add(new TableDataObj(i, firstName, lastName, city));
}
final TableView<TableDataObj> table = buildTable();
table.setItems(items);
final VBox root = new VBox(new RadioButton("Use this for focus changing"), table);
root.setSpacing(10);
root.setPadding(new Insets(10));
VBox.setVgrow(table, Priority.ALWAYS);
final Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Cancel Table Edit Demo");
primaryStage.show();
}
@SuppressWarnings("unchecked")
private TableView<TableDataObj> buildTable() {
final TableView<TableDataObj> tableView = new TableView<>();
tableView.setEditable(true);
final TableColumn<TableDataObj, Integer> idCol = new TableColumn<>();
idCol.setText("Id");
idCol.setCellValueFactory(param -> param.getValue().idProperty().asObject());
final TableColumn<TableDataObj, String> fnCol = new TableColumn<>();
fnCol.setText("First Name");
fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());
fnCol.setPrefWidth(150);
final TableColumn<TableDataObj, String> lnCol = new TableColumn<>();
lnCol.setText("Last Name");
lnCol.setCellValueFactory(param -> param.getValue().lastNameProperty());
lnCol.setPrefWidth(150);
final TableColumn<TableDataObj, String> cityCol = new TableColumn<>();
cityCol.setEditable(true);
cityCol.setText("City");
cityCol.setCellValueFactory(param -> param.getValue().cityProperty());
cityCol.setPrefWidth(150);
cityCol.setCellFactory(param -> {
final EditingCell<TableDataObj, String> cell = new EditingCell<>();
cell.setOnMouseClicked(e -> {
tableView.edit(cell.getTableRow().getIndex(), cityCol);
});
return cell;
});
cityCol.setOnEditStart(e -> {
System.out.println("On City edit start :: " + e.getRowValue());
});
cityCol.setOnEditCancel(e -> {
System.out.println("On City edit cancel :: " + e.getRowValue());
});
cityCol.setOnEditCommit(e -> {
System.out.println("On City edit commit :: val : " + e.getNewValue() + " :: " + e.getRowValue());
e.getRowValue().setCity(e.getNewValue());
});
tableView.getColumns().addAll(idCol, fnCol, lnCol, cityCol);
return tableView;
}
/**
* Editing Cell
*/
class EditingCell<T, S> extends TableCell<T, S> {
private TextField textField;
@Override
public void cancelEdit() {
super.cancelEdit();
updateItem(getItem(), getItem() == null);
}
@Override
public void commitEdit(final S newValue) {
super.commitEdit(newValue);
}
@Override
public void startEdit() {
super.startEdit();
updateItem(getItem(), getItem() == null);
textField.selectAll();
textField.requestFocus();
}
@Override
public void updateItem(final S item, final boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(textField);
} else {
if (isEditing()) {
if (textField == null) {
createTextField();
}
textField.setText(getString());
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
setText(item != null ? item.toString() : "");
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(getWidth() - getGraphicTextGap() * 2);
textField.setOnKeyPressed(keyEvent -> {
if (keyEvent.getCode() == KeyCode.ESCAPE) {
cancelEdit();
keyEvent.consume();
} else if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit((S) textField.getText()); // For now casting directly for testing
keyEvent.consume();
}
});
/* Cancel edit when loosing focus. */
textField.focusedProperty().addListener((obs, prevFocus, focused) -> {
if (!focused) {
cancelEdit();
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
/**
* Data object.
*/
class TableDataObj {
private final IntegerProperty id = new SimpleIntegerProperty();
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final StringProperty city = new SimpleStringProperty();
public TableDataObj(final int i, final String fn, final String ln, final String cty) {
setId(i);
setFirstName(fn);
setLastName(ln);
setCity(cty);
}
public StringProperty cityProperty() {
return city;
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getCity() {
return city.get();
}
public String getFirstName() {
return firstName.get();
}
public int getId() {
return id.get();
}
public String getLastName() {
return lastName.get();
}
public IntegerProperty idProperty() {
return id;
}
public StringProperty lastNameProperty() {
return lastName;
}
public void setCity(final String city1) {
city.set(city1);
}
public void setFirstName(final String firstName1) {
firstName.set(firstName1);
}
public void setId(final int idA) {
id.set(idA);
}
public void setLastName(final String lastName1) {
lastName.set(lastName1);
}
@Override
public String toString() {
return "TableDataObj{" +
"firstName=" + firstName.get() +
", lastName=" + lastName.get() +
", city=" + city.get() +
'}';
}
}
}
好的..因为在升级到 JavaFX 17 之前我必须寻找解决方法,下面是我想出的更改(对于 JavaFX 8):
首先,在 onCancelEdit 事件处理程序中添加对 TablePosition 的空检查,以确保不会因内部错误而引发错误。
cityCol.setOnEditCancel(e -> {
if (e.getTablePosition() != null) {
System.out.println("On City edit cancel :: " + e.getRowValue());
}
});
其次,为了触发正确的取消事件,我在条件不正确时明确触发取消事件。
@Override
public void cancelEdit() {
TablePosition<T, ?> editingCell = getTableView().getEditingCell();
super.cancelEdit();
// If the editingCell is null, then the editCancelEvent fired in super method has no impact. So explicitly firing a valid editCancelEvent.
if (editingCell == null) {
final TablePosition<T, S> pos = new TablePosition<>(getTableView(), getTableRow().getIndex(), getTableColumn());
Event.fireEvent(getTableColumn(), new TableColumn.CellEditEvent<>(getTableView(), pos, TableColumn.editCancelEvent(), null));
}
setText(getItem() != null ? getItem().toString() : "");
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
包含更改的完整工作演示如下:
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CancelTableEditDemo extends Application {
public static void main(String... a) {
Application.launch(a);
}
@Override
public void start(final Stage primaryStage) throws Exception {
final ObservableList<TableDataObj> items = FXCollections.observableArrayList();
final int no = 2;
for (int i = 0; i < no; i++) {
final String firstName = "First Name " + i;
final String lastName = "Last Name " + i;
final String city = "City " + i;
items.add(new TableDataObj(i, firstName, lastName, city));
}
final TableView<TableDataObj> table = buildTable();
table.setItems(items);
final VBox root = new VBox(new RadioButton("Use this for focus changing"), table);
root.setSpacing(10);
root.setPadding(new Insets(10));
VBox.setVgrow(table, Priority.ALWAYS);
final Scene sc = new Scene(root);
primaryStage.setScene(sc);
primaryStage.setTitle("Cancel Table Edit Demo");
primaryStage.show();
}
@SuppressWarnings("unchecked")
private TableView<TableDataObj> buildTable() {
final TableView<TableDataObj> tableView = new TableView<>();
tableView.setEditable(true);
final TableColumn<TableDataObj, Integer> idCol = new TableColumn<>();
idCol.setText("Id");
idCol.setCellValueFactory(param -> param.getValue().idProperty().asObject());
final TableColumn<TableDataObj, String> fnCol = new TableColumn<>();
fnCol.setText("First Name");
fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());
fnCol.setPrefWidth(150);
final TableColumn<TableDataObj, String> lnCol = new TableColumn<>();
lnCol.setText("Last Name");
lnCol.setCellValueFactory(param -> param.getValue().lastNameProperty());
lnCol.setPrefWidth(150);
final TableColumn<TableDataObj, String> cityCol = new TableColumn<>();
cityCol.setEditable(true);
cityCol.setText("City");
cityCol.setCellValueFactory(param -> param.getValue().cityProperty());
cityCol.setPrefWidth(150);
cityCol.setCellFactory(param -> {
final EditingCell<TableDataObj, String> cell = new EditingCell<>();
cell.setOnMouseClicked(e -> {
tableView.edit(cell.getTableRow().getIndex(), cityCol);
});
return cell;
});
cityCol.setOnEditStart(e -> {
System.out.println("On City edit start :: " + e.getRowValue());
});
cityCol.setOnEditCancel(e -> {
if (e.getTablePosition() != null) {
System.out.println("On City edit cancel :: " + e.getRowValue());
}
});
cityCol.setOnEditCommit(e -> {
System.out.println("On City edit commit :: val : " + e.getNewValue() + " :: " + e.getRowValue());
e.getRowValue().setCity(e.getNewValue());
});
tableView.getColumns().addAll(idCol, fnCol, lnCol, cityCol);
return tableView;
}
/**
* Editing Cell
*/
class EditingCell<T, S> extends TableCell<T, S> {
private TextField textField;
@Override
public void cancelEdit() {
TablePosition<T, ?> editingCell = getTableView().getEditingCell();
super.cancelEdit();
// If the editingCell is null, then the editCancelEvent fired in super method has no impact. So explicitly firing a valid editCancelEvent.
if (editingCell == null) {
final TablePosition<T, S> pos = new TablePosition<>(getTableView(), getTableRow().getIndex(), getTableColumn());
Event.fireEvent(getTableColumn(), new TableColumn.CellEditEvent<>(getTableView(), pos, TableColumn.editCancelEvent(), null));
}
setText(getItem() != null ? getItem().toString() : "");
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
@Override
public void startEdit() {
super.startEdit();
if (textField == null) {
createTextField();
}
textField.setText(getString());
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.selectAll();
textField.requestFocus();
}
@Override
public void updateItem(final S item, final boolean empty) {
super.updateItem(item, empty);
setGraphic(null);
if (empty) {
setText(null);
} else {
setText(item != null ? item.toString() : "");
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(getWidth() - getGraphicTextGap() * 2);
textField.setOnKeyPressed(keyEvent -> {
if (keyEvent.getCode() == KeyCode.ESCAPE) {
cancelEdit();
keyEvent.consume();
} else if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit((S) textField.getText()); // For now casting directly for testing
keyEvent.consume();
}
});
/* Cancel edit when loosing focus. */
textField.focusedProperty().addListener((obs, prevFocus, focused) -> {
if (!focused && isEditing()) {
cancelEdit();
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
/**
* Data object.
*/
class TableDataObj {
private final IntegerProperty id = new SimpleIntegerProperty();
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final StringProperty city = new SimpleStringProperty();
public TableDataObj(final int i, final String fn, final String ln, final String cty) {
setId(i);
setFirstName(fn);
setLastName(ln);
setCity(cty);
}
public StringProperty cityProperty() {
return city;
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getCity() {
return city.get();
}
public String getFirstName() {
return firstName.get();
}
public int getId() {
return id.get();
}
public String getLastName() {
return lastName.get();
}
public IntegerProperty idProperty() {
return id;
}
public StringProperty lastNameProperty() {
return lastName;
}
public void setCity(final String city1) {
city.set(city1);
}
public void setFirstName(final String firstName1) {
firstName.set(firstName1);
}
public void setId(final int idA) {
id.set(idA);
}
public void setLastName(final String lastName1) {
lastName.set(lastName1);
}
@Override
public String toString() {
return "TableDataObj{" +
"firstName=" + firstName.get() +
", lastName=" + lastName.get() +
", city=" + city.get() +
'}';
}
}
}
我正在实现一个可编辑的 TableView
,它依赖于 CellEditEvents
来取消、启动和提交事件。
在下面的例子中,城市列是可编辑的,相应的事件被触发时:
- 取消:在文本字段中按转义键或文本字段失去焦点时。
- 提交:在文本字段中按回车键。
当我从编辑单元格移动到 RadioButton
时,开始和取消事件正确触发。但是从一个单元格遍历到另一个单元格时会抛出错误。
请检查下面的 gif(步骤)和控制台输出。
On City edit start :: TableDataObj{firstName=First Name 0, lastName=Last Name 0, city=City 0}
On City edit cancel :: TableDataObj{firstName=First Name 0, lastName=Last Name 0, city=City 0}
On City edit start :: TableDataObj{firstName=First Name 1, lastName=Last Name 1, city=City 1}
On City edit cancel :: TableDataObj{firstName=First Name 1, lastName=Last Name 1, city=City 1}
On City edit start :: TableDataObj{firstName=First Name 0, lastName=Last Name 0, city=City 0}
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at javafx.scene.control.TableColumn$CellEditEvent.getTableView(TableColumn.java:772)
at javafx.scene.control.TableColumn$CellEditEvent.getRowValue(TableColumn.java:829)
at com.thales.javafx.tableview.CancelTableEditDemo.lambda$buildTable(CancelTableEditDemo.java:84)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.control.TableCell.cancelEdit(TableCell.java:400)
at com.thales.javafx.tableview.CancelTableEditDemo$EditingCell.cancelEdit(CancelTableEditDemo.java:105)
at javafx.scene.control.TableCell.updateEditing(TableCell.java:565)
at javafx.scene.control.TableCell.lambda$new(TableCell.java:142)
at javafx.beans.WeakInvalidationListener.invalidated(WeakInvalidationListener.java:83)
at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:349)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.ReadOnlyObjectWrapper$ReadOnlyPropertyImpl.fireValueChangedEvent(ReadOnlyObjectWrapper.java:176)
at javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:142)
at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
at javafx.scene.control.TableView.setEditingCell(TableView.java:1145)
at javafx.scene.control.TableView.edit(TableView.java:1457)
at com.sun.javafx.scene.control.behavior.TableCellBehavior.edit(TableCellBehavior.java:106)
at com.sun.javafx.scene.control.behavior.TableCellBehavior.edit(TableCellBehavior.java:38)
at com.sun.javafx.scene.control.behavior.CellBehaviorBase.handleClicks(CellBehaviorBase.java:269)
at com.sun.javafx.scene.control.behavior.TableCellBehaviorBase.simpleSelect(TableCellBehaviorBase.java:218)
at com.sun.javafx.scene.control.behavior.TableCellBehaviorBase.doSelect(TableCellBehaviorBase.java:148)
at com.sun.javafx.scene.control.behavior.CellBehaviorBase.mousePressed(CellBehaviorBase.java:150)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase.handle(BehaviorSkinBase.java:95)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase.handle(BehaviorSkinBase.java:89)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
at javafx.scene.Scene$MouseHandler.access00(Scene.java:3485)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:352)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:275)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent5(GlassViewEventHandler.java:388)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:387)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:937)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null9(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)
我期望的是:当从 Cell-0 遍历到 Cell-1 时,它必须在开始编辑 Cell-1 之前为 Cell-0 触发有效的 Cancel 事件。
你们谁能帮我弄清楚 where/what 我失踪了吗?
下面是问题的完整工作代码:
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class CancelTableEditDemo extends Application {
public static void main(String... a) {
Application.launch(a);
}
@Override
public void start(final Stage primaryStage) throws Exception {
final ObservableList<TableDataObj> items = FXCollections.observableArrayList();
final int no = 2;
for (int i = 0; i < no; i++) {
final String firstName = "First Name " + i;
final String lastName = "Last Name " + i;
final String city = "City " + i;
items.add(new TableDataObj(i, firstName, lastName, city));
}
final TableView<TableDataObj> table = buildTable();
table.setItems(items);
final VBox root = new VBox(new RadioButton("Use this for focus changing"), table);
root.setSpacing(10);
root.setPadding(new Insets(10));
VBox.setVgrow(table, Priority.ALWAYS);
final Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Cancel Table Edit Demo");
primaryStage.show();
}
@SuppressWarnings("unchecked")
private TableView<TableDataObj> buildTable() {
final TableView<TableDataObj> tableView = new TableView<>();
tableView.setEditable(true);
final TableColumn<TableDataObj, Integer> idCol = new TableColumn<>();
idCol.setText("Id");
idCol.setCellValueFactory(param -> param.getValue().idProperty().asObject());
final TableColumn<TableDataObj, String> fnCol = new TableColumn<>();
fnCol.setText("First Name");
fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());
fnCol.setPrefWidth(150);
final TableColumn<TableDataObj, String> lnCol = new TableColumn<>();
lnCol.setText("Last Name");
lnCol.setCellValueFactory(param -> param.getValue().lastNameProperty());
lnCol.setPrefWidth(150);
final TableColumn<TableDataObj, String> cityCol = new TableColumn<>();
cityCol.setEditable(true);
cityCol.setText("City");
cityCol.setCellValueFactory(param -> param.getValue().cityProperty());
cityCol.setPrefWidth(150);
cityCol.setCellFactory(param -> {
final EditingCell<TableDataObj, String> cell = new EditingCell<>();
cell.setOnMouseClicked(e -> {
tableView.edit(cell.getTableRow().getIndex(), cityCol);
});
return cell;
});
cityCol.setOnEditStart(e -> {
System.out.println("On City edit start :: " + e.getRowValue());
});
cityCol.setOnEditCancel(e -> {
System.out.println("On City edit cancel :: " + e.getRowValue());
});
cityCol.setOnEditCommit(e -> {
System.out.println("On City edit commit :: val : " + e.getNewValue() + " :: " + e.getRowValue());
e.getRowValue().setCity(e.getNewValue());
});
tableView.getColumns().addAll(idCol, fnCol, lnCol, cityCol);
return tableView;
}
/**
* Editing Cell
*/
class EditingCell<T, S> extends TableCell<T, S> {
private TextField textField;
@Override
public void cancelEdit() {
super.cancelEdit();
updateItem(getItem(), getItem() == null);
}
@Override
public void commitEdit(final S newValue) {
super.commitEdit(newValue);
}
@Override
public void startEdit() {
super.startEdit();
updateItem(getItem(), getItem() == null);
textField.selectAll();
textField.requestFocus();
}
@Override
public void updateItem(final S item, final boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(textField);
} else {
if (isEditing()) {
if (textField == null) {
createTextField();
}
textField.setText(getString());
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
setText(item != null ? item.toString() : "");
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(getWidth() - getGraphicTextGap() * 2);
textField.setOnKeyPressed(keyEvent -> {
if (keyEvent.getCode() == KeyCode.ESCAPE) {
cancelEdit();
keyEvent.consume();
} else if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit((S) textField.getText()); // For now casting directly for testing
keyEvent.consume();
}
});
/* Cancel edit when loosing focus. */
textField.focusedProperty().addListener((obs, prevFocus, focused) -> {
if (!focused) {
cancelEdit();
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
/**
* Data object.
*/
class TableDataObj {
private final IntegerProperty id = new SimpleIntegerProperty();
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final StringProperty city = new SimpleStringProperty();
public TableDataObj(final int i, final String fn, final String ln, final String cty) {
setId(i);
setFirstName(fn);
setLastName(ln);
setCity(cty);
}
public StringProperty cityProperty() {
return city;
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getCity() {
return city.get();
}
public String getFirstName() {
return firstName.get();
}
public int getId() {
return id.get();
}
public String getLastName() {
return lastName.get();
}
public IntegerProperty idProperty() {
return id;
}
public StringProperty lastNameProperty() {
return lastName;
}
public void setCity(final String city1) {
city.set(city1);
}
public void setFirstName(final String firstName1) {
firstName.set(firstName1);
}
public void setId(final int idA) {
id.set(idA);
}
public void setLastName(final String lastName1) {
lastName.set(lastName1);
}
@Override
public String toString() {
return "TableDataObj{" +
"firstName=" + firstName.get() +
", lastName=" + lastName.get() +
", city=" + city.get() +
'}';
}
}
}
好的..因为在升级到 JavaFX 17 之前我必须寻找解决方法,下面是我想出的更改(对于 JavaFX 8):
首先,在 onCancelEdit 事件处理程序中添加对 TablePosition 的空检查,以确保不会因内部错误而引发错误。
cityCol.setOnEditCancel(e -> {
if (e.getTablePosition() != null) {
System.out.println("On City edit cancel :: " + e.getRowValue());
}
});
其次,为了触发正确的取消事件,我在条件不正确时明确触发取消事件。
@Override
public void cancelEdit() {
TablePosition<T, ?> editingCell = getTableView().getEditingCell();
super.cancelEdit();
// If the editingCell is null, then the editCancelEvent fired in super method has no impact. So explicitly firing a valid editCancelEvent.
if (editingCell == null) {
final TablePosition<T, S> pos = new TablePosition<>(getTableView(), getTableRow().getIndex(), getTableColumn());
Event.fireEvent(getTableColumn(), new TableColumn.CellEditEvent<>(getTableView(), pos, TableColumn.editCancelEvent(), null));
}
setText(getItem() != null ? getItem().toString() : "");
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
包含更改的完整工作演示如下:
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CancelTableEditDemo extends Application {
public static void main(String... a) {
Application.launch(a);
}
@Override
public void start(final Stage primaryStage) throws Exception {
final ObservableList<TableDataObj> items = FXCollections.observableArrayList();
final int no = 2;
for (int i = 0; i < no; i++) {
final String firstName = "First Name " + i;
final String lastName = "Last Name " + i;
final String city = "City " + i;
items.add(new TableDataObj(i, firstName, lastName, city));
}
final TableView<TableDataObj> table = buildTable();
table.setItems(items);
final VBox root = new VBox(new RadioButton("Use this for focus changing"), table);
root.setSpacing(10);
root.setPadding(new Insets(10));
VBox.setVgrow(table, Priority.ALWAYS);
final Scene sc = new Scene(root);
primaryStage.setScene(sc);
primaryStage.setTitle("Cancel Table Edit Demo");
primaryStage.show();
}
@SuppressWarnings("unchecked")
private TableView<TableDataObj> buildTable() {
final TableView<TableDataObj> tableView = new TableView<>();
tableView.setEditable(true);
final TableColumn<TableDataObj, Integer> idCol = new TableColumn<>();
idCol.setText("Id");
idCol.setCellValueFactory(param -> param.getValue().idProperty().asObject());
final TableColumn<TableDataObj, String> fnCol = new TableColumn<>();
fnCol.setText("First Name");
fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());
fnCol.setPrefWidth(150);
final TableColumn<TableDataObj, String> lnCol = new TableColumn<>();
lnCol.setText("Last Name");
lnCol.setCellValueFactory(param -> param.getValue().lastNameProperty());
lnCol.setPrefWidth(150);
final TableColumn<TableDataObj, String> cityCol = new TableColumn<>();
cityCol.setEditable(true);
cityCol.setText("City");
cityCol.setCellValueFactory(param -> param.getValue().cityProperty());
cityCol.setPrefWidth(150);
cityCol.setCellFactory(param -> {
final EditingCell<TableDataObj, String> cell = new EditingCell<>();
cell.setOnMouseClicked(e -> {
tableView.edit(cell.getTableRow().getIndex(), cityCol);
});
return cell;
});
cityCol.setOnEditStart(e -> {
System.out.println("On City edit start :: " + e.getRowValue());
});
cityCol.setOnEditCancel(e -> {
if (e.getTablePosition() != null) {
System.out.println("On City edit cancel :: " + e.getRowValue());
}
});
cityCol.setOnEditCommit(e -> {
System.out.println("On City edit commit :: val : " + e.getNewValue() + " :: " + e.getRowValue());
e.getRowValue().setCity(e.getNewValue());
});
tableView.getColumns().addAll(idCol, fnCol, lnCol, cityCol);
return tableView;
}
/**
* Editing Cell
*/
class EditingCell<T, S> extends TableCell<T, S> {
private TextField textField;
@Override
public void cancelEdit() {
TablePosition<T, ?> editingCell = getTableView().getEditingCell();
super.cancelEdit();
// If the editingCell is null, then the editCancelEvent fired in super method has no impact. So explicitly firing a valid editCancelEvent.
if (editingCell == null) {
final TablePosition<T, S> pos = new TablePosition<>(getTableView(), getTableRow().getIndex(), getTableColumn());
Event.fireEvent(getTableColumn(), new TableColumn.CellEditEvent<>(getTableView(), pos, TableColumn.editCancelEvent(), null));
}
setText(getItem() != null ? getItem().toString() : "");
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
@Override
public void startEdit() {
super.startEdit();
if (textField == null) {
createTextField();
}
textField.setText(getString());
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.selectAll();
textField.requestFocus();
}
@Override
public void updateItem(final S item, final boolean empty) {
super.updateItem(item, empty);
setGraphic(null);
if (empty) {
setText(null);
} else {
setText(item != null ? item.toString() : "");
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(getWidth() - getGraphicTextGap() * 2);
textField.setOnKeyPressed(keyEvent -> {
if (keyEvent.getCode() == KeyCode.ESCAPE) {
cancelEdit();
keyEvent.consume();
} else if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit((S) textField.getText()); // For now casting directly for testing
keyEvent.consume();
}
});
/* Cancel edit when loosing focus. */
textField.focusedProperty().addListener((obs, prevFocus, focused) -> {
if (!focused && isEditing()) {
cancelEdit();
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
/**
* Data object.
*/
class TableDataObj {
private final IntegerProperty id = new SimpleIntegerProperty();
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final StringProperty city = new SimpleStringProperty();
public TableDataObj(final int i, final String fn, final String ln, final String cty) {
setId(i);
setFirstName(fn);
setLastName(ln);
setCity(cty);
}
public StringProperty cityProperty() {
return city;
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getCity() {
return city.get();
}
public String getFirstName() {
return firstName.get();
}
public int getId() {
return id.get();
}
public String getLastName() {
return lastName.get();
}
public IntegerProperty idProperty() {
return id;
}
public StringProperty lastNameProperty() {
return lastName;
}
public void setCity(final String city1) {
city.set(city1);
}
public void setFirstName(final String firstName1) {
firstName.set(firstName1);
}
public void setId(final int idA) {
id.set(idA);
}
public void setLastName(final String lastName1) {
lastName.set(lastName1);
}
@Override
public String toString() {
return "TableDataObj{" +
"firstName=" + firstName.get() +
", lastName=" + lastName.get() +
", city=" + city.get() +
'}';
}
}
}