在不丢失同步的情况下加载 JTable

Loading a JTable without losing sync

这个例子有一个 JDViewCustomer 对话框,它有两个按钮 <> 来异步重新加载 JTable (我添加了一些 Thread.sleep() 调用来模拟网络负载)。

快速按下按钮会导致应用程序与屏幕失去同步(请参阅 class LoadCustomerOrdersWorker.java,它还会在加载数据后执行 revalidate/repaint)。

我怎样才能防止这种情况发生?

Test.java

package testrepaint;

public class Test {

    private static JFrame frame;

    private static void createAndShowGUI() {
        frame = new JFrame("Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        run();

        frame.pack();
        frame.setVisible(true);
    }

    private static void run() {

        List<CustomerOrder> customerOrders = new ArrayList<>();
        customerOrders.add(new CustomerOrder(15, 60.0f));
        customerOrders.add(new CustomerOrder(16, 280.0f));
        customerOrders.add(new CustomerOrder(17, 150.53f));
        customerOrders.add(new CustomerOrder(18, 280.0f));

        new JDViewCustomer(frame, true, customerOrders).setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

JDViewCustomer.java

package testrepaint;

public class JDViewCustomer extends javax.swing.JDialog {

    private static final long serialVersionUID = 1L;

    private static final ExecutorService SINGLE_THREAD_POOL = Executors.newSingleThreadExecutor();

    private JPanel jPanelCustomerOrders;
    private JPanel panelContainingTheOtherPanels;
    private JPanel panelAbaixoDeCustomerOrders;
    private JPanel panelContendoOsPedidos;

    private final List<CustomerOrder> customerOrders;
    private JPanel panelTituloPedidos;
    private JLabel lblPedidos;

    private JTable tableOrders;
    private JPanel panelPagination;
    private JButton btnPrevious;
    private JButton btnNext;
    private JLabel lblPageSelection;

    private int currentPage = 1;
    private int maxPage = 3;

    public JDViewCustomer(java.awt.Frame parent, boolean modal, List<CustomerOrder> customerOrders) {
        super(parent, modal);
        this.customerOrders = new ArrayList<>(Objects.requireNonNull(customerOrders));

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        panelContainingTheOtherPanels = new JPanel();
        panelContainingTheOtherPanels.setLayout(new BoxLayout(panelContainingTheOtherPanels, BoxLayout.Y_AXIS));
        getContentPane().add(panelContainingTheOtherPanels);

        layoutCustomerOrdersSection();
        updateCustomerOrdersTable();

        updateCurrentPageTextField();
        toggleButtonsPreviousAndNext();

        initComponents();
    }

    private void layoutCustomerOrdersSection() {

        jPanelCustomerOrders = new JPanel();
        jPanelCustomerOrders.setBackground(new Color(255, 255, 255));
        panelContainingTheOtherPanels.add(jPanelCustomerOrders);
        jPanelCustomerOrders.setLayout(new BoxLayout(jPanelCustomerOrders, BoxLayout.Y_AXIS));


        panelAbaixoDeCustomerOrders = new JPanel();
        panelAbaixoDeCustomerOrders.setBorder(new LineBorder(new Color(0, 0, 0), 1, true));
        jPanelCustomerOrders.add(panelAbaixoDeCustomerOrders);
        panelAbaixoDeCustomerOrders.setLayout(new BoxLayout(panelAbaixoDeCustomerOrders, BoxLayout.Y_AXIS));

                panelTituloPedidos = new JPanel();
                panelAbaixoDeCustomerOrders.add(panelTituloPedidos);
                panelTituloPedidos.setBorder(new LineBorder(new Color(0, 0, 0), 1, true));
                panelTituloPedidos.setBackground(Color.LIGHT_GRAY);

                lblPedidos = new JLabel("Pedidos");
                panelTituloPedidos.add(lblPedidos);

        panelContendoOsPedidos = new JPanel();
        panelContendoOsPedidos.setBorder(new CompoundBorder(new LineBorder(new Color(0, 0, 0), 1, true), new EmptyBorder(10, 10, 10, 10)));
        panelContendoOsPedidos.setBackground(Color.WHITE);
        panelAbaixoDeCustomerOrders.add(panelContendoOsPedidos);
        panelContendoOsPedidos.setLayout(new BoxLayout(panelContendoOsPedidos, BoxLayout.Y_AXIS));

        panelPagination = new JPanel();
        panelAbaixoDeCustomerOrders.add(panelPagination);

        addPaginationComponentsToPaginationPanel();
    }

    private void addPaginationComponentsToPaginationPanel() {
        btnPrevious = new JButton("<");
        btnPrevious.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                performButtonPrevious(e);
            }
        });
        btnPrevious.setFont(new Font("Tahoma", Font.BOLD, 11));
        panelPagination.add(btnPrevious);

        lblPageSelection = new JLabel("0");
        panelPagination.add(lblPageSelection);

        btnNext = new JButton(">");
        btnNext.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                performButtonNext(e);
            }
        });
        btnNext.setFont(new Font("Tahoma", Font.BOLD, 11));
        panelPagination.add(btnNext);
    }

    private void performButtonPrevious(ActionEvent e) {

        if (currentPage > 1) {
            currentPage -= 1;
            updateCurrentPageTextField();
        }

        toggleButtonsPreviousAndNext();

        loadCustomerOrders();
    }

    private void performButtonNext(ActionEvent e) {
        if (currentPage < maxPage) {
            currentPage += 1;
            updateCurrentPageTextField();
        }

       toggleButtonsPreviousAndNext();

       loadCustomerOrders();
    }

    private void loadCustomerOrders() {
        LoadCustomerOrdersWorker worker = new LoadCustomerOrdersWorker(customerOrders, this);
        SINGLE_THREAD_POOL.execute(worker);
    }

    private void toggleButtonsPreviousAndNext() {
        togglePreviousButton();
        toggleNextButton();
    }

    private void togglePreviousButton() {
        btnPrevious.setEnabled(currentPage > 1);
    }

    private void toggleNextButton() {
        btnNext.setEnabled(currentPage < maxPage);
    }

    private void updateCurrentPageTextField() {
        lblPageSelection.setText(String.valueOf(currentPage));
    }

    private void updateCustomerOrdersTable() {

        TableModel tableModel = instantiateAbstractTableModel();

        tableOrders = new JTable(tableModel) {
            private static final long serialVersionUID = 1L;

            public Dimension getPreferredScrollableViewportSize() {
                return getPreferredSize();
            }            
        };

        centralizeTextInTableCells();

        //tableOrders.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        TableColumnModel colModel = tableOrders.getColumnModel();
        colModel.getColumn(2).setPreferredWidth(80);
        colModel.getColumn(3).setPreferredWidth(250);

        // Disables manual reordering of columns.
        tableOrders.getTableHeader().setReorderingAllowed(false);

        JScrollPane scrollPane = new JScrollPane(tableOrders);
        panelContendoOsPedidos.add(scrollPane);
    }

    private AbstractTableModel instantiateAbstractTableModel() {
        return new AbstractTableModel() {
            private static final long serialVersionUID = 1L;

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

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

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

            @Override
            public String getColumnName(int column) {
                switch(column) {
                case 0: return "Status";
                case 1: return "Número";
                case 4: return "Total";
                default: return "";
                }
            }

            @Override
            public Object getValueAt(int rowIndex, int columnIndex) {
                CustomerOrder customerOrder = customerOrders.get(rowIndex);
                switch(columnIndex) {
                case 0: return customerOrder;
                case 1: return "#" + String.valueOf(customerOrder.getNumber());
                case 4: return String.format("%.2f", customerOrder.getTotal());
                default: return "";
                }
            }

            @Override
            public Class<?> getColumnClass(int columnIndex) {
                switch(columnIndex) {
                case 0: return CustomerOrder.class;
                case 1: return String.class;
                case 4: return String.class;
                default: return Object.class;
                }
            }
        };
    }

    private void centralizeTextInTableCells() {
        DefaultTableCellRenderer stringCellRenderer = (DefaultTableCellRenderer)tableOrders.getDefaultRenderer(String.class);
        stringCellRenderer.setHorizontalAlignment(JLabel.CENTER);
        tableOrders.setDefaultRenderer(String.class, stringCellRenderer);
    }

    private void initComponents() {

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        setTitle("Ver Cliente");

        pack();

        // Para centralizar o JDialog em relação a seu parent.
        setLocationRelativeTo(getParent());

    }
}

LoadCustomerOrdersWorker.java

package testrepaint;

public class LoadCustomerOrdersWorker extends SwingWorker<Void, Void> {

    private final List<CustomerOrder> customerOrders;
    private final JDViewCustomer jDialogViewCustomer;

    public LoadCustomerOrdersWorker(List<CustomerOrder> customerOrders, JDViewCustomer jDialogViewCustomer) {
        this.customerOrders = customerOrders;
        this.jDialogViewCustomer = jDialogViewCustomer;
    }

    @Override
    protected Void doInBackground() throws Exception {

        this.customerOrders.clear();
        Thread.sleep(300);
        this.customerOrders.add(new CustomerOrder(15, 60.0f));
        Thread.sleep(300);
        this.customerOrders.add(new CustomerOrder(16, 280.0f));
        Thread.sleep(300);
        this.customerOrders.add(new CustomerOrder(17, 150.53f));
        Thread.sleep(300);
        this.customerOrders.add(new CustomerOrder(18, 280.0f));
        Thread.sleep(300);
        this.customerOrders.add(new CustomerOrder(19, 280.0f));
        Thread.sleep(300);

        return null;
    }

    @Override
    protected void done() {
        jDialogViewCustomer.revalidate();
        jDialogViewCustomer.repaint();
    }
}

CustomerOrder.java

package testrepaint;

public class CustomerOrder {

    private final int number;
    private final float total;


    public CustomerOrder(int number, float total) {
        this.number = number;
        this.total = total;
    }

    public int getNumber() {
        return number;
    }

    public float getTotal() {
        return total;
    }

    @Override
    public String toString() {
        return "toString()";
    }
}

你更新一个table的基本概念是错误的:

  1. TableModel 应该包含数据结构。也就是说 ArrayList 应该是 TableModel 的一部分。然后 TableModel 应该有动态修改数据的方法。

  2. 应该对 TableModel 进行更新。然后 TableModel 将通知 table 重新绘制自己。

  3. 有关如何创建包含上述建议的自定义 TableModel 的 step-by-step 示例,请参阅 Table Row Model

  4. 必须在 Event Dispatch Thread (EDT) 上更新 Swing 组件(及其数据)。在 doInBackground() 方法中执行的代码不会 运行 编辑。因此,您需要 publish 结果,然后使用 CustomOrder 更新 TableModel,而不是直接更新 ArrayList。

阅读 Swing 教程中有关 Tasks That Have Interim Results 的部分,以获取显示 publish() 方法如何工作的示例。 publish() 方法中执行的代码确实在 EDT 上执行。

  1. 不需要在done()方法中调用revalidate()和repaint()。如前所述,当您更新 TableModel 时,它会导致 table 自动更新。