0

Yeah I know, this sounds stupid. But before let me illustrate my problem.

I have a GUI with A LOT of JTable. I have various DefaultTableModel, one for each JTable. So, 1:1. And for each DefaultTableModel I have create one a TableModelListener.

For insert and delete elements in my GUI I have create only TWO methods, addVehicle and removeVehicle:

public void addVehicle(final DefaultTableModel model, final String s1, final String s2) {
    Runnable addV = new Runnable() {
        @Override
        public void run() {
            model.addRow(new Object[] { s1, s2 });
        } 
    };
    SwingUtilities.invokeLater(addV);
}


public void removeVehicle(final DefaultTableModel model, final int index) {
    if (row!=null){
        String s1= row.elementAt(0).toString();
        Runnable removeV = new Runnable() {
            @Override
            public void run() {
                model.removeRow(index);
            }
        };
        SwingUtilities.invokeLater(removeV);
    }
}

These methods are perfect, recognize automatically the right tablemodel.

But I have a necessity: since I have many JTable (and corresponding DefaultTableModels) and given that when I add/delete a row from a table a third-party software MUST BE INFORMED, I have created several TableModelListener.

For example listener1:

TableModelListener listener1 = new TableModelListener() {
    public void tableChanged(TableModelEvent e) {
        DefaultTableModel m = (DefaultTableModel) e.getSource();
        int row = e.getLastRow();
        switch (e.getType()) {
            case (TableModelEvent.INSERT): {
                System.out.println("Insert! Key: "+m.getValueAt(row, 0));

                // My agent, the third-party software: 
                shipperAgent.newTruck((String) m.getValueAt(row, 0));
            } break;

            case (TableModelEvent.DELETE): {
                System.out.println("Delete! Key:"+m.getValueAt(row, 0));

                // My agent, the third-party software: 
                shipperAgent.removeTruck((String)m.getValueAt(row, 0));
            } break;

            case (TableModelEvent.UPDATE): {
                // ...
            } break;
        }
    }
};

Another example is listener 2 (I show you just a fragment...):

            case (TableModelEvent.INSERT): {
                System.out.println("Activate! Key: "+m.getValueAt(row, 0));

                // My agent, the third-party software: 
                shipperAgent.activateTruck((String) m.getValueAt(row, 0));
            } break;

            case (TableModelEvent.DELETE): {
                System.out.println("Deactivate! Key:"+m.getValueAt(row, 0));

                // My agent, the third-party software: 
                shipperAgent.deactivateTruck((String)m.getValueAt(row, 0));
            } break;

This is the problem: is all right for the insert, but not for the DELETE. I know why: when I remove a row from the GUI, the row is deleted and in the switch (case TableModelEvent.DELETE) work on another row (that immediately after the one that I deleted). Moreover, if this row is the last (since there are no more lines after) i get a NullPointerException.

HOW I CAN, in the listener, to use a row BEFORE it is actually removed? (or other brilliant ways?)

Gioce90
  • 554
  • 2
  • 10
  • 31

1 Answers1

3

...when I add/delete a row from a table a third-party software MUST BE INFORMED, I have created several TableModelListener.

If you see TableModelEvent API there's no way to get data from the event itself, all you have is the TableModel which is the source of the event. Having said this, when a table model event is fired the change in the model has been already done and it's nothing you can do about it, so you cannot get the deleted(s) row(s) value(s) just because those won't be there anymore.

However if your concern is to notify this third-party software in order to keep consistency I guess, then you should consider do that before removing rows from the table model.

public void removeVehicle(final DefaultTableModel model, final int index) {
    String value = (String)model.getValueAt(index, 0);
    shipperAgent.deactivateTruck(value);
    // If everything went ok, then remove the row from the table model.
    // Of course, do it in the Event Dispatch Thread.
    model.removeRow(index);
}

This way you can even coordinate that third-party software and your application: if the truck represented by the row cannot be deactivated then don't remove the row in your table model and show some error message instead.


Edit

But I wanted to centralize communication with the agent (third party software) and separate it from the rest of the code, and not disseminate it.

Well it makes perfectly sense and I'm sure there are more than one way to accomplish this, but IMHO your design needs another twist in order to fulfil these requirements:

  • Have only one point to coordinate between the third-party agent and your table model.
  • Don't write lot of methods that do practically the same thing: DRY principle
  • Process the actions in the right time.
  • All the responsibilities have to be well separated.

I was thinking that something close to Mediator pattern (not exactly this pattern but something close) might help you to encapsulate the coordination logic and decouple the third-party agent and table models involved in the same transaction. For example consider this prototype class:

public abstract class Coordinator {

    /*
     * protected class members so subclasses can access these directly
     */

    protected ShipperAgent shipperAgent;
    protected DefaultTableModel tableModel;

    public Coordinator(ShipperAgent sa, DefaultTableModel tm) {
        shipperAgent = sa;
        tableModel = tm;
    }

    public abstract void notifyAndAddRow(Object[] rowData);

    public abstract void notifyAndDeleteRow(int rowIndex);
}

Since you have managed to identify the right table models by the time you call addVehicle(...) and removeVehicle(...) methods, you will probably can do the same to identy the right coordinator and delegate the task of notify the third-party agent and update the table model. For instance:

ShipperAgent sa = new ShipperAgent(...);
DefaultTableModel model1 = new DefaultTableModel(...);
DefaultTableModel model2 = new DefaultTableModel(...);

Coordinator coordinator1 = new Coordinator(sa, model1) {
    @Override
    public void notifyAndAddRow(Object[] rowData) {
        this.shipperAgent.newTruck((String) rowData[0]);
        this.tableModel.addRow(rowData); // do this in the EDT
    }

    @Override
    public void notifyAndDeleteRow(int rowIndex) {
        String truck = (String)this.tableModel.getValueAt(rowIndex, 0);
        this.shipperAgent.removeTruck(truck);
        this.tableModel.removeRow(rowIndex); // do this in the EDT
    }
};

Coordinator coordinator2 = new Coordinator(sa, model2) {
    @Override
    public void notifyAndAddRow(Object[] rowData) {
        this.shipperAgent.activateTruck((String) rowData[0]);
        this.tableModel.addRow(rowData); // do this in the EDT
    }

    @Override
    public void notifyAndDeleteRow(int rowIndex) {
        String truck = (String)this.tableModel.getValueAt(rowIndex, 0);
        this.shipperAgent.removeTruck(truck);
        this.tableModel.removeRow(rowIndex); // do this in the EDT
    }
};

Then your addVehicle(...) and removeVehicle(...) methods could look like this:

public void addVehicle(Coordinator coordinator, String s1, String s2) {
    coordinator.notifyAndAddRow(new Object[]{s1, s2});
}

public void removeVehicle(Coordinator coordinator, int index) {
    coordinator.notifyAndDeleteRow(index);
}

Of course as I've said you will have to manage to identify the right Coordinator instance by the time you call addVehicle(...) and removeVehicle(...) methods, but I think you have the abstraction required to solve your problem now.

dic19
  • 17,821
  • 6
  • 40
  • 69
  • yes, I had thought. But I wanted to centralize communication with the agent (third party software) and separate it from the rest of the code, and not disseminate it. But since in the TableModelListener is not possible... I will settle. – Gioce90 Aug 28 '14 at 07:47
  • But now I have to create many methods "addVehicle / removeVehicle" as many as the DefaultTableModels. If you notice, the listener1 and listener2 (respectively for two different DefaultTableModel) do different operations. The first `case (TableModelEvent.INSERT)` does `shipperAgent.newTruck(...)` In the second `case (TableModelEvent.INSERT)` does `shipperAgent.activateTruck(...)` I'll have to create different methods? – Gioce90 Aug 28 '14 at 07:54
  • 1
    Hi there. Please see my edit. I have started to write it yesterday but I didn't have time enough to finish it. Hope it helps :) @Gioce90 – dic19 Aug 29 '14 at 12:28
  • thank you!!! My code works! I had do only a little change, in notifyAndAddRow and notifyAndDeleteRow methods: I do shipperAgentGUI.this.shipperAgent.methodName(...). Aside from that, it's perfect! Thanks again! – Gioce90 Sep 01 '14 at 09:58
  • 1
    You are welcome :) EDT is an acronym for [Event Dispatch Thread](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html) which is a single and special thread where Swing components should be created/updated. When you call `SwingUtilities.invokeLater(...)` you ensure that code will execute in the EDT. However this is not the only way and not all your code should run in the EDT. [Concurrency in Swing](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html) is a broad topic and you may want to take a look to the tutorials. @Gioce90 – dic19 Sep 01 '14 at 11:20
  • I have created another abstract method in the Coordinator class: **setUpdate()** (you suggest another name?). This method, at difference from others, is called in Coordinator's *constructor*. So, in his implementations, I create and add two different *TableModelListeners* (one for table obviously) for manage the updates! If I change a Vehicle's information in a row, **this change is visible also in the other table**. I don't have found other way (if not create and add the listeners in others part of the code). But this in Coordinators seems an elegant solution. What do you think? – Gioce90 Oct 09 '14 at 09:19
  • 1
    About method's name, and just a suggestion, don't you think something like ***notifyRowUpdated()*** is more apropriate? If both tables have to be in synch then *Coordinator* sounds like the candidate to do that. And yes, you need *TableModelListener* because it's the proper way to listen for row events. The only detail I see is you have to keep a reference between *Coordinators* to be able to notify each other on rows updates and keep table models in synch. Let me know if I'm clear enough and if it works. Good luck! :) @Gioce90 – dic19 Oct 09 '14 at 11:30
  • I have used the references to TableModels, and not to Coordinators. For example in *notifyRowUpdated()* of the coordinator of the second table (**availablesModel**) In addTableModelListener, in the cases DELETE and UPDATES I use **parkTable**.repaint(); ... Is all right, no? You can see the results here (last answer): http://stackoverflow.com/questions/26142930/complex-use-of-jtable-tablemodel-and-others/26234689#26234689 – Gioce90 Oct 09 '14 at 13:32
  • I have upvoted your answer because it's a great job implementing your own table model. However repainting the whole table is never the right approach to update the view on model's change (either insert/update/delete). I have the answer you need but I think you should accept peeskillet's answer and post your table model in a new question so I can post my own answer to the new update-notification problem. Otherwise posts loose their original intention and turn out in a help-desk service useless to future visitors. @Gioce90 – dic19 Oct 09 '14 at 13:45
  • the title is *Complex use of JTable, TableModel and others* :) but I will follow your suggestion. Thank you. – Gioce90 Oct 09 '14 at 14:02
  • I have posted the question here: http://stackoverflow.com/questions/26382515/the-right-approach-to-update-complex-jtables-tablemodel-and-others – Gioce90 Oct 15 '14 at 12:24