更新复杂JTable,TableModel等的正确方法

时间:2014-10-15 12:22:42

标签: java swing jtable tablemodel

我的GUI显示我的 park 中的车辆,以及我想在两个不同的 VehicleTables 中设置 availables 的车辆(扩展JTable的类) )。 对于可用的,我打算从代理(第三方软件)中观察这些车辆。 这两个表格都显示了行中车辆的描述...为此我创建了 VehicleTableModel 车辆类。 Vehicle类是一个抽象类,他的子类是:Car,Truck,Trailer等。

您可以看到我的软件的快照: enter image description here

我的问题是这些: 在我目前的实施中,我没有想到管理 更新 。您可以在VehicleTableModel(fire ...()方法)和ShipperAgentGUI(协调器和侦听器)中看到。我认为通过使用协调器内部类来表示表之间的更新,我已经部分解决了这个问题,但我不知道如何优化这些。 例如,在删除或更新行的情况下,我制作 xxxTable.repaint(); ......整个表......

......另一种方式?

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?
        }
    }
}

1 个答案:

答案 0 :(得分:2)

整个问题始于TableModel实施,所以让我们来看看它:

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);
            }
        }
    }

}

其他一些提示:

  • 从不 使用repaint()updateUI()来刷新表格数据。它的表模型负责通知有关正确事件的视图。

  • 从不 使用fireTableDataChanged()(如有人建议的那样),除非整个表模型数据已更改。行,列和单元格更改有适当的fireXxxx()方法。

  • 据我所知,这两个表共享车辆列表,因此您必须保持同步。如果是这样,我想知道你为什么需要两种不同的桌面模型?如果唯一的原因是状态可用/停放(互斥),那么您可以在两个表中共享一个表模型,并根据车辆的状态应用不同的过滤器。在状态字段更新时,将通知两个表,并且车辆将从一个表 转移到另一个表。

更新

前段时间在对this answer的评论中,将notifyRowUpdated()等方法添加到Coordinator抽象类的想法似乎适用于解决两个表之间的同步问题。 / p>

但是现在我认为最好的方法是与两个表共享相同的表模型,并根据车辆的状态过滤第二个表:如果可用(DISPONIBILE)然后显示它,如果没有则隐藏它。

这种方式在行更新和行删除两个表都将被通知并将采取相应的行动。在单元格更新中,我们可以将TableModelListener添加到在第二个表上应用过滤器的模型,显示可用的车辆并隐藏不可用的车辆。更不用说Coordinator抽象类将保持简单并保持其原始目的:在行更新/删除时通知第三方代理。

所以请看下面的代码示例(对不起扩展名)。一些说明:

  • 我用更简单的课程模拟了你的Vehicle课程。状态由可用的布尔属性定义。
  • DataObjectTableModel代码位于代码wiki,我已使用此类来模拟您的桌面模型。
  • 因为我没有Coordinator类,所以我直接在表模型上添加/删除行,但是你应该通过适当的协调器来完成。
  • 不知道为什么我们必须在表格单元更新事件上重新应用过滤器。据我所知,应通知表行分拣机并自动应用过滤器。但是它不会以这种方式工作,我们必须手动重新应用装配工。虽然是小问题。

代码示例

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();
            }
        });
    }
}

截图

请注意,在第二个表格中仅显示可用的车辆。

enter image description here