简体   繁体   中英

Deleting multiple rows freeze JTable

I am trying to delete multiple rows (per example, five of fifty) in a Jtable, but I can only delete one at time (I am using multiple interval selection before you ask!), and I feel the Jtable freeze a bit. My delete button:

deleteButton.addActionListener(new java.awt.event.ActionListener() {
 public void actionPerformed(java.awt.event.ActionEvent e){                                      
SwingUtilities.invokeLater(new Runnable() {
  public void run(){ 
int[] selectedRow = jTable.getSelectedRows(); 
 for(int j=0; j<selectedRow.length; j++){
                     Boolean state= (Boolean)jTable.getValueAt(selectedRow[j],10); 
                     if(state==true){//deleta the row
                         User u=model.getUsers(selectedRow[j]);
                         new UserDao().delete(u);
                         model.remove(selectedRow[j]);
                         numberField.setText(String.valueOf(model.getRowCount()));
                     }
                 }
                     }
   });               
 }
});

My remove:

public void remove(int row) {
this.userlist.remove(row);
this.fireTableDataChanged();
}

What I am doing wrong?

Let's take a closer look at the code...

deleteButton.addActionListener(new java.awt.event.ActionListener() {
    public void actionPerformed(java.awt.event.ActionEvent e){     
        // Don't know why you need to use invokeLater
        // The event should be trigged within the EDT if the user
        // clicked the button.  This may introduce a small "pause"
        // depending on what else is in the EDT...
        SwingUtilities.invokeLater(new Runnable() {
            public void run(){ 
                // Get the indices of the selected rows...okay
                int[] selectedRow = jTable.getSelectedRows(); 
                // Loop through the selected rows...good...
                for(int j=0; j<selectedRow.length; j++){
                    // Get the "state" of the row...okay
                    Boolean state= (Boolean)jTable.getValueAt(selectedRow[j],10); 
                    // Long winded if, but okay...
                    if(state==true){//deleta the row
                        // Don't know what's going on here,
                        // But I assume you are trying to delete
                        // something from some kind of database
                        // THIS is likely to chew up some time...
                        User u=model.getUsers(selectedRow[j]);
                        new UserDao().delete(u);
                        // Uh oh...
                        // If you remove a row from the model, the list of indices you
                        // have is now invalid, as they no longer point
                        // to the correct rows in the model
                        model.remove(selectedRow[j]);
                        numberField.setText(String.valueOf(model.getRowCount()));
                    }
                }
            }
        });               
    }
});

So. Two problems.

  1. You seem to be calling some kind of management functionality within the context of the EDT, functionality that "looks" like it's going to cause the EDT to be slowed/paused for even a small amount of time...
  2. You are relying on out of date information...

A better solution would be to use some kind of background process to perform the deleting of the User and provide a means within the model to look up the User object itself and remove it from the model. This removes the possibility of the indices being changed under you.

A SwingWorker provides a means by which we can perform operations in the background, off the Event Dispatching Thread, while provide means to re-sync required actions (like modifying the table model) back onto the EDT when required...

For example...

public class DeleteUsersWorker extends SwingWorker<List<User>, User> {

    private UserTableModel model;
    private List<User> users;

    public DeleteUsersWorker(UserTableModel model, List<User> users) {
        this.model = model;
        this.users = users;
    }

    protected List<User> doInBackground() {
        UserDao dao = new UserDao();
        for (User user : users) {
            dao.delete(user);
            publish(user);
        }
        return users;
    }

    protected void process(List<User> users) {
        for (User user : users) {
            model.remove(user);
        }
    }
}

And the contents of the actionPerformed method...

int[] selectedRow = jTable.getSelectedRows(); 
List<User> usersToBeRemoved = new ArrayList<>(selectedRow.length);
for(int row : selectedRow){
    // Is state part of the User object??
    Boolean state = (Boolean)jTable.getValueAt(row,10); 
    if(state){
        usersToBeRemoved.add(model.getUsers(row));
    }
}
DeleteUsersWorker worker = new DeleteUsersWorker(model, users);
worker.execute();

This will probably require to add some additional functionality to the table model to support removing the User object from the model, but I don't have your model, so it's difficult to make suggestions...

Take a look at Concurrency in Swing for more details...

A better solution might be to have a listener on your dao API that could provide notifications about updates, this way the model could update itself, but again, not enough context to make a determination ;)

Updated with comments form TrashGod

You should also beware that the view indices don't always map directly to the model indices. This happens when the table is sorted or filtered. While you might argue that your table isn't (sorted or filtered) it is good practice never to make such assumptions...

When taking a row index from the table, you should call JTable#convertRowIndexToModel(int) which will return you the index point in the model

Take a look at Sorting and Filtering for more details...

Update with runnable example

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.Arrays;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;

public class TableDeleteRowsTest {

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

    public TableDeleteRowsTest() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                final UserTableModel model = new UserTableModel(
                        new User("Kermit"),
                        new User("Fozzie"),
                        new User("Animal"),
                        new User("Miss Piggy"),
                        new User("Gonzo"),
                        new User("Beaker"),
                        new User("Crazy Harry"),
                        new User("Floyd Pepper"),
                        new User("Sweetums"));

                final JTable table = new JTable(model);

                JButton delete = new JButton("Delete");
                delete.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        int[] selectedRows = table.getSelectedRows();
                        if (selectedRows.length > 0) {
                            List<User> users = new ArrayList<>(selectedRows.length);
                            for (int row : selectedRows) {
                                int modelRow = table.convertRowIndexToModel(row);
                                Boolean selected = (Boolean) model.getValueAt(modelRow, 1);
                                if (selected) {
                                    users.add(model.getUser(modelRow));
                                }
                            }
                            if (users.size() > 0) {
                                new DeleteUserWorker(users, model).execute();
                            }
                        }
                    }
                });

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(table));
                frame.add(delete, BorderLayout.SOUTH);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class DeleteUserWorker extends SwingWorker<List<User>, User> {

        private List<User> users;
        private UserTableModel model;

        public DeleteUserWorker(List<User> users, UserTableModel model) {
            this.users = users;
            this.model = model;
        }

        @Override
        protected void process(List<User> chunks) {
            for (User user : users) {
                model.remove(user);
            }
        }

        @Override
        protected List<User> doInBackground() throws Exception {
            for (User user : users) {
                // Simulated delay
                Thread.sleep(250);
                publish(user);
            }
            return users;
        }

    }

    public class UserTableModel extends AbstractTableModel {

        private List<User> users;
        private List<Boolean> selected;

        public UserTableModel(User... users) {
            this.users = new ArrayList<>(Arrays.asList(users));
            selected = new ArrayList<>(this.users.size());
            for (User user : this.users) {
                selected.add(new Boolean(false));
            }
        }

        public User getUser(int row) {
            return users.get(row);
        }

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

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

        @Override
        public String getColumnName(int column) {
            String name = "?";
            switch (column) {
                case 0:
                    name = "User";
                    break;
                case 1:
                    name = "";
                    break;
            }
            return name;
        }

        @Override
        public Class getColumnClass(int column) {
            Class type = String.class;
            switch (column) {
                case 0:
                    type = String.class;
                    break;
                case 1:
                    type = Boolean.class;
                    break;
            }
            return type;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            Object value = null;
            switch (columnIndex) {
                case 0:
                    value = users.get(rowIndex).getName();
                    break;
                case 1:
                    value = selected.get(rowIndex);
                    break;
            }
            return value;
        }

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

        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
            switch (columnIndex) {
                case 1:
                    if (aValue instanceof Boolean) {
                        selected.set(rowIndex, (Boolean) aValue);
                        fireTableCellUpdated(rowIndex, columnIndex);
                    }
                    break;
            }
        }

        public void remove(User user) {
            int index = users.indexOf(user);
            if (index >= 0) {
                selected.remove(index);
                users.remove(user);
                fireTableRowsDeleted(index, index);
            }
        }
    }

    public class User {

        private String name;

        public User(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }
}

Updated with additional example

The above example will only delete the rows that are marked AND selected. To delete all the marked rows, you will need something more like...

JButton delete = new JButton("Delete");
delete.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        List<User> users = new ArrayList<>(selectedRows.length);
        for (int row = 0; row < table.getRowCount(); row++) {
            int modelRow = table.convertRowIndexToModel(row);
            Boolean selected = (Boolean) model.getValueAt(modelRow, 1);
            if (selected) {
                users.add(model.getUser(modelRow));
            }
        }
        if (users.size() > 0) {
            new DeleteUserWorker(users, model).execute();
        }            
    }
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM