abadil
abadil

Reputation: 5

Can't get TableView cells to refresh when adding new data

it's been two days that I've been struggling to get TableView cells to refresh its content when adding new data to its underlying data model.

It's a simple app that shows contacts information on a TableView. The list of contacts is an ObservableList that is stored in an XML file. The contact data model uses JavaFX properties for its fields.

The new contacts are added by clicking the 'add' menu item in the toolbar (showAddContactDialog()). A dialog gets shown where we can enter the contact informations. Then the contact gets saved in the contacts list and then stored in an xml file that will be loaded in the next launch of the application. When the app is run the contacts gets rendered correctly to the tableview, but when I add a new one it's not added to the tableview but only gets stored in the contact list and the xml file.

I don't know what I'm missing, any help please?

I've tried many solutions on stackoverflow and others but none of them was successful.

Below is the code for Contact class :

public class Contact {
private SimpleStringProperty mFirstName;
private SimpleStringProperty mLastName;
private SimpleStringProperty mPhoneNumber;
private SimpleStringProperty mNotes;

public Contact(){...}
public Contact(String firstName, String lastName, String phoneNumber, String notes){
    this.mFirstName = new SimpleStringProperty(firstName);
    this.mLastName = new SimpleStringProperty(lastName);
    this.mPhoneNumber = new SimpleStringProperty(phoneNumber);
    this.mNotes = new SimpleStringProperty(notes);
}

String getFirstName() { return mFirstName.get(); }
public SimpleStringProperty mFirstNameProperty() { return mFirstName; }
void setFirstName(String value) { mFirstName.set(value); }

String getLastName() { return mLastName.get(); }
public SimpleStringProperty mLastNameProperty() { return mLastName; }
void setLastName(String value) { mLastName.set(value); }

String getPhoneNumber() { return mPhoneNumber.get(); }
public SimpleStringProperty mPhoneNumberProperty() { return mPhoneNumber; }
void setPhoneNumber(String value) { mPhoneNumber.set(value); }

String getNotes() { return mNotes.get(); }
public SimpleStringProperty mNotesProperty() { return mNotes;}
void setNotes(String value) { mNotes.set(value); }

Below is the code for ContactData class that is responsible for loading and saving the contacts from the xml file and the operations of adding and deleting contacts from the list :

public class ContactData {
private static final String CONTACTS_FILE = "contacts.xml";
private static final String CONTACT = "contact";
private static final String FIRST_NAME = "first_name";
private static final String LAST_NAME = "last_name";
private static final String PHONE_NUMBER = "phone_number";
private static final String NOTES = "notes";

private ObservableList<Contact> contacts;

public ContactData() {
   contacts = FXCollections.observableArrayList();
   loadContacts();
}

public void addContact(Contact newContact){
    contacts.add(newContact);
    saveContacts();
}

public void deleteContact(Contact contact){
    contacts.remove(contact);
    saveContacts();
}

public ObservableList<Contact> getContacts() {
    return FXCollections.observableList(contacts);
}

public void loadContacts() {
    try {
        // First, create a new XMLInputFactory
        XMLInputFactory inputFactory = XMLInputFactory.newInstance();
        // Setup a new eventReader
        InputStream in = new FileInputStream(CONTACTS_FILE);
        XMLEventReader eventReader = inputFactory.createXMLEventReader(in);
        // read the XML document
        Contact contact = null;

        while (eventReader.hasNext()) {
            XMLEvent event = eventReader.nextEvent();

            if (event.isStartElement()) {
                StartElement startElement = event.asStartElement();
                // If we have a contact item, we create a new contact
                if (startElement.getName().getLocalPart().equals(CONTACT)) {
                    contact = new Contact();
                    continue;
                }

                if (event.isStartElement()) {
                    if (event.asStartElement().getName().getLocalPart()
                            .equals(FIRST_NAME)) {
                        event = eventReader.nextEvent();
                        contact.setFirstName(event.asCharacters().getData());
                        continue;
                    }
                }
                if (event.asStartElement().getName().getLocalPart()
                        .equals(LAST_NAME)) {
                    event = eventReader.nextEvent();
                    contact.setLastName(event.asCharacters().getData());
                    continue;
                }

                if (event.asStartElement().getName().getLocalPart()
                        .equals(PHONE_NUMBER)) {
                    event = eventReader.nextEvent();
                    contact.setPhoneNumber(event.asCharacters().getData());
                    continue;
                }

                if (event.asStartElement().getName().getLocalPart()
                        .equals(NOTES)) {
                    event = eventReader.nextEvent();
                    contact.setNotes(event.asCharacters().getData());
                    continue;
                }
            }

            // If we reach the end of a contact element, we add it to the list
            if (event.isEndElement()) {
                EndElement endElement = event.asEndElement();
                if (endElement.getName().getLocalPart().equals(CONTACT)) {
                    contacts.add(contact);
                }
            }
        }
    }
    catch (FileNotFoundException e) {
        //e.printStackTrace();
    }
    catch (XMLStreamException e) {
        e.printStackTrace();
    }
}

public void saveContacts() {
    try {
        // create an XMLOutputFactory
        XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
        // create XMLEventWriter
        XMLEventWriter eventWriter = outputFactory
                .createXMLEventWriter(new FileOutputStream(CONTACTS_FILE));
        // create an EventFactory
        XMLEventFactory eventFactory = XMLEventFactory.newInstance();
        XMLEvent end = eventFactory.createDTD("\n");
        // create and write Start Tag
        StartDocument startDocument = eventFactory.createStartDocument();
        eventWriter.add(startDocument);
        eventWriter.add(end);

        StartElement contactsStartElement = eventFactory.createStartElement("",
                "", "contacts");
        eventWriter.add(contactsStartElement);
        eventWriter.add(end);

        for (Contact contact: contacts) {
            saveContact(eventWriter, eventFactory, contact);
        }

        eventWriter.add(eventFactory.createEndElement("", "", "contacts"));
        eventWriter.add(end);
        eventWriter.add(eventFactory.createEndDocument());
        eventWriter.close();
    }
    catch (FileNotFoundException e) {
        System.out.println("Problem with Contacts file: " + e.getMessage());
        e.printStackTrace();
    }
    catch (XMLStreamException e) {
        System.out.println("Problem writing contact: " + e.getMessage());
        e.printStackTrace();
    }
}

private void saveContact(XMLEventWriter eventWriter, XMLEventFactory eventFactory, Contact contact)
        throws FileNotFoundException, XMLStreamException {

    XMLEvent end = eventFactory.createDTD("\n");

    // create contact open tag
    StartElement configStartElement = eventFactory.createStartElement("",
            "", CONTACT);
    eventWriter.add(configStartElement);
    eventWriter.add(end);
    // Write the different nodes
    createNode(eventWriter, FIRST_NAME, contact.getFirstName());
    createNode(eventWriter, LAST_NAME, contact.getLastName());
    createNode(eventWriter, PHONE_NUMBER, contact.getPhoneNumber());
    createNode(eventWriter, NOTES, contact.getNotes());

    eventWriter.add(eventFactory.createEndElement("", "", CONTACT));
    eventWriter.add(end);
}

private void createNode(XMLEventWriter eventWriter, String name,
                        String value) throws XMLStreamException {

    XMLEventFactory eventFactory = XMLEventFactory.newInstance();
    XMLEvent end = eventFactory.createDTD("\n");
    XMLEvent tab = eventFactory.createDTD("\t");
    // create Start node
    StartElement sElement = eventFactory.createStartElement("", "", name);
    eventWriter.add(tab);
    eventWriter.add(sElement);
    // create Content
    Characters characters = eventFactory.createCharacters(value);
    eventWriter.add(characters);
    // create End node
    EndElement eElement = eventFactory.createEndElement("", "", name);
    eventWriter.add(eElement);
    eventWriter.add(end);
}
}

Below is the code for the Controller class :

public class Controller {
@FXML
private TableView<Contact> mContactsTableView;
@FXML
private BorderPane mMainWindow;
private ContactData mContactData;
private ObservableList<Contact> mContactsList;

public void initialize(){
    mContactData = new ContactData();
    mContactsList = mContactData.getContacts();
    mContactsTableView.setItems(mContactsList);

    //Initializing the columns of the TableView
    TableColumn<Contact, String> firstNameCol = new TableColumn<>("First Name");
    firstNameCol.setCellValueFactory(param -> param.getValue().mFirstNameProperty());

    TableColumn<Contact, String> lastNameCol = new TableColumn<>("Last Name");
    lastNameCol.setCellValueFactory(new PropertyValueFactory<Contact, String>("mLastName"));

    TableColumn<Contact, String> phoneCol = new TableColumn<>("Phone Number");
    phoneCol.setCellValueFactory(new PropertyValueFactory<Contact, String>("mPhoneNumber"));

    TableColumn<Contact, String> notesCol = new TableColumn<>("Notes");
    notesCol.setCellValueFactory(new PropertyValueFactory<Contact, String>("mNotes"));

    mContactsTableView.getColumns().setAll(firstNameCol, lastNameCol, phoneCol, notesCol);
}


@FXML
private void showAddContactDialog(){
    Dialog<ButtonType> dialog = new Dialog<>();
    dialog.initOwner(mMainWindow.getScene().getWindow());
    dialog.setTitle("ADD NEW CONTACT");
    FXMLLoader fxmlLoader = new FXMLLoader();     
    fxmlLoader.setLocation(getClass()
        .getResource("AddContactDialog.fxml"));
    try {
        dialog.getDialogPane().setContent(fxmlLoader.load());
    } catch (IOException e) {
        System.out.println("Couldn't load the dialog");
        e.printStackTrace();
    }
    dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
    dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);

    Optional<ButtonType> result = dialog.showAndWait();
    if (result.isPresent() && result.get() == ButtonType.OK){
        AddContactDialogController dialogController = fxmlLoader.getController();
        Contact newContact = dialogController.addContact();
    }

Below is the code for the AddContactDialogController class :

public class AddContactDialogController {
@FXML
private TextField firstNameField;
@FXML
private TextField lastNameField;
@FXML
private TextField phoneNumberField;
@FXML
private TextField notesField;

public Contact addContact(){
    String firstName = firstNameField.getText().trim();
    String lastName = lastNameField.getText().trim();
    String phoneNumber = phoneNumberField.getText().trim();
    String notes = notesField.getText().trim();
    Contact newContact = new Contact(firstName, lastName, phoneNumber, notes);
    ContactData contactData = new ContactData();
    contactData.addContact(newContact);
    return newContact;
}
}

Below is mainLayout.fxml file :

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<BorderPane fx:id="mMainWindow" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
    <top>
        <MenuBar>
           <menus>
               <Menu text="Contacts">
                  <items>
                      <MenuItem onAction="#showAddContactDialog" text="Add" />
                      <MenuItem onAction="#handleEditContact" text="Edit" />
                      <MenuItem onAction="#handleDeleteContact" text="Delete" />
                  </items>
               </Menu>
           </menus>
        </MenuBar>
    </top>
    <center>
        <TableView fx:id="mContactsTableView" />
    </center>
</BorderPane>

Below is AddContactDialog.fxml file :

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.text.Text?>
<DialogPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
        fx:controller="sample.AddContactDialogController">
    <header>
        <Text text="Add a New Contact"/>
    </header>
    <content>
        <GridPane vgap="10" hgap="10">
            <Label text="First Name" GridPane.rowIndex="0" GridPane.columnIndex="0"/>
            <TextField fx:id="firstNameField" GridPane.rowIndex="0" GridPane.columnIndex="1"/>

            <Label text="Last Name" GridPane.rowIndex="1" GridPane.columnIndex="0"/>
            <TextField fx:id="lastNameField" GridPane.rowIndex="1" GridPane.columnIndex="1"/>

            <Label text="Phone Number" GridPane.rowIndex="2" GridPane.columnIndex="0"/>
            <TextField fx:id="phoneNumberField" GridPane.rowIndex="2" GridPane.columnIndex="1"/>

            <Label text="Notes" GridPane.rowIndex="3" GridPane.columnIndex="0"/>
            <TextField fx:id="notesField" GridPane.rowIndex="3" GridPane.columnIndex="1"/>
        </GridPane>
    </content>
</DialogPane>

Upvotes: 0

Views: 166

Answers (1)

Zephyr
Zephyr

Reputation: 10253

You actually have at least two problems with your implementation.

First, your getContacts() method is returning a new ObservableList, not the one being updated in the addContact() method:

public ObservableList<Contact> getContacts() {
    return FXCollections.observableList(contacts);
}

Since this new list is the one you've bound to your TableView, it never gets updated when you add a new contact.

Update your getContact() method to simply return contacts. It is already an ObservableList so there's no need to call FXCollections.observableList().


Secondly, within your AddContactDialogController, you are also creating a new ContactData class and that is where you're adding the new contact. Again, this is not the same ContactData or contact List that you've bound to your TableView.

You need to instead pass a reference to you original ContactData object to the AddContactDialogController.addContact() method...

Upvotes: 2

Related Questions