2

My GUI shows the vehicles in my park, and vehicles that I want to set availables in two different VehicleTables (classes that extend JTable). For availables I intend that these vehicles can be observed from an agent (third-part software). Both the tables show the descriptions of Vehicles in the rows...for this I have created VehicleTableModel and Vehicle classes. The Vehicle class is an abstract class and his subclasses are: Car, Truck, Trailer, etc. .

You can see a snapshot of my software: enter image description here

My problems are these: In my current implementation I don't think of manage really good the updates of the rows. You can see in VehicleTableModel (fire...() methods) and in ShipperAgentGUI (coordinators and listeners). I think I have partially resolved this problem with the use of the Coordinator inner class for the updates between tables, but I don't know how optimize these. For example in case of delete or update of a row I make xxxTable.repaint(); ... the WHOLE table...

...another way?

ShipperAgentGUI.java

public class ShipperAgentGUI extends JFrame implements ActionListener {

    // Graphics variables..
    // bla bla...

    // Headers, TableModels, JTables for the tables
    private COLUMNS[] parkModelHeader = {COLUMNS.IMAGE_COLUMN, COLUMNS.TARGA_COLUMN,
        COLUMNS.CAR_TYPE_COLUMN, COLUMNS.MARCA_COLUMN, COLUMNS.STATE_COLUMN, COLUMNS.PTT_COLUMN };
    private COLUMNS[] availablesModelHeader = {COLUMNS.IMAGE_COLUMN, COLUMNS.TARGA_COLUMN,
        COLUMNS.CAR_TYPE_COLUMN, COLUMNS.MARCA_COLUMN };

    private VehicleTableModel parkModel = new VehicleTableModel(parkModelHeader);
    private VehicleTableModel availablesModel = new VehicleTableModel(availablesModelHeader);

    private VehicleTable parkTable;
    private VehicleTable availablesTable;

    // My third-part software, a JADE agent:
    protected ShipperAgent shipperAgent;


    // --------------------------------------------------------------------------

    // CONSTRUCTOR

    ShipperAgentGUI(ShipperAgent agent) {

        shipperAgent = agent; // valorizes the agent

        setTitle("Shipper Agent: "+agent.getLocalName()+" GUI");

        // graphic bla bla...

        // Park Table and Available Table:
        parkTable = new VehicleTable(parkModel);
            // bla bla...
        availablesTable = new VehicleTable(availablesModel);
            // bla bla...

        // JButtons: add/remove vehicle in Park Table and Available Table
        btnPM_plus = new JButton();
            btnPM_plus.setToolTipText("Add vehicle");
            btnPM_plus.setIcon(...);
            btnPM_plus.setActionCommand("+park");
            btnPM_plus.addActionListener(this);

        // similar things for other three buttons:
        // remove from parkTable, add and remove from availablesTable

        //bla bla...

        // Data from agent:
        Vector<Vehicle> veicoli = shipperAgent.getVehicles();
        Iterator<Vehicle> I = veicoli.iterator();
        while (I.hasNext()){
            addVehicle(parkCoordinator, I.next());
        }

        showGui();
    }



    ///////////////////////////////////////////////////////////////////////
    // Methods:

    public void showGui() {
        // bla bla
    }


    //////////////////////////////////////////////
    // actionPerformed method

    @Override
    public void actionPerformed(ActionEvent e) {
        switch (e.getActionCommand()) {
        case "+park": {
            new InsertVehicleJDialog(this, parkCoordinator);
        } break;

        case "-park": {
            int selectedRow = parkTable.getSelectedRow();
            if (selectedRow != -1)
                removeVehicle(parkCoordinator, selectedRow);
        } break;

        case "+available": {
            int selectedRow = parkTable.getSelectedRow();
            if (selectedRow != -1){
                addVehicle(availablesCoordinator, parkModel.getVehicleAt(selectedRow)); 
            }
        } break;

        case "-available": {
            int selectedRow = availablesTable.getSelectedRow();
            if (selectedRow != -1)
                removeVehicle(availablesCoordinator, selectedRow);
        } break;
        }
    }


    ///////////////////////////////////////
    // Add/Remove vehicle methods:

    void addVehicle(Coordinator coordinator, Vehicle v) {
        coordinator.notifyAndAddRow(v);
    }

    // mhm...
    void removeVehicle(Coordinator coordinator, Vehicle v) {
        int row = coordinator.indexOf(v);
        if (row!=-1)
            coordinator.notifyAndDeleteRow(row);
    }

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


    // on dispose, delete the agent
    public void dispose() {
        super.dispose();
        shipperAgent.doDelete(); 
    }




    ///////////////////////////////////////
    // INNER CLASS COORDINATOR:

    protected abstract class Coordinator {
        private VehicleTableModel tableModel;

        public Coordinator(VehicleTableModel tm) {
            tableModel = tm;
            notifyRowUpdated();
        }

        public abstract void notifyAndAddRow(Vehicle vehicle);
        public abstract void notifyAndDeleteRow(int rowIndex);
        public abstract void notifyRowUpdated();

        public int indexOf(Vehicle v) {
            return tableModel.indexOf(v);
        }

        boolean vehicleExists(Vehicle vehicle){
            int bool = indexOf(vehicle);
            if (bool==-1) return false;
            else return true;
        }
    }


    // Coordinator for parkTable
    Coordinator parkCoordinator = new Coordinator(parkModel) {

        @Override
        public void notifyAndAddRow(final Vehicle vehicle) {
            if (!vehicleExists(vehicle)){ // is this the right control? Or in VehicleTableModel ?
                shipperAgent.newTruck(vehicle.getPlate());

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        parkModel.addRow(vehicle);
                        if (vehicle.getState().equals(Stato.DISPONIBILE))
                            availablesModel.addRow(vehicle); 
                            // or with availablesCoordinator.notifyAndAddRow(vehicle) ?
                            // or with addVehicle(availablesCoordinator, vehicle) ?
                            // or with a kind of listener on vehicle's state ?
                    }
                });
            }
        }

        @Override
        public void notifyAndDeleteRow(final int rowIndex) {
            final Vehicle v = parkModel.getVehicleAt(rowIndex);

            removeVehicle(availablesCoordinator, v); // Remove also from the "availables"

            shipperAgent.removeTruck(v.getPlate());

            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    parkModel.removeRow(rowIndex);
                }
            });
        }

        @Override
        public void notifyRowUpdated() {
            parkModel.addTableModelListener(new TableModelListener() {
                public void tableChanged(TableModelEvent e) {
                    switch (e.getType()) {
                        case (TableModelEvent.DELETE):
                            parkTable.repaint();
                            break;
                        case (TableModelEvent.UPDATE):
                            int row = e.getLastRow();
                            Vehicle v = parkModel.getVehicleAt(row);
                            if (v.getState().equals(Stato.DISPONIBILE)){
                                addVehicle(availablesCoordinator, v);
                                availablesTable.repaint();
                            } else
                                removeVehicle(availablesCoordinator, v);
                            parkTable.repaint();
                            break;
                    }
                }
            });
        }
    };



    // Coordinator for availablesTable
    Coordinator availablesCoordinator = new Coordinator(availablesModel) {

        @Override
        public void notifyAndAddRow(final Vehicle vehicle) {
            if (!vehicleExists(vehicle)){ // is this the right control? Or in VehicleTableModel ?
                vehicle.setStato(Stato.DISPONIBILE);
                parkTable.repaint();

                shipperAgent.activateTruck(vehicle.getPlate());
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        availablesModel.addRow(vehicle);
                    }
                });
            }
        }

        @Override
        public void notifyAndDeleteRow(final int rowIndex) {
            Vehicle v = availablesModel.getVehicleAt(rowIndex);
            if (v!=null){
                v.setStato(Stato.NON_DISPONIBILE); // mhm
                shipperAgent.deactivateTruck(v.getPlate());

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        availablesModel.removeRow(rowIndex);
                    }
                });
            }
        }

        @Override
        public void notifyRowUpdated() {
            availablesModel.addTableModelListener(new TableModelListener() {
                public void tableChanged(TableModelEvent e) {
                    switch (e.getType()) {
                    case (TableModelEvent.DELETE):
                        parkTable.repaint();
                        break;
                    case (TableModelEvent.UPDATE):
                        parkTable.repaint();
                        break;
                    }
                }
            });
        }
    };

}

VehicleTableModel.java

public class VehicleTableModel extends AbstractTableModel {

    private ArrayList<Vehicle> vehicles ;
    private COLUMNS[] header;

    // possible column names:
    public enum COLUMNS {
        IMAGE_COLUMN,
        TARGA_COLUMN,
        CAR_TYPE_COLUMN,
        MARCA_COLUMN,
        STATE_COLUMN,
        PTT_COLUMN,
    };

    ///////////////////////////////////////////////////////
    // Constructor:

    public VehicleTableModel(COLUMNS[] headerTable) {
        this.vehicles = new ArrayList<Vehicle>();
        this.header = headerTable;
    }


    ///////////////////////////////////////////////////////
    // obligatory override methods (from AbstractTableModel):

    @Override
    public int getColumnCount() {
        return header.length;
    }

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

    @Override
    public Object getValueAt(int row, int col) {
        Object value = "?";
        Vehicle v = vehicles.get(row);
        if (v!=null) {
            COLUMNS column = header[col];
            switch (column) {
                case IMAGE_COLUMN:
                    value = VehicleUtils.findImageByColumnCarType(v.getType());
                    break;
                case TARGA_COLUMN:
                    value = v.getPlate();
                    break;
                case CAR_TYPE_COLUMN:
                    value = VehicleUtils.findStringByColumnCarType(v.getType());
                    break;
                // other cases... bla bla...
            }
        }
        return value;
    }



    ///////////////////////////////////////////////////////
    // My methods:

    public void addRow(Vehicle vehicle) {
        vehicles.add(vehicle);
        fireTableRowsInserted(0, getRowCount()); // is right?
    }

    /*public boolean removeRow(Vehicle vehicle) {
        boolean flag = vehicles.remove(vehicle);
        fireTableRowsDeleted(0, getRowCount()); // is right?
        return flag;
    }*/

    public void removeRow(int row) {
        vehicles.remove(row);
        fireTableRowsDeleted(row, row); // is right?
    }

    public Vehicle getVehicleAt(int row) {
        return vehicles.get(row);
    }

    public int indexOf(Vehicle v){
        return vehicles.indexOf(v);
    }

    // found the corresponding column index
    public int findColumn(COLUMNS columnName) {
        for (int i=0; i<getColumnCount(); i++)
            if (columnName.equals(header[i])) 
                return i;
        return -1;
    }


    // a value in that column exist in the table?
    private boolean controllIfExist(Object value, int col) {
        boolean bool = false;
        for (int i=0; i<getRowCount();i++){
            if (value.equals(getValueAt(i, col))){
                bool=true;
                break;
            }
        }
        return bool;
    }

    public int getColumnIndex(COLUMNS column){
        for(int i=0;i<header.length;i++){
            if (column.equals(header[i])){
                return i;
            }
        }
        return -1;
    }



    ///////////////////////////////////////////////////////
    // other methods (from AbstractTableModel) to override:


    @Override
    public Class<?> getColumnClass(int col) {
        Class<?> c;
        COLUMNS column = header[col];
        if (column.equals(COLUMNS.IMAGE_COLUMN))
            c = ImageIcon.class;
        else if (column.equals(COLUMNS.STATE_COLUMN))
            c =  JComboBox.class;
        else c = super.getColumnClass(col);
        return c;
    }


    @Override
    public String getColumnName(int col) {
        COLUMNS column = header[col];
        if (column.equals(COLUMNS.IMAGE_COLUMN))
            return " ";
        else if (column.equals(COLUMNS.TARGA_COLUMN))
            return "Targa";
        // others... bla bla...
        return super.getColumnName(col);
    };


    @Override
    public boolean isCellEditable(int row, int col) {
        return true;
    }


    @Override
    public void setValueAt(Object value, int row, int col) {
        Vehicle v = vehicles.get(row);
        boolean flag = false;
        if (v!=null) {
            COLUMNS column = header[col];
            switch (column) {
                case TARGA_COLUMN:
                    if (!v.getPlate().equals(value)){
                        if (!controllIfExist(value, col)){  // mhm...
                            v.setPlate((String) value);
                            flag = true;
                        }
                    }
                    break;
                case MARCA_COLUMN:
                    if (!v.getMark().equals(value)){
                        v.setMark((String) value);
                        flag = true;
                    }
                    break;

                // others ... bla bla...
            }
            // update ONLY if necessary:
            if (flag) fireTableRowsUpdated(row, row); // is right?
        }
    }
}
Gioce90
  • 554
  • 2
  • 10
  • 31
  • so what you are trying to say is that fireTableRowsUpdated(row, row); works in one table, but it needs to reflected in another table? And you want to update just specific rows? BTW don't use repaint, use fireTableDataUpdated() – Oliver Watkins Oct 15 '14 at 12:27
  • yes, if I modify a camp in a row of a table, this modify must to appear also in the other table (sure, if that row exist in the other table). This just works, but I don't know if is a good solution. – Gioce90 Oct 15 '14 at 12:40
  • but you don't know what rows you want to update in the other table? – Oliver Watkins Oct 15 '14 at 12:48
  • 1. by using DefaultTableModel (just with getColumnClass/isCellEditable) isn't required lots of code, 2. then you can to use DefaultTableModel as main data for whole apps (JTable/JTree/nested JComponents), 3missing there ListSelectionListener and little bit customized RowFilter and RowSorter for 2nd JTables view, 4. (addendum to point second) you can to show all columns from XxxTableModel in JTables view (removeColumn) rest of column can be used for JTree/2nd.JTable, 5. in memory database should be advantage for long and hard actions (better optimized enviroment as programing language) – mKorbel Oct 15 '14 at 12:49
  • then just use fireTableDataUpdated(). If you don't notice any performance problems then its fine. – Oliver Watkins Oct 15 '14 at 12:49
  • generally these days fireTableRowUpdate is not really needed (unless you are building a financial trading system). fireTableDataUpdated(..) is usually sufficient in 99.9 % of cases – Oliver Watkins Oct 15 '14 at 12:51
  • [???](http://stackoverflow.com/a/11859210/714968) – mKorbel Oct 15 '14 at 12:56
  • @mKorbel 1 and 2: by using DefaultTableModel I can't do it a lot of other things, for example the dynamic change of the icon that correspond the type of vehicle. I have defined this column in my VehicleTableModel and the icon's change is automatic. Otherwise, with DTM I must always pass the value of the Icon when I add a row. I don't want this. And if I remember, is very complicate manage the rows: I want that correspond at vehicles. If the vehicle changes, the row must change and viceversa. With DTM this is impossible or very difficult. If I remember, DTM works only on Vector & object arrays. – Gioce90 Oct 15 '14 at 13:25
  • wrong, not, you can't, why, short explanations - AbstractTableModel is about to stop, reduce, override with different logics or is there required to use another arrays (e.g. java.util.List), there is everything accessible from JTables and Models methods – mKorbel Oct 15 '14 at 13:29
  • [setValueAt ???](http://stackoverflow.com/a/7049095/714968) – mKorbel Oct 15 '14 at 13:32
  • And if I remember, is very complicate manage the rows: - DYM reordering row in model, otherwise is nonsence (by using DefaultTableModel is possible too) – mKorbel Oct 15 '14 at 13:37

1 Answers1

3

The whole matter starts at TableModel implementation, so let's take a look to it:

public class VehicleTableModel extends AbstractTableModel {

    private ArrayList<Vehicle> vehicles;

    // Most of your code here, didn't examine it closer though

    public void addRow(Vehicle vehicle) {
        int rowIndex = vehicles.size();
        vehicles.add(vehicle);
        fireTableRowsInserted(rowIndex, rowIndex); // just notify last row == vehicles.size() == getRowCount()
    }

    public void removeRow(int row) {
        vehicles.remove(row);
        fireTableRowsDeleted(row, row); // is right? yes, it looks ok.
    }

    @Override
    public void setValueAt(Object value, int row, int col) {
        Vehicle v = vehicles.get(row);
        if (v != null) {
            COLUMNS column = header[col];
            switch (column) {
                case TARGA_COLUMN:...; break;
                case MARCA_COLUMN:...; break;
                // others...
            }
            fireTableCellUpdated(row, column); // this is the appropriate fire method.
        }
    }

    /**
     * Convenience method to notify if a vehicle was updated in 
     * the outside, not through setValueAt(...).
     */
    public void notifyVehicleUpdated(Vehicle vehicle) {
        Vehicle[] elements = (Vehicles[])vehicles.toArray();
        for (int i = 0; i < elements.length; i++) {
            if (elements[i] == vehicle) {
                fireTableRowsUpdated(i, i);
            }
        }
    }

}

Some other hints:

  • Never use repaint() nor updateUI() to refresh table's data. It's table model responsibility to notify the view about the right event.

  • Never use fireTableDataChanged() (as someone suggested) unless the whole table model data has changed. There are appropriate fireXxxx() methods for rows, columns and cells changes.

  • As far as I understand the problem, both tables share the vehicles list and thus you have to keep them in synch. If so, I'm wondering why do you need two different table models? If the only reason is the status available/parked (mutually exclusive) then you can have a single table model shared along two tables and apply different filters accordingly to the vehicle's status. On status field update, both tables will be notified and vehicle will be transferred from one table to another.

Update

Some time ago in a comment to this answer the idea of adding a method such as notifyRowUpdated() to Coordinator abstract class seemed to be appropriated to solve the synchronization matter between both tables.

But now I think the best approach is sharing the same table model along with the two tables and filtering the second table based on vehicle's status: if available (DISPONIBILE) then show it, if not then hide it.

This way on both row update and row delete both tables will be notified and will act accordingly. On cell update we can add a TableModelListener to the model that applies a filter on second table, showing available vehicles and hidding non available ones. Not to mention that Coordinator abstract class will remain simple and keep its original purpose: notify the third-party agent on row updates/deletes.

So please take a look to the code example below (sorry for the extension). Some notes:

  • I have emulated your Vehicle class with a simpler one. Status is defined by available a boolean property.
  • DataObjectTableModel code is available in tag wiki and I've used this class to emulate your table model.
  • Because I don't have any Coordinator class I add/remove rows directly on the table model, but you should do that hrough the appropriate coordinator.
  • Don't know why we have to re-apply filters on table cell update events. As far as I understand the table row sorter should be notified and automatically apply filters. However it doesn't work in this way and we have to manually re-apply fitlers. Minor problem though.

Code example

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.util.Arrays;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultRowSorter;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableColumnModel;

public class DemoSharedTableModel {

    private DataObjectTableModel<Vehicle> model;
    private JTable table1, table2;
    private Action addAction, removeAction;

    private void createAndShowGui() {

        String[] columnIdentifiers = new String[] {
            "Plates",
            "Description",
            "Available"
        };

       model = new DataObjectTableModel<Vehicle>(Arrays.asList(columnIdentifiers)) {

            @Override
            public Class<?> getColumnClass(int columnIndex) {
                switch (columnIndex) {
                    case 0:
                    case 1: return String.class;
                    case 2: return Boolean.class;
                }
                return super.getColumnClass(columnIndex);
            }

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

            @Override
            public Object getValueAt(int rowIndex, int columnIndex) {
                Vehicle vehicle = getDataObject(rowIndex);
                switch (columnIndex) {
                    case 0 : return vehicle.getPlates();
                    case 1: return vehicle.getDescription();
                    case 2: return vehicle.isAvailable();
                        default: throw new ArrayIndexOutOfBoundsException(columnIndex);
                }
            }

            @Override
            public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
                if (columnIndex == 2) {
                    Vehicle vehicle = getDataObject(rowIndex);
                    vehicle.setAvailable((Boolean)aValue);
                    fireTableCellUpdated(rowIndex, columnIndex);
                } else {
                    throw new UnsupportedOperationException("Unsupported for column " + columnIndex);
                }
            }
        };

        model.addRow(new Vehicle("AAA1", "Car - Peugeot", true));
        model.addRow(new Vehicle("AAA2", "Truck - Volvo", true));
        model.addRow(new Vehicle("AAA3", "Car - Ford", false));
        model.addRow(new Vehicle("AAA4", "Car - Mercedes-Benz", false));
        model.addRow(new Vehicle("AAA5", "Car - Ferrari", true));

        model.addTableModelListener(new TableModelListener() {
            @Override
            public void tableChanged(TableModelEvent e) {
                if (e.getType() == TableModelEvent.UPDATE) {
                    DemoSharedTableModel.this.applyFilterOnSecondTable();
                }
            }
        });

        table1 = new JTable(model);
        table1.setAutoCreateRowSorter(true);
        table1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        table2 = new JTable(model);
        table2.setAutoCreateRowSorter(true);
        table2.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        // Make third column not visible
        TableColumnModel columnModel = table2.getColumnModel();
        columnModel.removeColumn(columnModel.getColumn(2));

        applyFilterOnSecondTable();

        addAction = new AbstractAction("+") {
            @Override
            public void actionPerformed(ActionEvent e) {
                model.addRow(new Vehicle("new", "default text", true));
            }
        };

        removeAction = new AbstractAction("-") {
            @Override
            public void actionPerformed(ActionEvent e) {
                int viewIndex = table1.getSelectedRow();
                if (viewIndex != -1) {
                    int modelIndex = table1.convertRowIndexToModel(viewIndex);
                    model.deleteRow(modelIndex);
                }
                setEnabled(model.getRowCount() > 0);
            }
        };

        JPanel buttonsPanel = new JPanel();
        buttonsPanel.add(new JButton(addAction));
        buttonsPanel.add(new JButton(removeAction));

        JPanel content = new JPanel(new BorderLayout(8, 8));
        content.add(new JScrollPane(table1), BorderLayout.WEST);
        content.add(buttonsPanel, BorderLayout.CENTER);
        content.add(new JScrollPane(table2), BorderLayout.EAST);

        JFrame frame = new JFrame("Demo");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(content);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private void applyFilterOnSecondTable() {
        DefaultRowSorter sorter = (DefaultRowSorter)table2.getRowSorter();
        sorter.setRowFilter(new RowFilter() {
            @Override
            public boolean include(RowFilter.Entry entry) {
                Vehicle vehicle = model.getDataObject((Integer)entry.getIdentifier());
                return vehicle.isAvailable();
            }
        });
    }

    class Vehicle {

        private String plates, description;
        private Boolean available;

        public Vehicle(String plates, String description, Boolean available) {
            this.plates = plates;
            this.description = description;
            this.available = available;
        }

        public String getPlates() {
            return plates;
        }

        public void setPlates(String plates) {
            this.plates = plates;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }

        public Boolean isAvailable() {
            return available;
        }

        public void setAvailable(Boolean available) {
            this.available = available;
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new DemoSharedTableModel().createAndShowGui();
            }
        });
    }
}

Screenshot

Note that in second table only available vehicles are displayed.

enter image description here

Community
  • 1
  • 1
dic19
  • 17,821
  • 6
  • 40
  • 69
  • Point 1 and 2: good! Thanks. Point 4: But the agent MUST BE notified. Why you doubt? In xxxCoordinator.notifyAndxxxRow() I use the shipperAgent agent. And in future I will use a lot more. For point 3... I have to reason about. – Gioce90 Oct 15 '14 at 14:03
  • Sorry, I've got lost and overlooked a reference to shipperAgent in your first class. Just removed 4th point in my answer. @Gioce90 – dic19 Oct 15 '14 at 14:13
  • ;) it's ok. You can found the two coordinators after the declaration of the Coordinator inner class. Now, I must understand where use your *notifyVehicleUpdated(..)* – Gioce90 Oct 15 '14 at 14:21
  • In *setValueAt* I have leave the flag and the various if. If I clic on a cell but don't modify anything, I don't want that fire method propagates an useless event :) but thank you for the suggest of *fireTableCellUpdated*. – Gioce90 Oct 15 '14 at 14:41
  • You are welcome :) It makes sense to me. IMHO you should think about a single table model and different table filters, if it suits your requirements of course. Then you won't have to notify in case of row updated (*fireTableCellUpdated(...)* should be enough) and `Coordinator` will keep as simple as possible. @Gioce90 – dic19 Oct 15 '14 at 14:49
  • I am considering your proposal for a single VehicleTableModel, but first I'll have to investigate other changes I have to do (long story). And I still have not understand how use that your notifyVehicleUpdated () ... the last time you spoke to solve my problem upgrading. In an alternative way to my listeners. How, specifically? – Gioce90 Oct 15 '14 at 15:02
  • Please see my update. Sorry for the extension and I hope it be helpful. @Gioce90 – dic19 Oct 15 '14 at 22:56
  • If you want to keep your current approach, then *notifyVehicleUpdated()* should be called inside of both *notifyAndXxxxRow()* methods, because its intention, at least my first idea, is to notify the other Coordinator/model that something has happened and it should act in consequence. @Gioce90 – dic19 Oct 15 '14 at 23:54
  • I appreciate your suggestion on using a single model, I'll keep it in consideration. But right now I think I'll keep my current implementation, because I may decide to add other tables in the future ... and who knows what else. But like I said, I'm not sure, so your implementation in the future could be very useful. Instead, as far as your original suggestion, I'm trying to apply it ... but it does not work. – Gioce90 Oct 16 '14 at 08:12