0

I'm trying to show table from JSONArray. but the result never show up. The table already show up with right column name by calling this initTable() method. The initTable() is as follow:

private void initTable() {
    contentPane.removeAll();

    PrintTableModel tblModel = new PrintTableModel();

    DefaultTableColumnModel columns = new DefaultTableColumnModel();
    TableColumn c;

    c = new TableColumn();
    c.setHeaderValue(columnNames[0]);
    columns.addColumn(c);

    c = new TableColumn();
    c.setHeaderValue(columnNames[1]);
    columns.addColumn(c);

    c = new TableColumn();
    c.setHeaderValue(columnNames[2]);
    columns.addColumn(c);

    c = new TableColumn();
    c.setHeaderValue(columnNames[3]);
    columns.addColumn(c);

    listTable = new JTable(tblModel, columns);
    listTable.getTableHeader().setReorderingAllowed(false);

    JScrollPane scrollPane = new JScrollPane();
    scrollPane.add(listTable);
    scrollPane.setViewportView(listTable);
    contentPane.add(scrollPane, BorderLayout.CENTER);       
}

And I'm calling the refresh table from a thread like this:

public class RetrievePrintRequest extends Thread {
    private boolean speedUp = false;

    public void run() {
        if (loginId == 0)
            return;

        try {
            String token = generateLink("prc_admin_id=" + loginId);
            do {
                JSONArray jsonresult = readJsonFromUrl(BASE_URL + "ajax/get_latest_print?code=" + token);

                // looping through All elements
                // list holding row data
                List<PrintModel> printList = new ArrayList<PrintModel>();
                for (int i = 0; i < jsonresult.length(); i++) {
                    JSONObject c = jsonresult.getJSONObject(i);

                    // Storing each json item in variable
                    int printId = c.getInt("print_id");
                    String bookingCode = c.getString("booking_code");
                    long startDate = c.getLong("start_date");
                    String patientName = c.getString("patient_name");
                    String doctorName = c.getString("doc_name");

                    PrintModel printModel = new PrintModel(
                            printId, bookingCode, patientName, doctorName, new Date(startDate));
                    // add rest of the json data to NodePOJO class

                    System.out.println(printId + ";" + bookingCode + ";" + startDate + ";" + patientName + ";" + doctorName);
                    // the object to list
                    printList.add(printModel);
                }
                PrintTableModel printModel = (PrintTableModel) listTable.getModel();
                printModel.refresh(printList); //<--- I expect this to refresh the table content
                Thread.sleep(!speedUp ? 10000 : 60000);
            } while(true);
        } catch (JSONException | IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

And this is the refresh method inside TableModel (Add the whole PrintTableModel class)

protected class PrintTableModel extends DefaultTableModel {
        private List<PrintModel> listData;

        public void refresh(List<PrintModel> data) {
            listData = data; // <== refresh with new List and call fireTableDataChanged
            fireTableDataChanged();
        }

        public Object getValueAt(int row, int column) {
            Object result = null;

            PrintModel model = (PrintModel)listData.get(row);
            switch (column)
            {
                case 0:
                    result = model.getBookingCode();
                    break;
                case 1:
                    result = SDF.format(model.getAppointmentDate());
                    break;
                case 2:
                    result = model.getPatientName();
                    break;
                case 3:
                    result = model.getDoctorName();
                    break;      
            }
            return result;
        }

        public Class getColumnClass(int column) {
            switch (column)
            {
                case 0:
                    return String.class;
                case 1:
                    return String.class;
                case 2:
                    return String.class;            
                case 3:
                    return String.class;
            }
            return null;
        }

Anyone can spot my error? Many thanks.

yodann
  • 355
  • 5
  • 20
  • 1
    Nowhere do I see you calling `setTableModel(...)` with your new data or making changes to the current table model, your tblModel variable. Without doing one or the other, the table will not update. Note that if you need further help, you're going to want to create and post a valid [mcve](http://stackoverflow.com/help/mcve) (please read the link). – Hovercraft Full Of Eels Feb 14 '16 at 18:35
  • 1
    Here's a simple idea, stop recreating the table and scrollpane each time, instead, simply change the model associated with the table. – MadProgrammer Feb 14 '16 at 20:40

3 Answers3

3

You are updating UI from a Thread that is not synchronized with the Event Dispatch Thread (EDT):

Swing event handling code runs on a special thread known as the event dispatch thread. Most code that invokes Swing methods also runs on this thread. This is necessary because most Swing object methods are not "thread safe": invoking them from multiple threads risks thread interference or memory consistency errors

Swing is not thread safe and so you need to update the UI by using SwingUtilities.invokeLater.

From the documentation on SwingUtilities.invokeLater:

Causes doRun.run() to be executed asynchronously on the AWT event dispatching thread. This will happen after all pending AWT events have been processed. This method should be used when an application thread needs to update the GUI.


PS: Use this way of adding the table to the content pane:

contentPane.add(new JScrollPane(listTable), BorderLayout.CENTER);
TT.
  • 15,774
  • 6
  • 47
  • 88
  • I add this line on calling the thread, SwingUtilities.invokeLater(new RetrievePrintRequest()); but still not working – yodann Feb 15 '16 at 03:38
  • @yodann You need only synchronize the parts **that update the UI** not the whole request! You have a `while(true)` loop with a `Thread.Sleep` on it, that's something you should never post as an event to the EDT! Just the part where you update the model needs to be executed on the EDT! – TT. Feb 15 '16 at 07:26
  • @yodann Take a look at the answer I gave [here](http://stackoverflow.com/a/35382587/243373), about having while(true)+sleep on your EDT and why that is going to fail. – TT. Feb 15 '16 at 07:28
  • @yodann Other than that, it's not unlikely you have other problems in your code. It's not easy to debug code from a few snippets stitched together in a post on SO. Spotting glaring mistakes, that's possible, debugging those snippets isn't. You should try make an [MCVE](https://stackoverflow.com/help/mcve), a short program that mimics what you are doing in your large program. It's easier to debug that way. – TT. Feb 15 '16 at 07:31
  • sorry for the late reply. I understand it will be hard without the complete code to debug. I just knew about MCVE, and next time I will provide accordingly. At the moment, I've already found the answer written below – yodann Feb 16 '16 at 06:26
  • @yodann Ah yes, I did not see your updated code in your question. Indeed the `DefaultTableModel` has its own `dataVector` and any changes need to be applied to this `dataVector` (through [`setDataVector`](https://docs.oracle.com/javase/7/docs/api/javax/swing/table/DefaultTableModel.html#setDataVector%28java.util.Vector,%20java.util.Vector%29)). If your data store is **not** the `dataVector` you are probably better off creating your own model, like what you did in your answer. – TT. Feb 16 '16 at 06:37
  • @yodann Note that the information I gave in this answer is very important if you plan to generate the data in a separate `Thread`. After the data is generated, you need to add the data to the model **on the EDT** using `SwingUtilities.invokeLater`. Just the part that updates the model. If you don't play nice with the EDT, it will eventually result in undefined behaviour. Good to hear you found the other problem in your code though =). – TT. Feb 16 '16 at 06:54
3

To add to what TT. has already said...

For iteration of the for-loop, you do this...

    PrintModel printModel = new PrintModel(printId, bookingCode, patientName, doctorName, new Date(startDate));
    // add rest of the json data to NodePOJO class

    System.out.println(printId + ";" + bookingCode + ";" + startDate + ";" + patientName + ";" + doctorName);
    // the object to list
    printList.add(printModel);
}

After you processed each "row", you do this...

PrintTableModel printModel = (PrintTableModel) listTable.getModel();
printModel.refresh(printList); //<--- I expect this to refresh the table content

The question here is, what relationship does the PrintModel you make in the for-loop have to the one which you try and refresh?

Instead, you should either be creating a new instance of PrintTableModel BEFORE the start of the for-loop, adding each row of data to this instance of PrintTableModel and then (after synchornising the calling to the EDT) applying this instance of the PrintTableModel to the JTable

OR

Using a SwingWorker, publish each row of data and adding it the current instance of PrintTableModel (from the JTable) when you process it.

See Concurrency in Java and Worker Threads and SwingWorker for more details

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
1

The Answer: It didn't work because DefaultTableModel has it's own Vector data, and creating new one didn't affected when calling fireTableDataChanged(). So, I guess DefaultTableModel not design to be extended, but to be used as standard TableModel. Java provides AbstractTableModel for extended purposes.

So, this is the new code that extended from AbstractTableModel:

protected class PrintTableModel extends AbstractTableModel {
    private List<PrintModel> listData = new ArrayList<PrintModel>();
    protected String[] columnNames;

    public int getRowCount() {
        return listData.size();
    }

    public void setColumnNames(String[] columnNames) {
        this.columnNames = columnNames;
    }

    public String getColumnName(int column) {
        return columnNames[column];
    }

    public int getColumnCount() {
        return columnNames.length;
    }

    public void add(Object obj) {
        listData.add(0, (PrintModel)obj);
        fireTableRowsInserted(listData.size() - 1, listData.size() - 1);
    }

    public void update(Object obj, int row) {
        listData.set(row, (PrintModel)obj);
        fireTableRowsUpdated(row, row);
    }

    public void remove(int row) {
        listData.remove(row);
        fireTableRowsDeleted(row, row);
    }

    public Object getObjectAt(int row) {
        return listData.get(row);
    }

    public void refresh(List<PrintModel> data) {
        listData = data;
        fireTableDataChanged();
    }

    public Object getValueAt(int row, int column) {
        Object result = null;

        PrintModel model = (PrintModel)listData.get(row);
        switch (column)
        {
            case 0:
                result = model.getBookingCode();
                break;
            case 1:
                result = SDF.format(model.getAppointmentDate());
                break;
            case 2:
                result = model.getPatientName();
                break;
            case 3:
                result = model.getDoctorName();
                break;  
            case 4:
                result = "Print";
                break;                  
        }
        return result;
    }

    public Class getColumnClass(int column) {
        switch (column)
        {
            case 0:
                return String.class;
            case 1:
                return String.class;
            case 2:
                return String.class;            
            case 3:
                return String.class;
            case 4:
                return String.class;                    
        }
        return null;
    }       
}
yodann
  • 355
  • 5
  • 20