GlazedList: using the set method to update eventlist displayed in table removes table selection

143 Views Asked by At

I am using GlazedList with JFX TableView.

I have the following code:

TableView<Person> table = new TableView<Person>();
table.getSelectionModel().setSelectionMode( SelectionMode.MULTIPLE );
EventList<Person> person_list = new BasicEventList<Person>();
Map<int, Person> map;

public void updatePerson(Person p){
  int personID = p.getID();
  int index = person_list.indexOf( map.get(personID) );

  Person person = person_list.get(index);
  person.setFirstName(p.getFirstName());
  person.setLastName(p.getLastName());

  person_list.set(index, person);
}

Problem: When selecting multiple rows in the table, and the method updatePerson is called, after "person_list.set(index, person);" is called, all my table selection except for the last selected row are un-selected.

When is "updatePerson" called? This method is called every time my back-end receives an update and push this updated value to my front-end. It happens periodically every few seconds.

What i wish to achieve: When user selects multiple row, the table will continue to reflect the updated values without deselecting my selected rows.

EDIT: The following is a sample code i used to test.

public class ObservableJFXTableView extends Application {
    private final TableView<Person> table = new TableView<>();
    private FilterMatcherEditor filterMatcherEditor = new FilterMatcherEditor();

    private EventList<Person> people;
    private ObservableList<Person> data;
    final HBox hb = new HBox();

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

    private void setupGlazedList(){
        people = new BasicEventList<Person>();

        ObservableElementList.Connector<Person> personCOnnector = GlazedLists.beanConnector(Person.class);
        EventList<Person> observedPeople = new ObservableElementList<Person>(people, personConnector);

        EventList<Person> filtered_list = new FilterList<Person(observedPeople, filterMatcherEditor);
        data = new EventObservableList<Person>(filtered_list);
    }

    private void populatedList(){
        people.add(new Person("Jacob", "Smith", "[email protected]"));
        people.add(new Person("James", "Johnson", "[email protected]"));
        people.add(new Person("Christopher", "Lim", "[email protected]"));
        people.add(new Person("Emma", "Jones", "[email protected]"));
        people.add(new Person("Michael", "Brown", "[email protected]"));
    }

    @Override
    public void start(Stage stage){
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Example");
        stage.setWidth(450);
        stage.setHeight(550);

        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial"), 20);

        table.setEditable(true);
        table.getSelectionModel().setSeletionMode(SelectionMode.MULTIPLE);

        TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(new PropertyValueFactory<>("firstName"));

        TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(new PropertyValueFactory<>("lastName"));

        TableColumn<Person, String> emailCol = new TableColumn<>("Email");
        emailCol.setMinWidth(100);
        emailCol.setCellValueFactory(new PropertyValueFactory<>("email"));

        setupGlazedList();
        populatedList();

        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);

        final Button editButton = new Button("Edit -> people.get(2)");
        editButton.setOnAction((ActionEvent e) -> {
            people.get(2).setFirstName("NewFirst");
            people.get(2).setLastName("NewLast");
            people.get(2).setEmail("NewEmail");
        });

        hb.getChildren().add(editButton);
        hb.setSpacing(3);

        final VBox vbox = new VBox();
        vbox.setSpaceing(5);
        vbox.setPadding(new Insets(10,0,0,10));
        vbox.getChildren().addAll(filterMatcherEditor.getTextField(), label, table, hb);

        ((Group) scene.getRoot()).getChildren().addAll(vbox);

        stage.setScene(scene);
        stage.show();
    }

    private static class FilterMatcherEditor extends AbstractMatcherEditor<Person>{
        private TextField tf;

        public FilterMatcherEditor(){
            tf = new TextField();
            tf.textProperty().addListener((observable, oldValue, newValue) -> filterChanged());
        }

        public TextField getTextField(){
            return tf;
        }

        public void filterChanged(){
            if (tf.getText().isEmpty())
                this.fireMatchAll();
            else
                this.fireChanged(new FilterMatcher(tf.getText()));
        }

        private static class FilterMatcher implements Matcher {
            private final String textFieldInput;

            public FilterMatcher(String text){
                this.textFieldInput = text;
            }

            public boolean matched(Object obj){
                final Person person = (Person) obj;

                for (Object obj: person.getAll()){
                    String str = ((String) obj).toUpperCase();
                    if ( str.contains(textFieldInput.toUpperCase()))
                        return true;
                }
                return false;
            }
        }
    }

    public class Person {
        private final SimpleStringProperty firstName;
        private final SimpleStringProeprty lastName;
        private final SimpleStringProperty email;

        private final PropertyChangeSupport support = new PropertyChangeSupport(this);

        private Person(String fName, String lName, String email){
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }

        public void addPropertyChangeListener(PropertyChangeListener l){
            support.addPropertyChangeListener(l);
        }
        public void removePropertyChangeListener(PropertyChangeListener l){
            support.removePropertyChangeListener(l);
        }

        public String getFirstName(){
            return firstName.get();
        }
        public void setFirstName(String str){
            final String old = firstName.get();
            firstName.set(str);
            support.firePropertyCHange("firstName", old, str);
        }

        public String getLastName(){
            return lastName.get();
        }
        public void setLastName(String str){
            final String old = lastName.get();
            lastName.set(str);
            support.firePropertyCHange("lastName", old, str);
        }

        public String getEmail(){
            return email.get();
        }
        public void setEmail(String str){
            final String old = email.get();
            email.set(str);
            support.firePropertyCHange("email", old, str);
        }

        public List<String> getAll(){
            List<String> strList = new ArrayList<String>();
            strList.add(firstName.get());
            strList.add(lastName.get());
            strList.add(email.get());

            return strList;
        }
    }
}

Try using shift click to select multiple row, then click on the edit button, the 3rd row will be updated but all multiple selected rows will be unselected, except for one row.

1

There are 1 best solutions below

1
andyroberts On

I think you would benefit from looking at my answer to a similar question: GlazedList update EventList in JTable

You should be able to update the items in the collection without having to get & set on the list itself.

Also, make sure you're using the proper event selection model to obtain the selected items. Then you won't need to maintain the map object.