正确的方法来更新复杂的JTables,TableModel和其他 [英] The right approach to update complex JTables, TableModel and others

查看:74
本文介绍了正确的方法来更新复杂的JTables,TableModel和其他的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的GUI显示了我 park 中的车辆,以及我想在两个不同的 VehicleTables (扩展JTable的类)中设置 availables 的车辆). 对于可用产品,我希望可以通过 agent (第三方软件)观察这些车辆. 这两个表都在行中显示了Vehicles的描述...为此,我创建了 VehicleTableModel Vehicle 类. Vehicle类是抽象类,他的子类是:汽车,卡车,拖车等.

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:

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

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...

...另一种方式?

ShipperAgentGUI.java

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

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

推荐答案

整个问题始于TableModel实现,因此让我们来看一下:

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

}

其他一些提示:

  • 从不 使用repaint()updateUI()刷新表数据.将正确的事件通知给视图是表模型的责任.

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

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

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.

不久前,在对此答案的评论中notifyRowUpdated()等方法添加到抽象类似乎适合解决两个表之间的同步问题.

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.

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

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:

  • 我用一个简单的类来模拟您的Vehicle类.状态由可用的布尔属性定义.
  • DataObjectTableModel代码在的代码中提供标记 wiki ,并且我已经使用此类来模拟您的表模型.
  • 因为我没有任何Coordinator类,所以我直接在表模型上添加/删除行,但是您应该通过适当的协调器来做到这一点.
  • 不知道为什么我们必须对表单元格更新事件重新应用过滤器.据我了解,应该通知表行排序器并自动应用过滤器.但是,它不能以这种方式工作,我们必须手动重新应用拟合器.较小的问题.
  • I have emulated your Vehicle class with a simpler one. Status is defined by available a boolean property.
  • DataObjectTableModel code is available in tablemodel 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.
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.

这篇关于正确的方法来更新复杂的JTables,TableModel和其他的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆