我希望我的可编辑TableView的行为与Excel工作表完全相同。这意味着双击任何单元格都应该将其置于编辑状态,并将插入符号放在用户在编辑时显示的文本字段中单击的位置(如果用户在文本结束后单击任意位置,则在文本末尾) )。
据我所知,奇怪命名的方法positionCaret(int)
是用于将插入符号放在TextField
中的方法。但是这个位置似乎是在字符中指定的,而不是在MouseEvent.getX()
中可以获得的x位置值。
此外,只需尝试使用例如positionCaret(int)
虚拟值“2”,无论如何都不太有效。有时插入符号位于第二个字符之后,有时它只是放在文本的开头。由于它似乎是完全随机的,我猜它是某种线程问题。
我在下面提供了一个MCVE。可编辑单元格的代码是TableView tutorial on docs.oracle的示例12-11的略微修改版本。
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
public class TestEditableTable extends Application {
public void start(Stage stage) {
TableView<ObservableList<StringProperty>> table = new TableView<>();
table.setEditable(true);
table.getSelectionModel().setCellSelectionEnabled(true);
// Makes sure that the cell isn't put into editing mode unless it is double clicked.
table.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
// Only necessary in windows 7.
if (event.isControlDown()) {
return;
}
if (table.getEditingCell() == null) {
table.getSelectionModel().clearSelection();
}
}
});
// Dummy columns
ObservableList<String> columns = FXCollections.observableArrayList("Column1", "Column2", "Column3", "Column4",
"Column5");
// Dummy data
ObservableList<StringProperty> row1 = FXCollections.observableArrayList(new SimpleStringProperty("Cell1"),
new SimpleStringProperty("Cell2"), new SimpleStringProperty("0"), new SimpleStringProperty("Cell4"),
new SimpleStringProperty("0"));
ObservableList<StringProperty> row2 = FXCollections.observableArrayList(new SimpleStringProperty("Cell1"),
new SimpleStringProperty("Cell2"), new SimpleStringProperty("1"), new SimpleStringProperty("Cell4"),
new SimpleStringProperty("2"));
ObservableList<ObservableList<StringProperty>> data = FXCollections.observableArrayList(row1, row2);
for (int i = 0; i < columns.size(); i++) {
final int j = i;
TableColumn<ObservableList<StringProperty>, String> col = new TableColumn<>(columns.get(i));
col.setCellValueFactory(param -> param.getValue().get(j));
col.setCellFactory(e -> new EditingCell());
table.getColumns().add(col);
}
table.setItems(data);
Scene scene = new Scene(table);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
/**
* An editable cell.
*/
class EditingCell extends TableCell<ObservableList<StringProperty>, String> {
private TextField textField;
@Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
}
}
@Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem());
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(item);
}
setText(null);
setGraphic(textField);
} else {
setText(item);
setGraphic(null);
}
}
}
// Instantiates the text field.
private void createTextField() {
textField = new TextField(getItem());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textField.setPadding(new Insets(0));
// If the user defocus the textfield the edit should be commited. However, this only works properly if the focus is lost to another cell on the same row as far as I can tell.
textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) {
if (!arg2) {
commitEdit(textField.getText());
}
}
});
setOnMousePressed(e -> {
// Gets the x coordinate of the cursor relative to the text field. Isn't used at the moment, but might prove useful.
Double x = e.getX();
Platform.runLater(() -> {
// The text field isn't focused unless we do this.
textField.requestFocus();
// Just testing positionCaret with some dummy value, doesn't seem to work properly.
textField.positionCaret(2);
});
});
}
}
}
答案 0 :(得分:1)
也许考虑使用一个总是处于“编辑模式”的表格单元格实现:即它始终只显示一个文本字段,该文本字段双向绑定到相应的属性。这类似于标准CheckBoxTableCell
的实现方式。与该单元格实现一样,单元格永远不会进入和退出“编辑模式”,因此永远不会调用startEdit
,commitEdit
和cancelEdit
方法,CellEditEvent
s永远不会在列上触发,但显示的数据会不断与模型中的属性同步。
以下是一个示例:
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class AlwaysEditingTable extends Application {
private final static int NUM_COLUMNS = 10 ;
@Override
public void start(Stage primaryStage) {
TableView<List<String>> table = new TableView<>();
FilteredList<List<String>> filteredList = populateTableData(table);
for (int colIndex = 0; colIndex < NUM_COLUMNS; colIndex++) {
final int c = colIndex ;
table.getColumns().add(column("Column "+(colIndex+1),
(List<String> row) -> new ReadOnlyStringWrapper(row.get(c)),
(List<String> row, String newText) -> row.set(c, newText)));
}
Button debugButton = new Button("Print content");
debugButton.setOnAction(e ->
table.getItems().stream().map(row -> String.join("\t", row)).forEach(System.out::println)
);
TextField filter = new TextField();
filter.textProperty().addListener((obs, oldValue, newValue) ->
filteredList.setPredicate(row -> row.stream().filter(data -> data.startsWith(newValue)).findAny().isPresent())
);
filter.setPromptText("Enter filter");
BorderPane root = new BorderPane(table, filter, null, debugButton, null);
BorderPane.setMargin(debugButton, new Insets(10));
BorderPane.setAlignment(debugButton, Pos.CENTER);
primaryStage.setScene(new Scene(root, 880, 600));
primaryStage.show();
}
private <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property, BiConsumer<S,String> updater) {
TableColumn<S,T> column = new TableColumn<>(title);
column.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
column.setCellFactory(c -> new AlwaysEditingCell<S,T>(updater));
return column ;
}
private FilteredList<List<String>> populateTableData(TableView<List<String>> table) {
ObservableList<List<String>> data = FXCollections.observableArrayList() ;
for (int i = 1 ; i <= 30 ; i++) {
List<String> row = new ArrayList<>();
for (int colIndex = 0; colIndex < NUM_COLUMNS ; colIndex++) {
row.add("Data ["+(colIndex+1)+", "+i+"]");
}
data.add(row);
}
FilteredList<List<String>> filteredList = new FilteredList<>(data);
table.setItems(filteredList);
return filteredList ;
}
public static class AlwaysEditingCell<S,T> extends TableCell<S,T> {
private final TextField textField ;
/**
*
* @param updater A function that acts on the current row value and the
* text in the text field to update the row value according to the new
* text. This function is invoked whenever the text in the text field changes.
*/
public AlwaysEditingCell(BiConsumer<S,String> updater) {
textField = new TextField();
this.emptyProperty().addListener((obs, wasEmpty, isNowEmpty) -> {
if (isNowEmpty) {
setGraphic(null);
} else {
setGraphic(textField);
}
});
textField.textProperty().addListener((obs, oldText, newText) ->
updater.accept(getTableView().getItems().get(getIndex()), newText));
}
@Override
public void updateItem(T item, boolean empty) {
if (empty) {
setGraphic(null);
} else {
String value = item.toString() ;
if (! value.equals(textField.getText())) {
textField.setText(value);
}
setGraphic(textField);
}
}
}
public static void main(String[] args) {
launch(args);
}
}