我正尝试使用Oracle较差的教程来创建可编辑单元格。我发现他们的EditCell
类仅在我单击当前编辑的同一行或任何行之外时才更新。如果我单击另一行,则会取消编辑。这是本教程的链接,在它的结尾您可以找到EditCell
类,但这不是这个问题的重点:
https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/table-view.htm
此类出于创建目的创建TextField
。单击另一行将启动cancel()
方法。并有以下代码行:
setText((String( getItem());
阻止编辑。我将其替换为:
setText((String) textField.getText());
并立即进行编辑。但是在再次编辑此单元格后,旧值将被加载到TextField
中。我猜ObservableList
在第一次编辑后不会更新。
这是FXML代码:
<GridPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
<TableView GridPane.columnIndex="0" GridPane.rowIndex="1" items="${controller.data}" editable="true">
<columns>
<TableColumn fx:id="colName" text="name">
<cellValueFactory>
<PropertyValueFactory property="Name"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="colSurname" text="surname">
<cellValueFactory>
<PropertyValueFactory property="Surname"/>
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
</GridPane>
在控制器中,我声明ObservableList
:
public class Controller {
@FXML
private TableColumn<Person, String> colName;
@FXML
private TableColumn<Person, String> colSurname;
@FXML
private ObservableList<Person> data;
public Controller(){
data = FXCollections.observableArrayList(
new Person("John", "S."),
new Person("Jane", "S.")
);
}
public TableColumn<Person, String> getColName() {
return colName;
}
public void setColName(TableColumn<Person, String> colName) {
this.colName = colName;
}
public TableColumn<Person, String> getColSurname() {
return colSurname;
}
public void setColSurname(TableColumn<Person, String> colSurname) {
this.colSurname = colSurname;
}
public ObservableList<Person> getData() {
return data;
}
public void setData(ObservableList<Person> data) {
this.data = data;
}
}
Person.java代码:
public class Person {
private final SimpleStringProperty name;
private final SimpleStringProperty surname;
public Person(String name, String surname){
this.name = new SimpleStringProperty(name);
this.surname = new SimpleStringProperty(surname);
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getSurname() {
return surname.get();
}
public SimpleStringProperty surnameProperty() {
return surname;
}
public void setSurname(String surname) {
this.surname.set(surname);
}
}
在Main
中,我声明了控制器和可编辑列:
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root = (Parent) loader.load();
primaryStage.setScene(new Scene(root, 300, 275));
Controller controller = loader.getController();
TableColumn<Person, String> colName = controller.getColName();
Callback<TableColumn<Person, String>, TableCell<Person, String>> cellFactory =
(TableColumn<Person, String> p) -> new sample.EditCell();
colName.setCellFactory(cellFactory);
colName.setOnEditCommit(
(TableColumn.CellEditEvent<Person, String> t) -> {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setName(t.getNewValue());
});
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
我需要与ObservableList
绑定单元格吗?还是刷新一下?如何更新data
以使TextField始终填充实际值?
这里是整个EditCell
类:
class EditCell extends TableCell<Person, String> {
private TextField textField;
public EditCell() {
}
@Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
textField.selectAll();
}
}
@Override
public void cancelEdit() {
super.cancelEdit();
setText((String) getItem());
//setText((String) textField.getText());
//This line updates cell, but textField keeps old value after next edit.
setGraphic(null);
}
@Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(null);
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textField.focusedProperty().addListener(
(ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2) -> {
if (!arg2) {
commitEdit(textField.getText());
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
答案 0 :(得分:1)
编辑时,onEditCommit
处理程序将在提交编辑时得到通知(毫不奇怪)。该处理程序负责将新值写入模型(在您的情况下为Person
)。发生这种情况时,TableView
将自动更新以显示新值。
您的解决方案无法在取消编辑时将Cell
的文本设置为TextField
的值。最终,一旦以某种方式触发了更新,Cell
将刷新以显示由模型提供(由cellValueFactory
获得)的 real 数据。除此之外,您实际上还没有更新模型,因此假定的编辑只是视觉上的事情。
您链接到的tutorial出现问题。其中最大的假设是,当TextField
失去焦点时,您可以成功提交新值。在您遇到的情况并非如此。通过查看以下问题,您可以看到许多其他人都遇到了此问题:TableView doesn't commit values on focus lost event。该问题的答案提供了许多方法来 hack 解决该问题。有些人还指出了错误报告,表明“失去焦点时不提交”行为实际上是意料之外的。但是,从JavaFX 11.0.2开始,这些错误尚未得到修复。
这是什么意思:
textField.focusedProperty().addListener(
(ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2) -> {
if (!arg2) {
commitEdit(textField.getText());
}
});
永远不会提交修改。您(但实际上是本教程)没有提供任何工作方式来提交新值,因为在调用if (!arg2) { commitEdit(...); }
时取消了编辑。由于取消了编辑,因此不会触发提交编辑事件,并且您的TableColumn
无法将新值写入模型项。尽管无法解决失去焦点时没有提交的问题,但是您可以做的是在您的onAction
上添加TextField
处理程序以提交编辑。您可能还想提供一种通过键盘取消编辑的方法。看起来像这样:
textField.setOnAction(event -> {
commitEdit(textField.getText());
event.consume();
}
textField.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.ESCAPE) {
cancelEdit();
event.consume();
}
}
这将在按下 Enter 键时提交编辑,并在按下 Esc 键时取消编辑。
请注意,TextFieldTableCell
已经提供了开箱即用的功能,无需滚动自己的EditCell
实现。但是,如果您想在失去焦点时进行编辑,则必须查看TableView doesn't commit values on focus lost event的答案(或其相关问题),并尝试使用给定的解决方案之一(黑客)
此外,如以下文档中所述,您不必提供自己的onEditCommit
处理程序即可将新值写入模型-TableColumn
默认情况下会这样做(假设{ {1}}返回cellValueFactory
)。
也许阅读TableView
的文档比您正在阅读的教程更有益,或者至少是对它的补充:
编辑
此控件支持值的内联编辑,并且本节尝试概述可用的API以及应如何使用它们。
首先,与不编辑单元格相比,单元格编辑最通常需要不同的用户界面。这是使用
WritableValue
实现的责任。对于Cell
,强烈建议按{TableView
而不是按行进行编辑,因为您经常希望用户不同地编辑每个列值,并且这种方法允许特定于每列。您可以选择该单元格是否永久处于编辑状态(例如,TableColumn
单元格是常见的),还是在编辑开始时切换到其他UI(例如,当在单元格上双击时)要知道何时请求对单元格进行编辑,只需覆盖
CheckBox
方法,并适当地更新单元格的文本和图形属性(例如,将文本设置为null并将图形设置为{{ 1}})。此外,您还应该覆盖Cell.startEdit()
,以在编辑结束时将UI重置为其原始视觉状态。在这两种情况下,重要的是还要确保调用TextField
方法以使单元格执行进入或退出其编辑模式所必须执行的所有任务。单元格处于编辑状态后,您最可能感兴趣的下一件事是如何提交或取消正在进行的编辑。作为电池工厂提供者,这是您的责任。您的单元实现将基于用户输入(例如,当用户按下键盘上的 Enter 或 ESC 键时)知道编辑何时结束。发生这种情况时,您有责任视情况致电
Cell.cancelEdit()
或super
。调用
Cell.commitEdit(Object)
时会向Cell.cancelEdit()
触发一个事件,您可以通过Cell.commitEdit(Object)
添加一个TableView
来观察事件。同样,您也可以观察编辑开始和取消的编辑事件。默认情况下,
EventHandler
编辑提交处理程序为非null,默认处理程序尝试覆盖当前正在编辑的行中项目的属性值。之所以能够做到这一点,是因为在新值中传递了TableColumn.setOnEditCommit(javafx.event.EventHandler)
方法,并通过触发的TableColumn
将其传递给了编辑提交处理程序。只需调用Cell.commitEdit(Object)
来检索此值即可。请务必注意,如果您使用自己的
CellEditEvent
调用TableColumn.CellEditEvent.getNewValue()
,则将删除默认处理程序。除非您随后处理对属性(或相关数据源)的写回操作,否则将不会发生任何事情。您可以使用TableColumn.setOnEditCommit(javafx.event.EventHandler)
方法来解决此问题,方法是将EventHandler
作为第二个参数添加一个TableColumnBase.addEventHandler(javafx.event.EventType, javafx.event.EventHandler)
TableColumn.editCommitEvent()
。使用此方法,您不会替换默认的实现,但会在发生编辑提交时收到通知。