4

Hi,

I have created my TableModel and want to refresh JTable once I added a new row. What should be added to the listener to "refresh" JTable?

public class MyTableModel implements TableModel  {
    private Set<TableModelListener> listeners = new HashSet<TableModelListener>();

    //List<Staff> staffs = Factory.getInstance().getStaffDAO().getAllStaff();
    private List<Staff> staffs;

    public MyTableModel(List<Staff> staffs){
        this.staffs = staffs;
    }

    @Override
    public int getRowCount() {
        return staffs.size();
    }

    @Override
    public int getColumnCount() {
        return 5;  
    }

    @Override
    public String getColumnName(int columnIndex) {
        switch (columnIndex){
            case 0:
                return "First Name";
            case 1:
                return "Second Name";
            case 2:
                return "Date";
            case 3:
                return "Position";
            case 4:
                return "Salary";
        }
        return "";  
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return Object.class;  
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return true;  
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Staff staff = staffs.get(rowIndex);
        switch (columnIndex){
            case 0:
                return staff.getName();
            case 1:
                return staff.getSurname();
            case 2:
                return staff.getDate();
            case 3:
                return staff.getPosition();
            case 4:
                return staff.getSalary();
        }
        return "";  
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
    }

    @Override
    public void addTableModelListener(TableModelListener l) {
    }

    @Override
    public void removeTableModelListener(TableModelListener l) {
    }
}

Here is my listener of my Add row Button:

 @Override
    public void actionPerformed(ActionEvent e) {
        Staff staff = new Staff();
        staff.setName(JOptionPane.showInputDialog("Enter First Name"));
        staff.setSurname(JOptionPane.showInputDialog("Enter Second Name"));
        staff.setDate(JOptionPane.showInputDialog("Enter Date"));
        staff.setPosition(JOptionPane.showInputDialog("Enter Position"));
        staff.setSalary(JOptionPane.showInputDialog("Enter Salary"));
        try {
            Factory.getInstance().getStaffDAO().addStaff(staff);
        } catch (SQLException e1) {
            e1.printStackTrace();  
        }
!!!Here should be some code that will be firing my table after adding new row!!!
}

I've tried to use method firetabledatachanged() of AbstractTableModel in my actionPerformed() but with unluck, it is appeared ClassCastException.

UPDATE 1

WorkPlaceGui.java

public class WorkPlaceGui extends JFrame implements ActionListener {

    AbstractTableModel model;
    JTable jTable;
    JScrollPane jScrollPane;

    public WorkPlaceGui()throws SQLException{


        List<Staff> staffs = Factory.getInstance().getStaffDAO().getAllStaff();
        for(int i = 0; i < 0; i++) {
                staffs.add(new Staff("First Name " + staffs.get(i).getName(), "Second Name " + staffs.get(i).getSurname(), "Date " + staffs.get(i).getDate(), "Position " + staffs.get(i).getPosition(), "Salary " + staffs.get(i).getSalary()));
        }

        model = new MyTableModel(staffs);
        jTable = new JTable(model);
        JButton jBtnAdd = new JButton("Добавить");
        JButton jBtnDel = new JButton("Удалить");
        JButton jBtnUpd = new JButton("Обновить");
        JButton jBtnAdmin = new JButton("Админка");
        JPanel panelNorth = new JPanel();
        JPanel panelCenter = new JPanel();
        JPanel panelSouth = new JPanel();
        jTable.setPreferredScrollableViewportSize(new Dimension(350, 150));
        jScrollPane = new JScrollPane(jTable);


        panelNorth.setLayout(new FlowLayout());
        panelNorth.add(jBtnAdd);
        panelNorth.add(jBtnDel);
        panelNorth.add(jBtnUpd);
        panelNorth.add(jBtnAdmin);
        panelCenter.add(jScrollPane);

        setLayout(new BorderLayout());
        add(panelNorth, BorderLayout.NORTH);
        add(panelCenter, BorderLayout.CENTER);

        jBtnAdd.addActionListener(this);

        setPreferredSize(new Dimension(550, 300));
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setTitle("Staff data base");
        pack();
        setVisible(true);
        setLocationRelativeTo(null);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        Staff staff = new Staff();
        staff.setName(JOptionPane.showInputDialog("Enter First Name"));
        staff.setSurname(JOptionPane.showInputDialog("Enter Second Name"));
        staff.setDate(JOptionPane.showInputDialog("Enter Date"));
        staff.setPosition(JOptionPane.showInputDialog("Enter Position"));
        staff.setSalary(JOptionPane.showInputDialog("Enter Salary"));
        try {
            Factory.getInstance().getStaffDAO().addStaff(staff);
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
        model.fireTableDataChanged();
    }
}

MyTableModel.java

public class MyTableModel extends AbstractTableModel {

    private List<Staff> staffs;

    public MyTableModel(List<Staff> staffs){
        this.staffs = staffs;
    }

    @Override
    public int getRowCount() {
        return staffs.size();
    }

    @Override
    public int getColumnCount() {
        return 5;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Staff staff = staffs.get(rowIndex);
        switch (columnIndex){
            case 0:
                return staff.getName();
            case 1:
                return staff.getSurname();
            case 2:
                return staff.getDate();
            case 3:
                return staff.getPosition();
            case 4:
                return staff.getSalary();
        }
        return "";
    }
}

devger
  • 703
  • 4
  • 12
  • 26
  • for better help sooner post an [SSCCE](http://sscce.org/), short, runnable, compilable, just about JFrame with JTable contains hardcode value for TableModel, issue could be anywhere – mKorbel May 28 '13 at 07:24

6 Answers6

13

You've done it the hard way.

First of all, you've implemented directly from TableModel and secondly you've failed to implement the listener requirements...

Instead, try extending from the AbstractTableModel instead, which already includes the implementations of the listener registration and notification.

You will need to provide a method that will allow you to add a row to the table model. In this method you need to use the fireTableRowsInserted method which will notify any tables using the model, that a new row has been added...

Update with example

This is VERY, VERY basic example. It's only intention is to demonstrate the use of fireTableRowsInserted. It uses a Swing Timer to add a new row every 125 milliseconds until you kill it ;)

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;

public class DynamicTable {

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

    public DynamicTable() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }
                
                final MyTableModel model = new MyTableModel();
                JTable table = new JTable(model);
                
                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(table));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
                
                Timer timer = new Timer(125, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        model.addRow();
                    }
                });
                timer.start();
            }
        });
    }
    
    public class MyTableModel extends AbstractTableModel {

        private List<String[]> rows;

        public MyTableModel() {
            rows = new ArrayList<>(25);
        }
        
        @Override
        public int getRowCount() {
            return rows.size();
        }

        @Override
        public int getColumnCount() {
            return 4;
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return String.class;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            String[] row = rows.get(rowIndex);
            return row[columnIndex];
        }
        
        public void addRow() {
            int rowCount = getRowCount();
            String[] row = new String[getColumnCount()];
            for (int index = 0; index < getColumnCount(); index++) {
                row[index] = rowCount + "x" + index;
            }
            rows.add(row);
            fireTableRowsInserted(rowCount, rowCount);
        }            
    }                    
}

Updated with another example

Because your table model is backed by its own List, it has no connection to your factory. It doesn't know when you add or remove objects from it. This means you become responsible for updating the model:

public class MyTableModel extends AbstractTableModel {

    private List<Staff> staffs;

    public MyTableModel(List<Staff> staffs){
        this.staffs = staffs;
    }

    @Override
    public int getRowCount() {
        return staffs.size();
    }

    @Override
    public int getColumnCount() {
        return 5;
    }
    
    public void add(Staff staff) {
        int size = getSize();
        staffs.add(staff);
        fireTableRowsInserted(size, size);
    }

    public void remove(Staff staff) {
        if (staffs.contains(staff) {
            int index = stafff.indexOf(staff);
            staffs.remove(staff);
            fireTableRowsDeleted(index, index);
        }
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Staff staff = staffs.get(rowIndex);
        switch (columnIndex){
            case 0:
                return staff.getName();
            case 1:
                return staff.getSurname();
            case 2:
                return staff.getDate();
            case 3:
                return staff.getPosition();
            case 4:
                return staff.getSalary();
        }
        return "";
    }
}

And your actionPerformed:

@Override
public void actionPerformed(ActionEvent e) {
    Staff staff = new Staff();
    staff.setName(JOptionPane.showInputDialog("Enter First Name"));
    staff.setSurname(JOptionPane.showInputDialog("Enter Second Name"));
    staff.setDate(JOptionPane.showInputDialog("Enter Date"));
    staff.setPosition(JOptionPane.showInputDialog("Enter Position"));
    staff.setSalary(JOptionPane.showInputDialog("Enter Salary"));
    try {
        Factory.getInstance().getStaffDAO().addStaff(staff);
        ((MyTableModel)model).add(staff);
    } catch (SQLException e1) {
        e1.printStackTrace();  
    }
}
Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • I've switched to solution with extending **AbstractTableModel**, added method **fireTableDataChanged()**, but it doesn't repaint my table. – devger May 28 '13 at 08:00
  • I'm assuming that the model has been attached the table in question? – MadProgrammer May 28 '13 at 08:02
  • Yes, in such way `jTable = new JTable(model);` – devger May 28 '13 at 08:05
  • MadProgrammer, I've added it in **UPDATE1** – devger May 28 '13 at 12:22
  • The problem is, your not actually adding anything to model. You add the new staff to the factory, but there's no connection to between the factory or model. Take a look at them example I did. I add a new row directly to the model. You have at least 2 options. You connect your model directly to the factory, so that the model is extracting data directly from the factory. This would require you to provide some mechanism for the factory to notify the model (and other interested parties) that the factory gas changed, or, you need to also add the staff to model directly, like I have in my example – MadProgrammer May 28 '13 at 20:34
  • thanks for your help, but I still can get how to implement `addRow()` for my code – devger May 29 '13 at 19:17
  • 1
    I've updated your code for you. You need to work on your ability to take a abstract concept to apply it your problems. It will make you much stronger developer in the long run, as it will very unlikely that you will ever find exact solutions to your problems ;) – MadProgrammer May 30 '13 at 00:19
  • Thanks, MadProgrammer, for your instructions :) and help. As for code, I tried to Run code that you have sent, but it raised `NullPointerException`. I fixed it by changing constructor in `MyTableModel` to this way `public MyTableModel() throws SQLException {this.staffs = Factory.getInstance().getStaffDAO().getAllStaff();}` after these changes my app started to work. – devger May 30 '13 at 18:00
  • Also for me it was interesting to look on realization of `add()` method and binding `Staff` with my model, I didn't understand how to implement this and was a bit confused because I added lines `AbstractTableModel model = new MyTableModel(); JTable jTable = new JTable(model);` that, actually I decided, have done a binding, but in fact - they haven't. – devger May 30 '13 at 18:00
8

Your class MyTableModel implements TableModel, but it has no event handling mechanism to connect the model to the view. Instead extend AbstractTableModel, as shown here and here. AbstractTableModel provides the fireTable* methods needed for this.

public void setValueAt(Object value, int row, int col) {
    data[row][col] = value;
    fireTableCellUpdated(row, col);
}
Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • I can't upvote again, but nice suggestion about using `fireTableCellUpdated` in the `setValueAt` method! – MadProgrammer May 28 '13 at 07:29
  • 1
    Shamelessly adapted from [tutorial cited](http://docs.oracle.com/javase/tutorial/uiswing/components/table.html#data). :-) – trashgod May 28 '13 at 07:34
3

Well, first of all, find more about Observer pattern (http://en.wikipedia.org/wiki/Observer_pattern).

I suggest that you create a ObservableModel class that will have a list of PropertyChangeListeners. Your StaffDAO should extend this ObservableModel. When the new staff is added (i.e. addStaff is called) you should call ObservableModel's firePorpertyChange or something like that. firePropertyChange notifyies all propertyChangeListeners. One of those listeners should be registered in your Table, and its propertyChanged method should be implemented with refreshing of the table (hd1) had a good answer.

darijan
  • 9,725
  • 25
  • 38
  • Interesting idea, but I'm not sure that `PropertyChangeListener` is the most appropriate approach as it suggests that a single property of a object has changed, where as adding or removing would suggest a larger change requirement -IMHO. It would be more useful to provide, if possible, a custom interface that could provide more meaningful information about the state change of the model and included, references to all the rows that were effected, making it easier to determine exactly what has changed (as the row in question could have been inserted instead) – MadProgrammer May 28 '13 at 07:37
2

myTableModel.fireTableDataChanged(); should be just enough to force a refresh of your table. As usual, if you have further problems, do feel free to leave a comment.

hd1
  • 33,938
  • 5
  • 80
  • 91
  • But `TableModel` doesn't have a `fireTableDataChanged` and the only implementation I know of is `protected` – MadProgrammer May 28 '13 at 07:24
  • You should extend [DefaultTableModel](http://java.sun.com/javase/6/docs/api/javax/swing/table/DefaultTableModel.html), instead of implementing it yourself. – hd1 May 28 '13 at 07:25
  • 1
    Not necessarily. `fireTableDataChanged` comes from `AbstractTableModel`, which may be better suited (and IMHO often is) to peoples requirements. My point is, the the OP has used `TableModel` as the bases for there table model and it has no `fireTableDataChanged`, as such your current suggestion won't work. Right idea though... – MadProgrammer May 28 '13 at 07:28
  • @MadProgrammer is correct; DefaultTableModel inherits the event plumbing of AbstractTableModel, but it lures you into the technological cul-de-sac of java.util.Vector. – trashgod May 28 '13 at 09:20
2

For a more general solution you can use the Row Table Model and just implement the getValueAt() and setValueAt() methods.

Here should be some code that will be firing my table after adding new row!

The model is responsible for invoking the proper fireXXX method.

camickr
  • 321,443
  • 19
  • 166
  • 288
1

Use this type of casting.

((AbstractTableModel)student.getModel()).fireTableCellUpdated();