如何在使用TableModel的JTable中刷新数据

4

你好,

我已经创建了我的TableModel,并希望在添加新行后刷新JTable。在监听器中需要添加什么来“刷新”JTable?

public class MyTableModel implements TableModel  {
    private Set<TableModelListener> listeners = new HashSet<TableModelListener>();

    //List<Staff> staffs = Factory.getInstance().getStaffDAO().getAllStaff();
    private List<Staff> staffs;

    public MyTableModel(List<Staff> staffs){
        this.staffs = staffs;
    }

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

    @Override
    public int getColumnCount() {
        return 5;  
    }

    @Override
    public String getColumnName(int columnIndex) {
        switch (columnIndex){
            case 0:
                return "First Name";
            case 1:
                return "Second Name";
            case 2:
                return "Date";
            case 3:
                return "Position";
            case 4:
                return "Salary";
        }
        return "";  
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return Object.class;  
    }

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

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Staff staff = staffs.get(rowIndex);
        switch (columnIndex){
            case 0:
                return staff.getName();
            case 1:
                return staff.getSurname();
            case 2:
                return staff.getDate();
            case 3:
                return staff.getPosition();
            case 4:
                return staff.getSalary();
        }
        return "";  
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
    }

    @Override
    public void addTableModelListener(TableModelListener l) {
    }

    @Override
    public void removeTableModelListener(TableModelListener l) {
    }
}

这是我的“添加行”按钮的监听器代码:

 @Override
    public void actionPerformed(ActionEvent e) {
        Staff staff = new Staff();
        staff.setName(JOptionPane.showInputDialog("Enter First Name"));
        staff.setSurname(JOptionPane.showInputDialog("Enter Second Name"));
        staff.setDate(JOptionPane.showInputDialog("Enter Date"));
        staff.setPosition(JOptionPane.showInputDialog("Enter Position"));
        staff.setSalary(JOptionPane.showInputDialog("Enter Salary"));
        try {
            Factory.getInstance().getStaffDAO().addStaff(staff);
        } catch (SQLException e1) {
            e1.printStackTrace();  
        }
!!!Here should be some code that will be firing my table after adding new row!!!
}

我尝试在我的actionPerformed()方法中使用AbstractTableModel的firetabledatachanged()方法,但不幸的是,出现了ClassCastException。

更新1

WorkPlaceGui.java

public class WorkPlaceGui extends JFrame implements ActionListener {

    AbstractTableModel model;
    JTable jTable;
    JScrollPane jScrollPane;

    public WorkPlaceGui()throws SQLException{


        List<Staff> staffs = Factory.getInstance().getStaffDAO().getAllStaff();
        for(int i = 0; i < 0; i++) {
                staffs.add(new Staff("First Name " + staffs.get(i).getName(), "Second Name " + staffs.get(i).getSurname(), "Date " + staffs.get(i).getDate(), "Position " + staffs.get(i).getPosition(), "Salary " + staffs.get(i).getSalary()));
        }

        model = new MyTableModel(staffs);
        jTable = new JTable(model);
        JButton jBtnAdd = new JButton("Добавить");
        JButton jBtnDel = new JButton("Удалить");
        JButton jBtnUpd = new JButton("Обновить");
        JButton jBtnAdmin = new JButton("Админка");
        JPanel panelNorth = new JPanel();
        JPanel panelCenter = new JPanel();
        JPanel panelSouth = new JPanel();
        jTable.setPreferredScrollableViewportSize(new Dimension(350, 150));
        jScrollPane = new JScrollPane(jTable);


        panelNorth.setLayout(new FlowLayout());
        panelNorth.add(jBtnAdd);
        panelNorth.add(jBtnDel);
        panelNorth.add(jBtnUpd);
        panelNorth.add(jBtnAdmin);
        panelCenter.add(jScrollPane);

        setLayout(new BorderLayout());
        add(panelNorth, BorderLayout.NORTH);
        add(panelCenter, BorderLayout.CENTER);

        jBtnAdd.addActionListener(this);

        setPreferredSize(new Dimension(550, 300));
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setTitle("Staff data base");
        pack();
        setVisible(true);
        setLocationRelativeTo(null);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        Staff staff = new Staff();
        staff.setName(JOptionPane.showInputDialog("Enter First Name"));
        staff.setSurname(JOptionPane.showInputDialog("Enter Second Name"));
        staff.setDate(JOptionPane.showInputDialog("Enter Date"));
        staff.setPosition(JOptionPane.showInputDialog("Enter Position"));
        staff.setSalary(JOptionPane.showInputDialog("Enter Salary"));
        try {
            Factory.getInstance().getStaffDAO().addStaff(staff);
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
        model.fireTableDataChanged();
    }
}

MyTableModel.java

public class MyTableModel extends AbstractTableModel {

    private List<Staff> staffs;

    public MyTableModel(List<Staff> staffs){
        this.staffs = staffs;
    }

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

    @Override
    public int getColumnCount() {
        return 5;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Staff staff = staffs.get(rowIndex);
        switch (columnIndex){
            case 0:
                return staff.getName();
            case 1:
                return staff.getSurname();
            case 2:
                return staff.getDate();
            case 3:
                return staff.getPosition();
            case 4:
                return staff.getSalary();
        }
        return "";
    }
}


为了更快地获得帮助,请发布一个SSCCE,这是一个简短的、可运行的、可编译的示例,仅涉及包含硬编码值的JFrame和JTable的TableModel,问题可能出在任何地方。 - mKorbel
6个回答

13

你以一种较为困难的方式完成了它。

首先,你直接从TableModel实现,其次你未能实现所需的监听器要求......

相反,尝试从AbstractTableModel继承,该类已经包括了监听器注册和通知的实现。

您需要提供一个方法来允许您向表格模型添加行。在此方法中,您需要使用fireTableRowsInserted方法,该方法将通知使用该模型的任何表格,已经添加了新行...

更新示例

这是非常基本的示例。它的唯一目的是演示使用fireTableRowsInserted的方法。它使用Swing的Timer每125毫秒添加一行新数据,直到被终止 ;)

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;

public class DynamicTable {

    public static void main(String[] args) {
        new DynamicTable();
    }

    public DynamicTable() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }
                
                final MyTableModel model = new MyTableModel();
                JTable table = new JTable(model);
                
                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(table));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
                
                Timer timer = new Timer(125, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        model.addRow();
                    }
                });
                timer.start();
            }
        });
    }
    
    public class MyTableModel extends AbstractTableModel {

        private List<String[]> rows;

        public MyTableModel() {
            rows = new ArrayList<>(25);
        }
        
        @Override
        public int getRowCount() {
            return rows.size();
        }

        @Override
        public int getColumnCount() {
            return 4;
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return String.class;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            String[] row = rows.get(rowIndex);
            return row[columnIndex];
        }
        
        public void addRow() {
            int rowCount = getRowCount();
            String[] row = new String[getColumnCount()];
            for (int index = 0; index < getColumnCount(); index++) {
                row[index] = rowCount + "x" + index;
            }
            rows.add(row);
            fireTableRowsInserted(rowCount, rowCount);
        }            
    }                    
}

增加了另一个例子的更新

由于你的表格模型是由自己的List支持的,它与你的工厂没有任何联系。 它不知道何时添加或删除对象。 这意味着您需要负责更新模型:

public class MyTableModel extends AbstractTableModel {

    private List<Staff> staffs;

    public MyTableModel(List<Staff> staffs){
        this.staffs = staffs;
    }

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

    @Override
    public int getColumnCount() {
        return 5;
    }
    
    public void add(Staff staff) {
        int size = getSize();
        staffs.add(staff);
        fireTableRowsInserted(size, size);
    }

    public void remove(Staff staff) {
        if (staffs.contains(staff) {
            int index = stafff.indexOf(staff);
            staffs.remove(staff);
            fireTableRowsDeleted(index, index);
        }
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Staff staff = staffs.get(rowIndex);
        switch (columnIndex){
            case 0:
                return staff.getName();
            case 1:
                return staff.getSurname();
            case 2:
                return staff.getDate();
            case 3:
                return staff.getPosition();
            case 4:
                return staff.getSalary();
        }
        return "";
    }
}

并且您的 actionPerformed 方法:

@Override
public void actionPerformed(ActionEvent e) {
    Staff staff = new Staff();
    staff.setName(JOptionPane.showInputDialog("Enter First Name"));
    staff.setSurname(JOptionPane.showInputDialog("Enter Second Name"));
    staff.setDate(JOptionPane.showInputDialog("Enter Date"));
    staff.setPosition(JOptionPane.showInputDialog("Enter Position"));
    staff.setSalary(JOptionPane.showInputDialog("Enter Salary"));
    try {
        Factory.getInstance().getStaffDAO().addStaff(staff);
        ((MyTableModel)model).add(staff);
    } catch (SQLException e1) {
        e1.printStackTrace();  
    }
}

我已经切换到扩展 AbstractTableModel 的解决方案,并添加了方法 **fireTableDataChanged()**,但它没有重绘我的表格。 - devger
MadProgrammer,我已将其添加在UPDATE1中。 - devger
谢谢您的帮助,但我仍然不知道如何在我的代码中实现addRow()函数。 - devger
1
我已经为你更新了代码。你需要提高将抽象概念应用于解决问题的能力。从长远来看,这将使你成为更强大的开发人员,因为你很难找到完全符合你问题的解决方案 ;) - MadProgrammer
对我来说,看到add()方法的实现以及将Staff与我的模型绑定是很有趣的。我不知道如何实现这一点,有些困惑,因为我添加了以下代码:AbstractTableModel model = new MyTableModel(); JTable jTable = new JTable(model);,实际上我认为这样做了绑定,但事实上并没有。 - devger
显示剩余4条评论

8
你的类 MyTableModel 实现了 TableModel,但是它没有事件处理机制来连接模型和视图。相反,可以扩展 AbstractTableModel,如此处此处所示。 AbstractTableModel 提供了所需的 fireTable* 方法来实现此功能。
public void setValueAt(Object value, int row, int col) {
    data[row][col] = value;
    fireTableCellUpdated(row, col);
}

我不能再点赞了,但是你提到在setValueAt方法中使用fireTableCellUpdated的建议很好! - MadProgrammer
1
无耻地从引用的教程中改编。 :-) - trashgod

3

首先,了解更多关于观察者模式 (http://en.wikipedia.org/wiki/Observer_pattern)。

我建议您创建一个ObservableModel类,其中包含一个PropertyChangeListeners列表。您的StaffDAO应该扩展这个ObservableModel。当添加新员工时(即调用addStaff),您应该调用ObservableModel的firePorpertyChange或类似的方法。 firePropertyChange会通知所有propertyChangeListeners。其中之一监听器应该在您的表格中注册,并且其propertyChanged方法应该实现为刷新表格(hd1有很好的回答)。


有趣的想法,但我不确定PropertyChangeListener是否是最合适的方法,因为它表明对象的单个属性已更改,而添加或删除则意味着需要更大的更改要求-在我看来。如果可能的话,提供一个定制的接口会更有用,该接口可以提供有关模型状态更改的更有意义的信息,并包括对受影响的所有行的引用,这样可以更轻松地确定已经发生了什么更改(因为问题行可能已被插入) - MadProgrammer

2
"myTableModel.fireTableDataChanged();"应该足以强制刷新您的表格。通常情况下,如果您有进一步的问题,请随时留言。"

但是 TableModel 没有 fireTableDataChanged,我所知道的唯一实现是 protected - MadProgrammer
你应该继承DefaultTableModel,而不是自己实现它。 - hd1
1
不一定。fireTableDataChanged是来自AbstractTableModel的,这可能更适合(我个人认为经常如此)满足人们的需求。我的观点是,OP已经将TableModel作为表模型的基础,并且它没有fireTableDataChanged,因此你目前的建议行不通。 不过思路是正确的... - MadProgrammer
@MadProgrammer 是正确的;DefaultTableModel 继承了 AbstractTableModel 的事件处理机制,但它会让你陷入 java.util.Vector 的技术死胡同。 - trashgod

2

为了获得更一般的解决方案,您可以使用行表格模型,只需实现 getValueAt() 和 setValueAt() 方法。

这里应该有一些代码,将在添加新行后触发我的表格!

该模型负责调用适当的fireXXX方法。


1
使用这种类型的强制转换。
((AbstractTableModel)student.getModel()).fireTableCellUpdated();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接