ListView / TableView will not maintain selection of replaced but equal items

XMLWordPrintable

    • Type: Bug
    • Resolution: Unresolved
    • Priority: P4
    • tbd
    • Affects Version/s: jfx25
    • Component/s: javafx
    • None

      ListView is very eager to clear the entire selection when it receives a ListChangeListener replace change. Whereas permutations and additions/removals take care to preserve as much as possible of the selection, replacing a list (full or in part) with similar items (or even the same items) clears the selection, or exhibits other incorrect behavior.

      Furthermore, this behavior depends on how the list was set. If one replaces the entire list (with ListView.setItems()) the behavior differs from replacing all elements (with ListView.getItems().setAll()). This is due to some state being tracked that is not set correctly when the entire list is replaced.

      Finally, the "replace" logic can be fooled by doing individual `set` calls on items. If an "Orange" is selected and I replace the item at that exact position with "Kiwifruit", it remains selected.

      Here are failing test cases:

          @Test
          public void whenNewListInstalledAndAllIemsAreReplacedShouldClearSelection() {
              listView.setItems(FXCollections.observableArrayList("Apple", "Orange", "Banana"));
              listView.getSelectionModel().select(1);
              assertEquals(1, listView.getSelectionModel().getSelectedIndex());
              assertEquals("Orange", listView.getSelectionModel().getSelectedItem());

              listView.getItems().setAll("Kiwifruit", "Pineapple", "Grape");
              assertEquals(-1, listView.getSelectionModel().getSelectedIndex());
              assertNull(listView.getSelectionModel().getSelectedItem());
          }

      Fails because of how the list was created.

          @Test
          public void whenOnlyTheSelectedItemIsReplacedShouldClearItsSelection() {
              listView.getItems().setAll("Apple", "Orange", "Banana");
              listView.getSelectionModel().select(1);
              assertEquals(1, listView.getSelectionModel().getSelectedIndex());
              assertEquals("Orange", listView.getSelectionModel().getSelectedItem());

              listView.getItems().set(1, "Kiwifruit");
              assertEquals(-1, listView.getSelectionModel().getSelectedIndex());
              assertNull(listView.getSelectionModel().getSelectedItem());
          }

      Fails, the 2nd item remains selected.

          @Test
          public void whenASelectedItemIsReplacedShouldPreserveRestOfSelection() {
              listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
              listView.getItems().setAll("Apple", "Orange", "Banana");
              listView.getSelectionModel().selectIndices(1, 2);

              assertEquals(List.of(1, 2), listView.getSelectionModel().getSelectedIndices());
              assertEquals(List.of("Orange", "Banana"), listView.getSelectionModel().getSelectedItems());

              listView.getItems().set(1, "Kiwifruit");

              assertEquals(List.of(2), listView.getSelectionModel().getSelectedIndices());
              assertEquals(List.of("Banana"), listView.getSelectionModel().getSelectedItems());
          }

      Fails, both items remain selected.

          @Test
          public void replaceWithSameItemsShouldNotClearSelection() {
              listView.getItems().setAll("Apple", "Orange", "Banana");
              listView.getSelectionModel().select(1);
              assertEquals(1, listView.getSelectionModel().getSelectedIndex());
              assertEquals("Orange", listView.getSelectionModel().getSelectedItem());

              listView.getItems().setAll("Apple", "Orange", "Banana");
              assertEquals(1, listView.getSelectionModel().getSelectedIndex());
              assertEquals("Orange", listView.getSelectionModel().getSelectedItem());
          }

      Fails, selection is cleared.

          @Test
          public void replaceRangeShouldNotAffectSelectedItemsOutsideOfRange() {
              listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
              listView.getItems().setAll("A", "B", "C", "D", "E", "F", "G", "H");
              listView.getSelectionModel().selectIndices(0, 2, 4, 6);

              assertEquals(List.of(0, 2, 4, 6), listView.getSelectionModel().getSelectedIndices());
              assertEquals(List.of("A", "C", "E", "G"), listView.getSelectionModel().getSelectedItems());

              listView.getItems().replaceRange(2, 6, List.of("D", "C", "M", "N"));

              assertEquals(List.of(0, 3, 6), listView.getSelectionModel().getSelectedIndices());
              assertEquals(List.of("A", "C", "G"), listView.getSelectionModel().getSelectedItems());
          }

      Fails, selection is unchanged.

      Note that the permutation logic also contains problems (it assumes the whole list is permutated, while it could be a range). JavaFX provides no code that permutates a range, but 3rd parties can...

            Assignee:
            John Hendrikx
            Reporter:
            John Hendrikx
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: