I am working on a client-server solution and I have a ListView with my connected clients. The user should be able to remotely rename the clients just by editing the name in the ListView. I have read a lot about editing ListView cells, but I haven't found any good example where I can just change a member attribute of my class. Most of the examples are with a list of strings, and in my opinion this is not modern software development, if the items inside of the ListView are more than just strings.
What I want to do is to change the attribute name
inside of my client.
class Client {
private String name;
public String getName(){
return name;
}
public String setName(String val){
name = val;
}
}
I don't care, if I have to implement the member name
as a JavaFX property (in fact I already have done that, but left it out for readability and simplicity).
In case you are interested, I have tried to use the TextFieldListCell cell factory:
this.listViewClients.setCellFactory(TextFieldListCell
.forListView(new NetworkClientStringConverter(this.clientController)));
But there are some tricky things i found out:
fromString
).
StringConverter
. (This is not nice, I would prefer to just have access to the client I am changing.)This is a bit tricky using the standard TextFieldListCell
, but the following seems to work.
Assume throughout this question there's a Client
model class:
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Client {
private final StringProperty name = new SimpleStringProperty();
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
}
(fwiw I think everything still applies here if you use standard Java Bean properties instead of JavaFX Properties).
Create a converter class that references the cell:
public class ClientConverter extends StringConverter<Client> {
private final ListCell<Client> cell;
public ClientConverter(ListCell<Client> cell) {
this.cell = cell ;
}
@Override
public String toString(Client client) {
return client.getName();
}
@Override
public Client fromString(String string) {
Client client = cell.getItem();
client.setName(string);
return client ;
}
}
and then you can do
listViewClients.setCellFactory(lv -> {
TextFieldListCell<Client> cell = new TextFieldListCell<>();
cell.setConverter(new ClientConverter(cell));
return cell ;
});
SSCCE:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class EditableListView extends Application {
@Override
public void start(Stage primaryStage) {
ListView<Client> listViewClients = new ListView<>();
for (int i= 1 ; i <= 20; i++) {
Client c = new Client();
c.setName("Client "+i);
listViewClients.getItems().add(c);
}
listViewClients.setEditable(true);
listViewClients.setCellFactory(lv -> {
TextFieldListCell<Client> cell = new TextFieldListCell<>();
cell.setConverter(new ClientConverter(cell));
return cell ;
});
// debug:
Button debug = new Button("Show clients");
debug.setOnAction(e -> listViewClients.getItems().stream().map(Client::getName).forEach(System.out::println));
BorderPane root = new BorderPane(listViewClients, null, null, debug, null);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static class ClientConverter extends StringConverter<Client> {
private final ListCell<Client> cell;
public ClientConverter(ListCell<Client> cell) {
this.cell = cell ;
}
@Override
public String toString(Client client) {
return client.getName();
}
@Override
public Client fromString(String string) {
Client client = cell.getItem();
client.setName(string);
return client ;
}
}
public static void main(String[] args) {
launch(args);
}
}
This feels like a bit of a hack though, because it's not really the job of the StringConverter
to update the data in the model (Client
) class. I would probably favor just creating the cell implementation from scratch here.
It's a little more code, but this feels safer:
public class ClientListCell extends ListCell<Client> {
private final TextField textField = new TextField();
public ClientListCell() {
textField.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
textField.setOnAction(e -> {
getItem().setName(textField.getText());
setText(textField.getText());
setContentDisplay(ContentDisplay.TEXT_ONLY);
});
setGraphic(textField);
}
@Override
protected void updateItem(Client client, boolean empty) {
super.updateItem(client, empty);
if (isEditing()) {
textField.setText(client.getName());
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
setContentDisplay(ContentDisplay.TEXT_ONLY);
if (empty) {
setText(null);
} else {
setText(client.getName());
}
}
}
@Override
public void startEdit() {
super.startEdit();
textField.setText(getItem().getName());
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.requestFocus();
textField.selectAll();
}
@Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem().getName());
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
and the SSCCE using this cell implementation is
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
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.scene.layout.BorderPane;
import javafx.stage.Stage;
public class EditableListView extends Application {
@Override
public void start(Stage primaryStage) {
ListView<Client> listViewClients = new ListView<>();
for (int i= 1 ; i <= 20; i++) {
Client c = new Client();
c.setName("Client "+i);
listViewClients.getItems().add(c);
}
listViewClients.setEditable(true);
listViewClients.setCellFactory(lv -> new ClientListCell());
// debug:
Button debug = new Button("Show clients");
debug.setOnAction(e -> listViewClients.getItems().stream().map(Client::getName).forEach(System.out::println));
BorderPane root = new BorderPane(listViewClients, null, null, debug, null);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With