Reputation: 17
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?
Upvotes: 0
Views: 2146
Reputation: 347314
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.
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();
}
}
});
Upvotes: 2