0

After hours searching for an answer, finally I give up on this. I have an FXML form with the following ComboBox:

<ComboBox fx:id="cboTipo" disable="true" prefWidth="247.0" promptText="Tipo de obra..." GridPane.columnIndex="1" GridPane.rowIndex="2" />

Which is injected in a JavaFX controller class:

    @FXML
    public ComboBox<Tobra> cboTipo;

The combo shows a list of Tobra (stands for: Tipo de Obra in spanish) loaded from an embedded H2 database using Eclipselink 2.7 and JPA 2.2.

I don't show to the user the value of Tobra.toString, instead I set a converter in the initialization:

@Override
public void initialize(URL url, ResourceBundle rb) {
    ...
    Objects.requireNonNull(cboTipo, "cboTipo not injected");
    ...
    cboTipo.setConverter(new StringConverter<Tobra>() {
        @Override
        public String toString(Tobra object) {
            return object.getCod() + ": " + object.getNombre();
        }

        @Override
        public Tobra fromString(String string) {
            return new Tobra(string);
        }
    });
   ...
}

I have an inner class which implements Task<List<Tobra>> So I can load the data in background. Then, on succeeded:

task.setOnSucceeded(evt ->
    cboTipo.setItems(FXCollections.observableArrayList(task.getValue()))
);

Of course, when showing the form I run the task inside a Thread:

 new Thread(task).start();

Everything seems to work fine until the code is tested. No matters on what value I click, ALWAYS the selected value resets to the first item. I've tried to force some value from the code, and it shows up in the combobox, but when user clicks the combo to choose another value, again the selected value is reset to "first item". This behavior only occurs when using a ComboBox with type parameters. When I create the combobox without type parameter, and then I add String values, something like this:

 cboTipo.getItems().clear();
 cboTipo.getItems().addAll(
  tobraList.stream().map(x 
       -> x.getCod() + ": " + x.getNombre())
  .toArray());

Everything works fine.

So I've tried doing the same with my POJO Tobra without mapping to string:

 cboTipo.getItems().clear();
 cboTipo.getItems().addAll(tobraList);

But the issue reappears. I've also tried declaring ComboBox cboTipo without type parameter but it neither works. My POJO Tobra, overrides the equals method this way:

@Override
public boolean equals(Object object) {
    if (!(object instanceof Tobra)) {
        return false;
    }
      var other = (Tobra) object;
    return ((this.cod == null && other.cod != null)
            || (this.cod != null && !this.cod.equals(other.cod)));
}

What am I doing wrong?

PS: I also tried setting up my own cell factory as suggested in: Javafx combobox with custom object displays object address though custom cell factory is used

And trying and debugging it I realized that the problem isn't the rendering of the component because the value property of ComboBox never gets updated after selection.

bichoFlyer
  • 178
  • 9

2 Answers2

1

Your equals() method is weird.

return ((this.cod == null && other.cod != null)
        || (this.cod != null && !this.cod.equals(other.cod)));

Let's break this up. Part 1:

(this.cod == null && other.cod != null)

If cod of this Tobra is null and the cod of the other Tobra is not null, then this part is true.

Now the whole expression returns true when this happens, because you have a || operator.

Let's look at the second part.

(this.cod != null && !this.cod.equals(other.cod))

If cod of this Tobra is not null and the cod of the other Tobra are not equal, then this part is true.

This, again, looks reverse.

Most likely you need to ! the whole expression. Alternatively, you can use Objects.equals(this.cod, other.cod), which checks null for you.

Lastly, make sure you also override hashCode() and return a correct value that does not break the contract for equals(). Read the Javadoc for more details.

Jai
  • 8,165
  • 2
  • 21
  • 52
  • 2
    One more issue should be mentioned: `if (!(object instanceof Tobra)) { return false; }` This is only correct if `Tobra` is `final`. Otherwise you could violate symetry when extending `Tobra` (i.e. `a.equals(b) != b.equals(a)`). For a non-`final` `Tobra` class this should be `if (object == null || (getClass() != object.getClass())) { return false; }` – fabian Jun 21 '18 at 08:07
  • 1
    @fabian **Wow!** I used `instanceof` for so many `equals()` overrides and it didn't occur to me at all! – Jai Jun 21 '18 at 08:11
  • I've solved it thanks to @Jai solution, and fabian comment. The equals method was actually generated by netbeans IDE with the tool "generate entity classes from database". Now everything works fine. – bichoFlyer Jun 21 '18 at 18:08
0

I solved the issue changing the equals method this way:

@Override
    public boolean equals(Object object) {
        if (object == null || !getClass().isAssignableFrom(object.getClass())) {
            return false;
        } else {
              var other = (Tobra) object;
            return Objects.equals(this.cod, other.cod);
        }
    }

Lesson: don't ever trust the autogenerated code.

bichoFlyer
  • 178
  • 9