[ListView] `b.bind(a)` not behaviorally equivalent to `a.addListener(o -> b.set(a.get()))`

Tomas Mikula tomas.mikula at gmail.com
Fri Dec 13 11:15:22 PST 2013


I just came across a strange case when these two are not equivalent.
Maybe that is no surprise to you, but it was to me. The case I
stumbled upon most likely has to do with ListView internals.

Substitute
a := ListView.widthProperty()
b := ListCell.prefWidthProperty()

and the code that demonstrates the difference:


public class Test extends Application {

    private static class MyCell extends ListCell<String> {
        public MyCell(ListView<String> lv) {
            setMaxWidth(Region.USE_PREF_SIZE);

            // !!! comment out exactly one of the following
            // !!! two lines to demonstrate the difference
            prefWidthProperty().bind(lv.widthProperty());
            lv.widthProperty().addListener(o -> setPrefWidth(lv.getWidth()));
        }

        @Override
        protected void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
            setGraphic(empty ? null : new TextFlow(new Text(item)));
        }
    }

    @Override
    public void start(Stage stage) {
        ListView<String> listView = new ListView<>();
        listView.setCellFactory(lv -> new MyCell(lv));
        listView.getItems().add("This is a very long line that needs
to be wrapped");

        StackPane stack = new StackPane();
        stack.getChildren().add(listView);
        Scene scene = new Scene(stack, 200, 100);
        stage.setScene(scene);
        stage.show();
    }

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


When I run the "bind" version, the text shows up wrapped.
When I run the "addListener" version, the text shows up in one long line.

Do you think this is a bug in ListView, or is there a reasonable explanation?

Just for the record, I consider the "wrapping" behavior to be the correct one.


My *speculation* about the possible cause follows:

The only case I could come up with when
A) b.bind(a)
and
B) a.addListener(o -> b.set(a.get()))
are effectively different is when both of these conditions hold:
1) there is an invalidation listener set on b that causes b to become
valid (e.g. calls b.get()); and
2) a is invalidated twice in a row, but recomputes to the same value each time.

Now the scenarios for A) and B) differ:

Scenario A (b.bind(a)):
 - a is invalidated for the first time
 - b's invalidation listener is called for the first time
 - b is now valid
 - a is invalidated for the second time
 - b's invalidation listener is called for the *SECOND* time

Scenario B (a.addListener(o -> b.set(a.get()))):
 - a is invalidated for the first time
 - b is set to a.get() = x and b's invalidation listener is called for
the first time
 - b is now valid
 - a is invalidated for the second time
 - b is set to a.get() = x and b's invalidation listener is *NOT*
called for the second time, because b's value did not change

In scenario A, b's invalidation listener is called twice, while in
scenario B just once. If "weird things" are happening in this
invalidation listener, this can result in different behavior.

Now, if b is ListCell.prefWidthProperty(), scenario A and scenario B
cause it to be invalidated different number of times, which, my
*speculation*, could yield different behavior.
This is how far I got.

Cheers,
Tomas


More information about the openjfx-dev mailing list