s.d
s.d

Reputation: 4185

How to correctly update AbstractTableModel with fireTableDataChanged()?

I'm still struggling with a JTable that should be automatically updated.

The situation is as follows: I instantiate MyTable (extends JTable), and set it in my UI class (MyView). The MyTable class takes the UI class and an instance of the class that contains logic as parameters):

...
private JPanel createTablePanel() {
    tablePanel = new JPanel();
    myTable = new MyTable(this,mymeth);
    setMyTable(myTable);
    JScrollPane scrollPane = new JScrollPane(getMyTable());
    tablePanel.add(scrollPane);
    return tablePanel;
}

MyTable itself looks like below. An extension of AbstractTableModel (MyTableModel) is set to it. An extension of TableModelListener is set to the model. And finally an extension of ListSelectionListener is set to the model's SelectionModel.

public class MyTable extends JTable implements TableModelListener
{

public MyTable(MyView myView, MyMethods mymeth)
{

    AbstractTableModel tableModel = new MyTableModel(mymeth);
    setModel(tableModel);
    getModel().addTableModelListener(new MyTableModelListener());
    setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    setCellSelectionEnabled(true);
    getColumnModel().getSelectionModel().addListSelectionListener(new MyTableSelectionListener(this, mymeth, myView));
    setPreferredScrollableViewportSize(this.getPreferredSize());

}

}

Let's have a quick look at the model's constructor.

public MyTableModel(MyMethods mymeth) {
    dataObject = new MyData(mymeth);
    myData = dataObject.getMyData();
    colTitles = dataObject.getColTitles();
}

MyData compiles the data for the table: A Vector<Vector<Object>>, that consists of three Vector<Object>s (the table data) and a String[] (The column titles). The data itself comes from a graph via mymeth, the logic class' instance.

Whenever a table column is clicked, a popup (i.e. JOptionPane) object is instantiated, which presents a selection of values for the 3rd row in the selected column. The user chooses a value and the value is set to the data model. Note the way the table is updated after that.

public MyOptionPane(int i, MyMethods mymeth, MyView myView) {
    this.view = myView;
    String sourceString = mymeth.getSourceString(i); // Gets a String from a
String[]. In this Array, the order is the same as in the table.
    String tag = null;
    tag = (String) JOptionPane.showInputDialog(this, "Choose a tag for \"" + sourceString + "\"", "String tagging" , JOptionPane.PLAIN_MESSAGE, null, myView.getTags().toArray(), myView.getTags().get(0));
    mymeth.setTag(i, tag);

    // This is where fireTableDataChanged() didn't work, but this did
            MyTableModel model = new MyTableModel(mymeth); // New model instance
    view.getMyTable().setModel(model); // reset new model to table
}

This works. However, from what I've read I shold be able to simply call fireTableDataChanged() on the model and the table should update itself. However, this doesn't work. User kleopatra has commented to an answer in a previous post:

do not call any of the model's fireXX methods from any code external to the model. Instead implement the model to do so when anything changed

So: Can I call fireTableDataChanged() within such a structure at all? And if so, where and how?

Thanks in advance for all pieces of enlightenment!

Upvotes: 3

Views: 19727

Answers (4)

Abdul-Razak Adam
Abdul-Razak Adam

Reputation: 1138

Here is a simple program that has an ArrayList of Person, populates a table with data from the ArrayList and updates AbstractTableModel with fireTableDataChanged(). Hope this helps

import java.util.ArrayList;
import javax.swing.table.AbstractTableModel;

/**
 *
 * @author razak
 */
public class Table extends javax.swing.JFrame {

    ArrayList<Person> records; //arrayList of persons
    AbstractTable tableModel;  //table model --inner class

    /**
     * Creates new form Table
     */
    public Table() {
        records = new ArrayList<>();
        tableModel = new AbstractTable(records);
        addData();
        initComponents();
        recordTable.setModel(tableModel);
    }

    /**
     * Adds test values to the table
     */
    private void addData() {
        records.add(new Person("Tester", 21));
        records.add(new Person("Kofi", 20));
        records.add(new Person("Razak", 251));
        records.add(new Person("Joseph", 21));
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        recordTable = new javax.swing.JTable();
        nameLabel = new javax.swing.JLabel();
        ageLabel = new javax.swing.JLabel();
        nameTextField = new javax.swing.JTextField();
        ageTextField = new javax.swing.JTextField();
        addButton = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        recordTable.setModel(tableModel);
        jScrollPane1.setViewportView(recordTable);

        nameLabel.setText("Name");

        ageLabel.setText("Age");

        addButton.setText("Add");
        addButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
            .addGroup(layout.createSequentialGroup()
                .addGap(52, 52, 52)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(nameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(ageLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addGap(40, 40, 40)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 151, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(ageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 61, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(66, 66, 66)
                        .addComponent(addButton, javax.swing.GroupLayout.PREFERRED_SIZE, 109, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap(39, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addGap(16, 16, 16)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(nameLabel)
                    .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(18, 18, 18)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(ageLabel)
                            .addComponent(ageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 31, Short.MAX_VALUE)
                        .addComponent(addButton)
                        .addGap(18, 18, 18)))
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 173, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap())
        );

        pack();
    }// </editor-fold>                        

    /**
     * When add button is clicked
     *
     * @param evt
     */
    private void addButtonActionPerformed(java.awt.event.ActionEvent evt) {                                          
        // TODO add your handling code here:
        String name = nameTextField.getText();
        int age = Integer.parseInt(ageTextField.getText());
        records.add(new Person(name, age));
        tableModel.fireTableDataChanged();

    }                                         

    /**
     * Inner class
     */
    class AbstractTable extends AbstractTableModel {

        String col[]; //column names
        ArrayList<Person> data; //arrayList to populate table

        AbstractTable(ArrayList<Person> record) {
            this.col = new String[]{"Name", "Age"};
            data = record;
        }

        //get number of records
        @Override
        public int getRowCount() {
            return data.size();
        }

        //get number of columns
        @Override
        public int getColumnCount() {
            return col.length;
        }

        //get a value form the table
        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            Person person = data.get(rowIndex);
            if (columnIndex == 0) {
                return person.getName();
            } else if (columnIndex == 1) {
                return person.getAge();
            }
            return null;
        }

        //set value at a particular cell 
        public void setValueAt(Person person, int row, int column) {
            data.add(row, person);
            fireTableCellUpdated(row, column);
        }

        //get column name
        public String getColumnName(int column) {
            return col[column];
        }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(Table.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(Table.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(Table.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(Table.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new Table().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify                     
    private javax.swing.JButton addButton;
    private javax.swing.JLabel ageLabel;
    private javax.swing.JTextField ageTextField;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JLabel nameLabel;
    private javax.swing.JTextField nameTextField;
    private javax.swing.JTable recordTable;
    // End of variables declaration                   
}


/**
 * Person class
 * @author razak
 */
public class Person {

    private final String name; //name
    private final int age; //age

    //constructor
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //return name
    public String getName() {
        return name;
    }

    //returns age
    public int getAge() {
        return age;
    }
}

Upvotes: 0

trashgod
trashgod

Reputation: 205855

You shouldn't have to replace the entire model to update a single row. Instead, update the affected cell(s) and let setValueAt() fire the required event, as shown here. Alternatively, this related example uses a DefaultTableModel that handles the events for you, as suggested by @mKorbel & @camickr.

If you stay with AbstractTableModel, consider List<List<MyData>>, unless you need Vector for some other reason.

Upvotes: 3

camickr
camickr

Reputation: 324147

from what I've read I shold be able to simply call fireTableDataChanged() on the model and the table should update itself

Your program doesn't invoke the fireXXX methods on the model. The TableModel itself is responsible for invoking these methods whenever any of the data in the model is changed. Look at the Creating a Table Model example from the Swing tutorial. The setValueAt(...) method shows how to invoke the appropriate fireXXX method.

If you create a completely new TableModel, then you need to use the setModel() method.

Kleopatra's comment was that all fireXXX methods showed be invoked from within the TableModel class itself. If you want to understand how this is done, then take a look at the source code for the DefaultTableModel. If has example of when you would invoke the fireTableDataChanged() method along with the other fireXXX methods.

Upvotes: 4

mKorbel
mKorbel

Reputation: 109823

not to answer to your question, suggestion about using DefaultTableModel

if you don't need to something to restrict for JTable, really good reason, then you couldn't to use AbstractTableModel, best job is still if you'd implement DefaultTableModel, by using DefaultTableModel you'll never to care about that, which of FireXxxXxxChanged() you have to use for each from setXxx() methods,

Upvotes: 2

Related Questions