1

I have a tableView of id, name, mail, phone and select. I wanna get all selected row of my tableView and add selected mail to my email list in my model class. I read : this but I don't know how to add the listener to my obsevealList data to get what I want do that as a listener to data:

for (People p : model.getData()) {
           if (p.getCheck())
               model.getEmails().add(p.getMail());
       }
   }

How can I do this please ?

People class:

 public class People {
 private IntegerProperty id = new SimpleIntegerProperty(this, "id");
 private StringProperty name = new SimpleStringProperty(this, "name");
 private StringProperty mail = new SimpleStringProperty(this, "mail");
 private StringProperty phone = new SimpleStringProperty(this, "phone");
 private BooleanProperty check = new SimpleBooleanProperty(this, "check");


 public People(String name, String mail, String phone) {
     this.name.set(name);
     this.mail.set(mail);
     this.phone.set(phone);
     this.check.set(false);
 }
 public People(Integer id,String name, String mail, String phone) {
     this.id.set(id);
     this.name.set(name);
     this.mail.set(mail);
     this.phone.set(phone);
     this.check.set(false);
 }
 public IntegerProperty idProperty() {
     return id;
 }

 public Integer getId() {
     return idProperty().get();
 }

 public StringProperty nameProperty() {
     return name;
 }
 public String getName() {
     return nameProperty().get();
 }

 public void setName(String name) {
     nameProperty().set(name);
 }
 public StringProperty mailProperty() {
     return mail;
 }
 public String getMail() {
     return mailProperty().get();
 }

 public void setMail(String mail) {
     mailProperty().set(mail);
 }

 public StringProperty phoneProperty() {
     return phone;
 }
 public String getphone() {
     return phoneProperty().get();
 }

 public void setPhone(String phone) {
     phoneProperty().set(phone);
 }
 public BooleanProperty checkProperty() {
     return check;
 }

 public Boolean getCheck() {
     return checkProperty().get();
 }

 public void setCheck(Boolean remark) {
     checkProperty().set(remark);
}
}

My data class:

public class Data {
    private Connection con = null;
    private PreparedStatement ps = null;
    private Statement st = null;
    private ResultSet rs = null;
    private List<String> emails;
    private String mychoice;

    private ObservableList<People> data = FXCollections.observableArrayList();
    public  ObservableList<People> getData(){
        return data;
    }

    public List<String> getEmails() {
        return emails;
    }

    public void addEmails(String mail) {
        emails.add(mail);
    }

    public void setEmails(List<String> emails) {
        this.emails = emails;
    }

    public String getMychoice() {
        return mychoice;
    }

   public void setMychoice(String mychoice) {
        this.mychoice = mychoice;
    }

    
    public void loadData() {
        try {
            con=getConnect();
            data.clear();
            String query = "SELECT * FROM " + mychoice;
            ps = con.prepareStatement(query);
            rs = ps.executeQuery();
            while (rs.next()) {
                data.add(new People(
                        rs.getInt(1),
                        rs.getString(2),
                        rs.getString(3),
                        rs.getString(4)
                ));
            }
            rs.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

extrait of Show class:

public class Show implements Initializable {
  
    Data model=new Data();
    @FXML
    private TableView<People> table;
    @FXML
    private TableColumn<People, String> name,mail, phone;
    @FXML
    private TableColumn<People, Integer> id;
    @FXML
    private TableColumn <People,Boolean> select;

    @Override

    public void initialize(URL url, ResourceBundle resourceBundle) {
        con = getConnect();
        loadTable(); //set all table name inside the combobox
        table.setItems(model.getData());
        id.setCellValueFactory(cd -> cd.getValue().idProperty().asObject());
        name.setCellValueFactory(cd -> cd.getValue().nameProperty());
        phone.setCellValueFactory(cd -> cd.getValue().phoneProperty());
        mail.setCellValueFactory(cd -> cd.getValue().mailProperty());
        table.setEditable(true);
        name.setCellFactory(TextFieldTableCell.forTableColumn());
        mail.setCellFactory(TextFieldTableCell.forTableColumn());
        phone.setCellFactory(TextFieldTableCell.forTableColumn());
        select.setCellFactory(CheckBoxTableCell.forTableColumn(select));
        select.setCellValueFactory(cd -> cd.getValue().checkProperty());

 select.setCellFactory(CheckBoxTableCell.forTableColumn(new Callback<Integer, ObservableValue<Boolean>>() {

            @Override
            public ObservableValue<Boolean> call(Integer param) {
                System.out.println("Contact " + model.getData().get(param).getMail() + " changed value to " + model.getData().get(param).getCheck());
                return model.getData().get(param).checkProperty();
            }
        }));

            
    }
}
happyness
  • 19
  • 3

2 Answers2

2

As outlined here, and starting from this related complete example, I created an ObservableList with an extractor for the Observable[] containing the relevant BooleanProperty, new Observable[]{invited}.

ObservableList<Person> model =
    FXCollections.observableArrayList(p -> p.extract());

Then add the listener:

model.addListener(createListener());
ListChangeListener<Person> createListener() {
return (Change<? extends Person> c) -> {
    while (c.next()) {
        modelChanged = true:
    }
};

This approach conditionally marks the model as changed. Later, if modelChanged, you can traverse the ObservableList to collect required data and clear the flag.

To see the effect, the variation below updates a label to show the current selection status.

image

import java.util.Arrays;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

/**
 * https://stackoverflow.com/a/69715448/230513
 * https://stackoverflow.com/q/69711881/230513
 * https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/table-view.htm
 * https://docs.oracle.com/javase/8/javafx/properties-binding-tutorial/binding.htm#JFXBD107
 * https://stackoverflow.com/a/38050982/230513
 * https://stackoverflow.com/a/25204271/230513
 */
public class TableViewTest extends Application {

    private final ObservableList<Person> model = FXCollections.observableList(
        Arrays.asList(
            new Person("Ralph", "Alpher", true, "ralph.alpher@example.com"),
            new Person("Hans", "Bethe", false, "hans.bethe@example.com"),
            new Person("George", "Gammow", true, "george.gammow@example.com"),
            new Person("Paul", "Dirac", false, "paul.dirac@example.com"),
            new Person("Albert", "Einstein", true, "albert.einstein@example.com")
        ), p -> p.extract());

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

    @Override
    public void start(Stage stage) {
        stage.setTitle("Table View Sample");
        stage.setWidth(640);
        stage.setHeight(480);

        final TableView<Person> table = new TableView<>(model);
        final Label label = new Label("Address Book");
        label.setFont(Font.font("Serif", 20));
        final Label status = new Label(calcuateStatus());

        TableColumn<Person, String> firstName = new TableColumn<>("First Name");
        firstName.setCellValueFactory(cd -> cd.getValue().firstNameProperty());
        table.getColumns().add(firstName);

        TableColumn<Person, String> lastName = new TableColumn<>("Last Name");
        lastName.setCellValueFactory(cd -> cd.getValue().lastNameProperty());
        table.getColumns().add(lastName);

        TableColumn<Person, Boolean> invited = new TableColumn<>("Invited");
        invited.setCellValueFactory(cd -> cd.getValue().invitedProperty());
        invited.setCellFactory(CheckBoxTableCell.forTableColumn(invited));
        table.getColumns().add(invited);

        TableColumn<Person, String> email = new TableColumn<>("Email");
        email.setCellValueFactory(cd -> cd.getValue().emailProperty());
        email.setCellFactory(TextFieldTableCell.forTableColumn());
        email.setOnEditCommit(t -> t.getRowValue().emailProperty().set(t.getNewValue()));
        table.getColumns().add(email);

        model.addListener(createListener(status));
        table.setEditable(true);
        table.setTableMenuButtonVisible(true);

        final VBox vbox = new VBox();
        vbox.setSpacing(8);
        vbox.setPadding(new Insets(8));
        vbox.getChildren().addAll(label, table, status);

        stage.setScene(new Scene(vbox));
        stage.show();
    }

    private String calcuateStatus() {
        int sum = 0;
        for (Person p : model) {
            if (p.invited.get()) {
                sum += 1;
            }
        }
        return sum + " / " + model.size() + " invited.";
    }

    private ListChangeListener<Person> createListener(Label status) {
        return (Change<? extends Person> c) -> {
            while (c.next()) {
                status.setText(calcuateStatus());
            }
        };
    }

    private static class Person {

        private final StringProperty firstName;
        private final StringProperty lastName;
        private final BooleanProperty invited;
        private final StringProperty email;

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

        public StringProperty firstNameProperty() {
            return firstName;
        }

        public StringProperty lastNameProperty() {
            return lastName;
        }

        public BooleanProperty invitedProperty() {
            return invited;
        }

        public StringProperty emailProperty() {
            return email;
        }

        public Observable[] extract() {
            return new Observable[]{invited};
        }
    }
}
jewelsea
  • 150,031
  • 14
  • 366
  • 406
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Thank you so much for your effort but what I want is not just to know if someone select a checkbox ( I can get this from the last part of my code ``` select.setCellFactory``` ) or calculate number of selection (as the code you write can provide) . **I want get for each selected person his mail add it to my email list (declared in my data class) ** Can you please read my code again? – happyness Oct 25 '21 at 19:15
  • @jewelsea soory my comment was uncompleted – happyness Oct 25 '21 at 19:17
  • 1
    @Lamia: Your question was how to add a listener that sees the `Change`; the example illustrates this in a self-contained context; I wouldn't build the `List` until the user has concluded selecting checkboxes, as when submitting the form; you exact solution will depend on your application's design. – trashgod Oct 25 '21 at 22:30
2

This solution is a variation of trashgod's solution, I know that this code is not exactly based on the code provided in your question, but I think the concepts should be able to be applied to your application without too much additional difficulty.

Using a FilteredList

This solution adds an example of reflecting the selected items from the TableView into a separate backing list.

The separate backing list is defined as a FilteredList based on only the selected values from the complete list. The filtered list uses a predicate to determine whether an item from the original list should be reflected in the filtered list. The predicate is based on the checkbox selection status.

private final ObservableList<Person> mailingList = new FilteredList<>(
        people,
        person -> person.invitedProperty().get()
);

An Extractor is Still Required for the FilteredList to Work (in this case)

In order for the filtered list to be updated in real-time, an extractor (as in trashgod's solution) is used. This is required because the filtered list needs to observe the changes on the original list.

person -> new Observable[] { person.invitedProperty() }

Usually, changes to the original list will only be fired when an item is added or removed from the original list. However, when we are changing just the boolean property backing the checkbox, it is just updating an item property to initiate a change rather than adding or removing an entire item from the original list. To register this change an extractor listing the observable property which can initiate the appropriate change notification for the original list is required (based on changes to the property backing the checkbox).

This works as in trashgod's solution. I only inlined some of the definitions in this solution, but the overall mechanism is the same.

Originally, when developing this solution, I thought that the extractor may not be needed. However, in order to get real-time changes, the FilteredList implementation relies on observing the appropriate changes in the original list. For this example, which is mutating the values of objects already in the list rather than adding or removing objects from the list, this is not possible without an extractor.

Example Solution

In the application I define two views, one is the TableView used to select the people to add to the mailing list. The other is the ListView which is backed by the FilteredList and only shows the people in the mailing list. As the user checks and unchecks items in the table view, the changes are immediately reflected in the mailing list view which is backed by the filtered list.

screenshot

import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.collections.transformation.FilteredList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.util.Arrays;

/**
 * https://stackoverflow.com/a/69712532/230513
 */
public class MailingListViewApp extends Application {

    private final ObservableList<Person> people = FXCollections.observableList(
            Arrays.asList(
                    new Person("Ralph", "Alpher", true, "ralph.alpher@example.com"),
                    new Person("Hans", "Bethe", false, "hans.bethe@example.com"),
                    new Person("George", "Gammow", true, "george.gammow@example.com"),
                    new Person("Paul", "Dirac", false, "paul.dirac@example.com"),
                    new Person("Albert", "Einstein", true, "albert.einstein@example.com")
            ),
            person -> new Observable[] { person.invitedProperty() }
    );

    private final ObservableList<Person> mailingList = new FilteredList<>(
            people,
            person -> person.invitedProperty().get()
    );

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

    @Override
    public void start(Stage stage) {
        final TableView<Person> mailingListSelectionTableView =
                createMailingListSelectionTableView();

        ListView<Person> mailingListView =
                createMailingListView();

        final VBox vbox = new VBox(
                10,
                new TitledPane("Candidates", mailingListSelectionTableView),
                new TitledPane("Mailing List", mailingListView)
        );
        vbox.setPadding(new Insets(10));

        stage.setScene(new Scene(vbox));
        stage.show();
    }

    private TableView<Person> createMailingListSelectionTableView() {
        final TableView<Person> selectionTableView = new TableView<>(people);
        selectionTableView.setPrefSize(440, 180);

        TableColumn<Person, String> firstName = new TableColumn<>("First Name");
        firstName.setCellValueFactory(cd -> cd.getValue().firstNameProperty());
        selectionTableView.getColumns().add(firstName);

        TableColumn<Person, String> lastName = new TableColumn<>("Last Name");
        lastName.setCellValueFactory(cd -> cd.getValue().lastNameProperty());
        selectionTableView.getColumns().add(lastName);

        TableColumn<Person, Boolean> invited = new TableColumn<>("Invited");
        invited.setCellValueFactory(cd -> cd.getValue().invitedProperty());
        invited.setCellFactory(CheckBoxTableCell.forTableColumn(invited));
        selectionTableView.getColumns().add(invited);

        TableColumn<Person, String> email = new TableColumn<>("Email");
        email.setCellValueFactory(cd -> cd.getValue().emailProperty());
        selectionTableView.getColumns().add(email);

        selectionTableView.setEditable(true);
        return selectionTableView;
    }

    private ListView<Person> createMailingListView() {
        ListView<Person> mailingListView = new ListView<>();
        mailingListView.setCellFactory(param -> new ListCell<>() {
            @Override
            protected void updateItem(Person item, boolean empty) {
                super.updateItem(item, empty);

                if (empty || item == null || item.emailProperty().get() == null) {
                    setText(null);
                } else {
                    setText(item.emailProperty().get());
                }
            }
        });
        mailingListView.setItems(mailingList);
        mailingListView.setPrefHeight(160);
        return mailingListView;
    }

    private static class Person {

        private final StringProperty firstName;
        private final StringProperty lastName;
        private final BooleanProperty invited;
        private final StringProperty email;

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

        public StringProperty firstNameProperty() {
            return firstName;
        }

        public StringProperty lastNameProperty() {
            return lastName;
        }

        public BooleanProperty invitedProperty() {
            return invited;
        }

        public StringProperty emailProperty() {
            return email;
        }
    }
}

Is the FilteredList Solution appropriate for you?

It is hard to say. It is one potential approach.

As trashgod notes in the comments, the best approach for your application may depend on your overall UI and application architecture.

For example, rather than using the filtered list, you could have a Save button or some other commit point, where it checks a dirty flag such as the modelChanged flag in trashgod's solution (or even do away with the listener/modelChanged concept completely). Then just iterate over the original list extracting any items which are selected at that point in time, and reflect those values back in your model (e.g. commit to the database). Such would be a kind of MVVM approach, where the observable list for selection is just involved as the view-model (VM) and the separate list of committed mailing information is the model (M).

Even if you did use the separate save point approach outlined above, perhaps the addition of the filtered list could still ease the conversion task for you, as all you would need to do is look at all items in the filtered list.

Also, the filtered list could be used in other functions of the app, such as the mailing list view displayed in this solution.

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • The immediate propagation of `FilteredList` is very appealing; I agree with your elaboration on context. – trashgod Oct 25 '21 at 23:32
  • @jewelsea Thank you a lot, I'm just learning new concept. I actually have a question **Where should I use the extractor**. In my case I wanna get list of selected mails to call it in other controller. I tied to extract data inside my data List `private ObservableList data = FXCollections.observableArrayList(p-> new Observable[]{ p.checkProperty()});` but I can't get mails if I do this. – happyness Oct 27 '21 at 16:08
  • 3
    It is probably best to ask a new question specifically about the extractor usage. That way you can provide a targeted [mcve] with well-formatted code and a clear, focused problem description that can be answered by a variety of people and provide searchable solutions for others. In your new question you can refer back to this question and also provide links to any other relevant documentation, javadoc or research. – jewelsea Oct 27 '21 at 16:43