Question

I want to use a custom control (ClientControl in the code) in my TableView. Therefore I created a class ClientCell:

    public class NewClientCell extends TableCell<Client, Client> {
    private final ClientControl cc;

    public NewClientCell(ObservableList<Client> suggestions) {
        cc = new ClientControl(this.getItem(), suggestions);
        this.setAlignment(Pos.CENTER_LEFT);
        this.setGraphic(cc);
        this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
    }
@Override
protected void updateItem(Client c, boolean empty) {
    super.updateItem(c, empty);
    if(!empty){
        setGraphic(cc);
    }
}
}

In the main program I use the following code to fill the table:

        TableColumn<Client, Client> clmClients = new TableColumn<>("Klient");
    clmClients.setCellFactory(new Callback<TableColumn<Client, Client>, TableCell<Client, Client>>() {
        @Override
        public TableCell<Client, Client> call(TableColumn<Client, Client> p) {
            return new NewClientCell(suggestions);
        };
    });

    clmClients.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Client, Client>, ObservableValue<Client>>() { 
        @Override
        public ObservableValue<Client> call(TableColumn.CellDataFeatures<Client, Client> p) {
            return new SimpleObjectProperty<Client>(p.getValue());
        }
    });
    getColumns().add(clmClients);

The data in the table comes from an ObservableList and is initialized correct.

My problem now is that the custom control needs an Client-Object which it should get out of the ObservableList, but "this.getItem()" always returns null.

How do I get the Client objects correctly into the custom control?

Thanks!

EDIT

Here's the constructor of ClientControl:

    public ClientControl(Client client, ObservableList<Client> suggestions) {
    setClient(client);
    setSuggestions(suggestions);
    FXMLLoader loader = new FXMLLoader(getClass().getResource("ClientControl.fxml"));
    loader.setRoot(this);
    loader.setController(this);
    try {
        loader.load();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    initTextField();
    setLabelText(client.toString());
}

The method setClient is a simple setter method (this.client = client;). The variables client and suggestions are this simple defined:

    private ObservableList<Client> suggestions;
private Client client;
Was it helpful?

Solution

AFAIK, you should instantiate any controls in the constructor as you did, so that they are only created once (remember that cells get reused for different locations).

But then you need to override one or more of the other methods such as updateItem to get the data from the current item to render.

EDIT

Well, you're assigning the same control without changing it over and over again. Rather than setting the graphics in the updateItem method, set the item property of the client control:

@Override
protected void updateItem(Client c, boolean empty) {
    super.updateItem(c, empty);
    if(!empty){
        cc.setClient(c);
    } else {
        cc.setClient(null);
    }
}

Edit 2

The ClientControl should provide the client item as a property instead of a constructor argument and set it in the updateItem method, not in the constructor.

E.g. something like this (untested!):

private final ObjectProperty<Client> client = new SimpleObjectProperty<>(this, "client");
public final Client getClient(){
    return clientProperty().get();
}
public final void setClient(Client client){
    clientProperty().set(client);
}

public ObjectProperty<Client> clientProperty(){
    return client;
}

And in the constructor: listen for changes of this property to set the labelText etc. You also might want to provide a constructor without a client argument, as it is not available when you instantiate it in the TableCell constructor.

OTHER TIPS

So I found the solution for my problem. Thank you so much Puce for your help! :-) Now I set Client via the property like that:

    private ObjectProperty<Client> clientProperty = new SimpleObjectProperty<Client>();

Additionally I added a ChangeListener in the constructor of ClientControl:

    public ClientControl(ObservableList<Client> suggestions) {
    clientProperty.addListener(new ChangeListener<Client>() {
        @Override
        public void changed(ObservableValue<? extends Client> observable, Client oldValue,
                ClientnewValue) {
            if(newValue != null) {
                setLabelText(newValue.toString());
            }
        }
    });
    setSuggestions(suggestions);
    FXMLLoader loader = new FXMLLoader(getClass().getResource("ClientControl.fxml"));
    loader.setRoot(this);
    loader.setController(this);
    try {
        loader.load();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    initTextField();
}

My ClientCell class needed only some simple changes because of the changes in ClientControl:

public class NewClientCell extends TableCell<Client, Client> {
private final ClientControl cc;

public NewClientCell(ObservableList<Client> suggestions) {
    cc = new ClientControl(suggestions);
this.setAlignment(Pos.CENTER_LEFT);
this.setGraphic(cc);
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}

@Override
protected void updateItem(Client c, boolean empty) {
    super.updateItem(c, empty);
    if(!empty){
        cc.setClient(c);
    }
}
}

In the main program nothing changed.

In conclusion I would like to thank Puce one more time, I stuck at this problem for many days...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top