gpt4 book ai didi

javafx - 如何创建复杂对象的 ListView 并允许编辑对象上的字段?

转载 作者:行者123 更新时间:2023-12-04 22:44:00 25 4
gpt4 key购买 nike

我想要一个 Person 对象的 JavaFX ListView。我希望列表仅显示名称并允许编辑名称。在提交对名称的编辑后,它还应该保留每个对象中的其他字段。您将如何在 JavaFX 中惯用地做到这一点?

我有以下代码,它有效,但它有点不稳定,因为它有一个 StringConverter 可以将一种方式从 Person 转换为人名的字符串,然后不进行反向转换,而是依赖于列表单元格 commitEdit 方法取一串名字并将其设置在适当的人身上。

这是代码:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;

public class Main extends Application {

public static void main(String[] args) {
launch(args);
}

@Override
public void start(Stage stage) throws Exception {
stage.setTitle("My Custom List View");
ObservableList<Person> people = FXCollections.observableArrayList(
new Person("John Doe", "123 New York"),
new Person("Jane Doe", "456 San Francisco")
);
ListView<Person> listView = new ListView();
listView.setCellFactory(new CustomCellFactory());
listView.setEditable(true);
listView.setItems(people);
Scene scene = new Scene(listView,400,300);
stage.setScene(scene);
stage.show();
}

public static class CustomCellFactory implements Callback<ListView<Person>,ListCell<Person>> {
@Override
public ListCell<Person> call(ListView param) {
TextFieldListCell<Person> cell = new TextFieldListCell() {
@Override
public void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
System.out.println("updating item: "+item.toString());
setText(((Person) item).getName());
} else {
setText(null);
}
}
@Override
public void commitEdit(Object newName) {
((Person)getItem()).setName((String)newName);
super.commitEdit(getItem());
}
};
cell.setConverter(new StringConverter() {
@Override
public String toString(Object person) {
return ((Person)person).getName();
}
@Override
public Object fromString(String string) {
return string;
}
});
return cell;
}
}

public static class Person {
private String name;
private String address;
public Person(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String toString() {
return name+" at "+address;
}
}
}

最佳答案

TextFieldListCell 只是 ListCell 的一种便利实现,它为列表单元格提供了最常见的编辑形式(即,如果列表中的项目是 String s,或者可以轻松转换为字符串的对象)。您经常会发现您需要更具体的编辑(例如,您通常希望使用 TextFormatter 过滤编辑文本字段中允许的文本),在这种情况下,您只需自己实现 ListCell。我认为这是一种情况,总的来说,从头开始实现 ListCell 更有意义。

似乎您可以强制 TextFieldListCell 用于此用例,使用:

    listView.setCellFactory(lv -> {
TextFieldListCell<Person> cell = new TextFieldListCell<Person>();
cell.setConverter(new StringConverter<Person>() {
@Override
public String toString(Person person) {
return person.getName();
}
@Override
public Person fromString(String string) {
Person person = cell.getItem();
person.setName(string);
return person ;
}
});
return cell;
});

(请注意,在您的代码中,您的 updateItem() 方法等效于 TextFieldListCell 中已经实现的方法,因此它是多余的,并且 commitEdit(...) 中的额外功能现在位于(类型安全) StringConverter 中,因此不再需要子类。)

这只是感觉有点脆弱,因为它依赖于从文本字段提交新值的特定实现及其与字符串转换器的交互,但它在测试中似乎工作正常。

但是,我对此的偏好是直接自己实现 ListCell,因为它使您可以完全控制文本字段和编辑过程之间的交互。这非常简单:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;

public class Main extends Application {

@Override
public void start(Stage primaryStage) {
ListView<Person> listView = new ListView<>();
ObservableList<Person> people = FXCollections.observableArrayList(
new Person("John Doe", "123 New York"),
new Person("Jane Doe", "456 San Francisco")
);
listView.setEditable(true);
listView.setItems(people);

listView.setCellFactory(lv -> new ListCell<Person>() {
private TextField textField = new TextField() ;

{
textField.setOnAction(e -> {
commitEdit(getItem());
});
textField.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
}

@Override
protected void updateItem(Person person, boolean empty) {
super.updateItem(person, empty);
if (empty) {
setText(null);
setGraphic(null);
} else if (isEditing()) {
textField.setText(person.getName());
setText(null);
setGraphic(textField);
} else {
setText(person.getName());
setGraphic(null);
}
}

@Override
public void startEdit() {
super.startEdit();
textField.setText(getItem().getName());
setText(null);
setGraphic(textField);
textField.selectAll();
textField.requestFocus();
}

@Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem().getName());
setGraphic(null);
}

@Override
public void commitEdit(Person person) {
super.commitEdit(person);
person.setName(textField.getText());
setText(textField.getText());
setGraphic(null);
}
});

// for debugging:
listView.setOnMouseClicked(e -> {
if (e.getClickCount() == 2) {
listView.getItems().forEach(p -> System.out.println(p.getName()));
}
});

Scene scene = new Scene(listView,400,300);
primaryStage.setScene(scene);
primaryStage.show();

}

public static class Person {
private String name;
private String address;
public Person(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String toString() {
return name+" at "+address;
}
}

public static void main(String[] args) {
launch(args);
}
}

如果您可能经常需要这种功能,您可以轻松创建一个可重用的类:
import java.util.function.BiFunction;
import java.util.function.Function;

import javafx.scene.control.ListCell;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

public class EditingListCell<T> extends ListCell<T> {
private final TextField textField ;
private final Function<T, String> propertyAccessor ;

public EditingListCell(Function<T, String> propertyAccessor, BiFunction<String, T, T> updater) {
this.propertyAccessor = propertyAccessor ;
this.textField = new TextField();

textField.setOnAction(e -> {
T newItem = updater.apply(textField.getText(), getItem());
commitEdit(newItem);
});
textField.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
}

@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else if (isEditing()) {
textField.setText(propertyAccessor.apply(item));
setText(null);
setGraphic(textField);
} else {
setText(propertyAccessor.apply(item));
setGraphic(null);
}
}

@Override
public void startEdit() {
super.startEdit();
textField.setText(propertyAccessor.apply(getItem()));
setText(null);
setGraphic(textField);
textField.selectAll();
textField.requestFocus();
}

@Override
public void cancelEdit() {
super.cancelEdit();
setText(propertyAccessor.apply(getItem()));
setGraphic(null);
}

@Override
public void commitEdit(T item) {
super.commitEdit(item);
getListView().getItems().set(getIndex(), item);
setText(propertyAccessor.apply(getItem()));
setGraphic(null);
}
}

然后你只需要
listView.setCellFactory(lv -> new EditingListCell<>(
Person::getName,
(text, person) -> {
person.setName(text);
return person ;
})
);

关于javafx - 如何创建复杂对象的 ListView 并允许编辑对象上的字段?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35963888/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com