Hi,
I have created my TableModel and want to refresh JTable once I added a new row. What should be added to the listener to "refresh" 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) {
}
}
Here is my listener of my Add row Button:
#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!!!
}
I've tried to use method firetabledatachanged() of AbstractTableModel in my actionPerformed() but with unluck, it is appeared ClassCastException.
UPDATE 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 "";
}
}
You've done it the hard way.
First of all, you've implemented directly from TableModel and secondly you've failed to implement the listener requirements...
Instead, try extending from the AbstractTableModel instead, which already includes the implementations of the listener registration and notification.
You will need to provide a method that will allow you to add a row to the table model. In this method you need to use the fireTableRowsInserted method which will notify any tables using the model, that a new row has been added...
Update with example
This is VERY, VERY basic example. It's only intention is to demonstrate the use of fireTableRowsInserted. It uses a Swing Timer to add a new row every 125 milliseconds until you kill it ;)
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);
}
}
}
Updated with another example
Because your table model is backed by its own List, it has no connection to your factory. It doesn't know when you add or remove objects from it. This means you become responsible for updating the model:
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 "";
}
}
And your 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();
}
}
Your class MyTableModel implements TableModel, but it has no event handling mechanism to connect the model to the view. Instead extend AbstractTableModel, as shown here and here. AbstractTableModel provides the fireTable* methods needed for this.
public void setValueAt(Object value, int row, int col) {
data[row][col] = value;
fireTableCellUpdated(row, col);
}
Well, first of all, find more about Observer pattern (http://en.wikipedia.org/wiki/Observer_pattern).
I suggest that you create a ObservableModel class that will have a list of PropertyChangeListeners. Your StaffDAO should extend this ObservableModel. When the new staff is added (i.e. addStaff is called) you should call ObservableModel's firePorpertyChange or something like that. firePropertyChange notifyies all propertyChangeListeners. One of those listeners should be registered in your Table, and its propertyChanged method should be implemented with refreshing of the table (hd1) had a good answer.
myTableModel.fireTableDataChanged(); should be just enough to force a refresh of your table. As usual, if you have further problems, do feel free to leave a comment.
For a more general solution you can use the Row Table Model and just implement the getValueAt() and setValueAt() methods.
Here should be some code that will be firing my table after adding new row!
The model is responsible for invoking the proper fireXXX method.
Use this type of casting.
((AbstractTableModel)student.getModel()).fireTableCellUpdated();
Related
I am dynamically creating JTextField based on click event on '+' button. Below is screen shot.
The problem is when I click '+' button, fields created but not shown on JFrame. When I put the cursor on next row under 'item Name', text field becomes visible.
Where is the problem?
Below is my code.
CreateBill()
{
jf = new JFrame("Create Bill");
jf.getContentPane().setLayout(null);
jf.setExtendedState(JFrame.MAXIMIZED_BOTH);
jf.setBounds(0, 0, d1.width, d1.height);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
createRow(); // This will create first row by default.
jf.pack();
jf.setVisible(true);
}
private void createRow() {
textField = new JTextField();
textField.setToolTipText("item name");
textField.setBounds(143, 46+j, 288, 20);
textField.setColumns(10);
textField.getDocument().addDocumentListener(new DocumentListener()
{
#Override
public void insertUpdate(DocumentEvent e) {
updatePrice();
}
#Override
public void removeUpdate(DocumentEvent e) {
}
#Override
public void changedUpdate(DocumentEvent e) {
updatePrice();
}
});
AutoCompleteDecorator.decorate(textField, names, true);
jf.getContentPane().add(comboComplete);
jf.getContentPane().add(textField);
comboComplete.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
String ItemSel = textField.getText().trim();
for(Item s:items)
{
if(s.getItemName().equals(ItemSel))
{
textField_1.setText(String.valueOf(s.getUnitPrice()));
}
}
}
});
textFields.add(textField);
textField_1 = new JTextField();
textField_1.setEditable(false);
textField_1.setBounds(639, 46+j, 175, 20);
jf.getContentPane().add(textField_1);
textField_1.setColumns(10);
qty = new JTextField();
qty.setBounds(455, 46+j, 156, 20);
jf.getContentPane().add(qty);
qty.setColumns(10);
qty.getDocument().addDocumentListener(new DocumentListener()
{
#Override
public void insertUpdate(DocumentEvent e) {
getTotal();
}
#Override
public void removeUpdate(DocumentEvent e) {
}
#Override
public void changedUpdate(DocumentEvent e) {
getTotal();
}
});
textFields.add(qty);
textFields.add(textField_1);
textField_3 = new JTextField();
textField_3.setEditable(false);
textField_3.setBounds(1038, 46+j, 156, 20);
jf.getContentPane().add(textField_3);
textField_3.setColumns(10);
textFields.add(textField_3);
JButton button = new JButton("+");
button.setBounds(1235, 45+j, 89, 23);
jf.getContentPane().add(button);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("value of J"+j);
createRow(); // after pressing '+' button i am calling same method again. by changing value of j.
}
});
j=j+22;
jf.setVisible(true);
}
I want my all 4 text fields appear simultaneously.
You need to call repaint() on the container after adding a component to it. You also should call revalidate() too, before calling repaint, since this tells the layout managers to layout the new component, but you're using null layouts, something that you really want to avoid doing.
So my suggestion is to either 1) use nested JPanels with appropriate layout managers, and call revalidate and repaint on your containers after adding or removing components, or 2) yeah, use a Cardlayout to swap views as Andrew Thompson astutely recommends. You can have your second JPanel have a JTextField that uses the same Document as the previous JPanel, so it looks like both are using the same JTextField (as the top JTextField).
On looking further at your images, I have to wonder if a JTable might be an even better solution overall. And yeah, after you start using the layout managers, do also call pack() on your top level window after adding all components and before setting it visible.
For an example of a JTable implementation of this, something along the lines of...
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
#SuppressWarnings("serial")
public class CreateRowGui extends JPanel {
private static final Item[] ITEMS = {
new Item("Light Bulb", 2.00),
new Item("Toilet Paper", 3.00),
new Item("Toothpaste", 1.50),
new Item("Aspirin", 3.75) };
private ItemTableModel tableModel = new ItemTableModel();
private JTable table = new JTable(tableModel);
private AddRowAction addRowAction = new AddRowAction("Add Row", KeyEvent.VK_A);
public CreateRowGui() {
TableCellRenderer moneyRenderer = new DefaultTableCellRenderer() {
private NumberFormat currFormat = NumberFormat.getCurrencyInstance();
#Override
protected void setValue(Object value) {
if (value != null) {
value = currFormat.format(value);
}
super.setValue(value);
}
};
table.getColumnModel().getColumn(2).setCellRenderer(moneyRenderer);
table.getColumnModel().getColumn(3).setCellRenderer(moneyRenderer);
JPanel btnPanel = new JPanel();
btnPanel.add(new JButton(addRowAction));
btnPanel.add(new JButton("Remove Row")); // TODO: need Action for this
setLayout(new BorderLayout());
add(new JScrollPane(table));
add(btnPanel, BorderLayout.PAGE_END);
}
class AddRowAction extends AbstractAction {
private NewRowPanel newRowPanel = new NewRowPanel(ITEMS);
public AddRowAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
newRowPanel.reset();
int reply = JOptionPane.showConfirmDialog(table,
newRowPanel.getMainPanel(),
"Select Item and Quantity",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE);
if (reply == JOptionPane.OK_OPTION) {
Item item = newRowPanel.getSelectedItem();
int quantity = newRowPanel.getQuantity();
tableModel.addRow(item, quantity);
}
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("CreateRowGui");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new CreateRowGui());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class NewRowPanel {
private JPanel mainPanel = new JPanel();
private JComboBox<Item> itemsCombo;
private JSpinner quantitySpinner = new JSpinner(new SpinnerNumberModel(0, 0, 20, 1));
#SuppressWarnings("serial")
public NewRowPanel(Item[] items) {
itemsCombo = new JComboBox<>(items);
itemsCombo.setRenderer(new DefaultListCellRenderer(){
#Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected,
boolean cellHasFocus) {
if (value != null) {
value = ((Item) value).getName();
} else {
value = "";
}
return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
}
});
mainPanel.add(new JLabel("Item:"));
mainPanel.add(itemsCombo);
mainPanel.add(Box.createHorizontalStrut(15));
mainPanel.add(new JLabel("Quantity"));
mainPanel.add(quantitySpinner);
}
public void reset() {
itemsCombo.setSelectedIndex(-1);
quantitySpinner.setValue(0);
}
public JPanel getMainPanel() {
return mainPanel;
}
public Item getSelectedItem() {
return (Item) itemsCombo.getSelectedItem();
}
public int getQuantity() {
return (int) quantitySpinner.getValue();
}
}
class ItemTableModel extends AbstractTableModel {
private static final String[] COL_NAMES = { "Item Name", "Quantity", "Unit Price", "Total" };
private static final long serialVersionUID = 1L;
private List<ItemWithCount> itemsWithCount = new ArrayList<>();
#Override
public int getColumnCount() {
return 4;
}
#Override
public int getRowCount() {
return itemsWithCount.size();
}
#Override
public Class<?> getColumnClass(int columnIndex) {
switch (columnIndex) {
case 0:
return super.getColumnClass(columnIndex);
case 1:
return Integer.class;
case 2:
case 3:
return Double.class;
}
return super.getColumnClass(columnIndex);
}
#Override
public String getColumnName(int column) {
return COL_NAMES[column];
}
#Override
public Object getValueAt(int row, int column) {
ItemWithCount itemWithCount = itemsWithCount.get(row);
switch (column) {
case 0:
return itemWithCount.getItem().getName();
case 1:
return itemWithCount.getCount();
case 2:
return itemWithCount.getItem().getUnitPrice();
case 3:
return itemWithCount.getCount() * itemWithCount.getItem().getUnitPrice();
}
return null;
}
#Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
ItemWithCount itemWithCount = itemsWithCount.get(rowIndex);
switch (columnIndex) {
case 1:
itemWithCount.setCount((int) aValue);
fireTableRowsUpdated(rowIndex, rowIndex);
break;
default:
break;
}
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 0 || columnIndex == 1;
}
public void addRow(Item item, int quantity) {
ItemWithCount itemWithCount = new ItemWithCount(item, quantity);
itemsWithCount.add(itemWithCount);
int row = itemsWithCount.size() - 1;
fireTableRowsInserted(row, row);
}
private class ItemWithCount {
private Item item;
private int count;
public ItemWithCount(Item item, int count) {
this.item = item;
this.count = count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Item getItem() {
return item;
}
}
}
class Item {
private String name;
private double unitPrice;
public Item(String name, double unitPrice) {
this.name = name;
this.unitPrice = unitPrice;
}
public String getName() {
return name;
}
public double getUnitPrice() {
return unitPrice;
}
public void setUnitPrice(double unitPrice) {
this.unitPrice = unitPrice;
}
#Override
public String toString() {
return "Item [name=" + name + ", unitPrice=" + unitPrice + "]";
}
}
I would like to populate a JTable during runtime with many rows (lets say 10000). But all my attempts are very poor and inefficient.
Starting point is the addData method which gets a List of Objects representing a row. I tried to fill the table via a SwingWorker but this only works for small data for me.
Another attempt was setting the data directly without using any kind of thread, but this is also very slow, at least the UI isn't blocked like its the case with the SwingWorker.
So how do you do this is general? The table should be filled row by row or chunkwise but not all by one and the vertical scrollbar should be scrollable meanwhile.
My TableModel:
public class MyTableModel extends AbstractTableModel {
/**
*
*/
private static final long serialVersionUID = 1L;
String[] columnNames;
public Map<Long, ErrorMessage> data = new LinkedHashMap<Long, ErrorMessage>();
public MyTableModel(String[] header) {
columnNames = header;
}
public String getColumnName(int col) {
return columnNames[col].toString();
}
#Override
public int getColumnCount() {
return columnNames.length;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int col) {
.
.
return value;
}
public void addRow(long id, MyDataObject o) {
data.put(id, m);
fireTableRowsInserted(0,nqm_messages.size()-1);
}
}
SwingWorker implementation:
class TableSwingWorker extends SwingWorker<MyTableModel, MyDataObject> {
private final MyTableModel tableModel;
List<MyDataObject> messages;
public TableSwingWorker(MyTableModel tableModel, List<MyDataObject> dataList) {
this.tableModel = tableModel;
this.messages = new LinkedList<MyDataObject>(mm);
}
#Override
protected MyTableModel doInBackground() throws Exception {
for(MyDataObject s : messages) {
publish(s);
}
return tableModel;
}
#Override
protected void process(List<MyDataObject> chunks) {
for(MyDataObject row : chunks){
Long l = Long.parseLong(row.getId());
tableModel.addRow(l, row);
}
}
}
Add Objects to JTable:
public void addData(List<MyDataObject> o) {
MyTableModel m = (MyTableModel)table.getModel();
(new TableSwingWorker(m,o)).execute();
//for(int i=0; i < mm.size();i++) {
// long l = Long.parseLong(mm.get(i).getId());
// m.addRow(l, mm.get(i));
//}
}
So, a number of things have being identified from the comments...
You need to correctly fire the row inserted method, indicating only those rows that have being added and where they have being updated. This very important, as the the table has being optimised for speed
You should provide batch add method for your table model, allowing you to more easily add multiple rows in a single or as few steps as possible
You should have the SwingWorker periodically sleep or yield, to allow it time to publish the results.
So, in this example, I'm adding 1, 000, 000 rows. In my test it took slightly under 1 second...
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
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;
import javax.swing.table.DefaultTableCellRenderer;
public class TestTableLoad01 {
public static void main(String[] args) {
new TestTableLoad01();
}
public TestTableLoad01() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
MyTableModel model = new MyTableModel();
JTable table = new JTable(model);
table.setDefaultRenderer(Date.class, new TimeCellRenderer());
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);
TableSwingWorker worker = new TableSwingWorker(model);
worker.execute();
}
});
}
public class TimeCellRenderer extends DefaultTableCellRenderer {
private DateFormat df;
public TimeCellRenderer() {
df = new SimpleDateFormat("HH:mm:ss");
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Date) {
value = df.format(value);
}
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
return this;
}
}
public class MyTableModel extends AbstractTableModel {
private String[] columnNames = new String[]{"Date", "Row"};
private List<RowData> data;
public MyTableModel() {
data = new ArrayList<>(25);
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex == 0 ? Date.class : Integer.class;
}
#Override
public String getColumnName(int col) {
return columnNames[col];
}
#Override
public int getColumnCount() {
return columnNames.length;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int col) {
RowData value = data.get(row);
return col == 0 ? value.getDate() : value.getRow();
}
public void addRow(RowData value) {
int rowCount = getRowCount();
data.add(value);
fireTableRowsInserted(rowCount, rowCount);
}
public void addRows(RowData... value) {
addRows(Arrays.asList(value));
}
private void addRows(List<RowData> rows) {
int rowCount = getRowCount();
data.addAll(rows);
fireTableRowsInserted(rowCount, getRowCount() - 1);
}
}
public class RowData {
private Date date;
private int row;
public RowData(int row) {
this.date = new Date();
this.row = row;
}
public Date getDate() {
return date;
}
public int getRow() {
return row;
}
}
public class TableSwingWorker extends SwingWorker<MyTableModel, RowData> {
private final MyTableModel tableModel;
public TableSwingWorker(MyTableModel tableModel) {
this.tableModel = tableModel;
}
#Override
protected MyTableModel doInBackground() throws Exception {
// This is a deliberate pause to allow the UI time to render
Thread.sleep(2000);
System.out.println("Start polulating");
for (int index = 0; index < 1000000; index++) {
RowData data = new RowData(index);
publish(data);
Thread.yield();
}
return tableModel;
}
#Override
protected void process(List<RowData> chunks) {
System.out.println("Adding " + chunks.size() + " rows");
tableModel.addRows(chunks);
}
}
}
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.
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...
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();
}
}
});
I would like to populate a JTable during runtime with many rows (lets say 10000). But all my attempts are very poor and inefficient.
Starting point is the addData method which gets a List of Objects representing a row. I tried to fill the table via a SwingWorker but this only works for small data for me.
Another attempt was setting the data directly without using any kind of thread, but this is also very slow, at least the UI isn't blocked like its the case with the SwingWorker.
So how do you do this is general? The table should be filled row by row or chunkwise but not all by one and the vertical scrollbar should be scrollable meanwhile.
My TableModel:
public class MyTableModel extends AbstractTableModel {
/**
*
*/
private static final long serialVersionUID = 1L;
String[] columnNames;
public Map<Long, ErrorMessage> data = new LinkedHashMap<Long, ErrorMessage>();
public MyTableModel(String[] header) {
columnNames = header;
}
public String getColumnName(int col) {
return columnNames[col].toString();
}
#Override
public int getColumnCount() {
return columnNames.length;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int col) {
.
.
return value;
}
public void addRow(long id, MyDataObject o) {
data.put(id, m);
fireTableRowsInserted(0,nqm_messages.size()-1);
}
}
SwingWorker implementation:
class TableSwingWorker extends SwingWorker<MyTableModel, MyDataObject> {
private final MyTableModel tableModel;
List<MyDataObject> messages;
public TableSwingWorker(MyTableModel tableModel, List<MyDataObject> dataList) {
this.tableModel = tableModel;
this.messages = new LinkedList<MyDataObject>(mm);
}
#Override
protected MyTableModel doInBackground() throws Exception {
for(MyDataObject s : messages) {
publish(s);
}
return tableModel;
}
#Override
protected void process(List<MyDataObject> chunks) {
for(MyDataObject row : chunks){
Long l = Long.parseLong(row.getId());
tableModel.addRow(l, row);
}
}
}
Add Objects to JTable:
public void addData(List<MyDataObject> o) {
MyTableModel m = (MyTableModel)table.getModel();
(new TableSwingWorker(m,o)).execute();
//for(int i=0; i < mm.size();i++) {
// long l = Long.parseLong(mm.get(i).getId());
// m.addRow(l, mm.get(i));
//}
}
So, a number of things have being identified from the comments...
You need to correctly fire the row inserted method, indicating only those rows that have being added and where they have being updated. This very important, as the the table has being optimised for speed
You should provide batch add method for your table model, allowing you to more easily add multiple rows in a single or as few steps as possible
You should have the SwingWorker periodically sleep or yield, to allow it time to publish the results.
So, in this example, I'm adding 1, 000, 000 rows. In my test it took slightly under 1 second...
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
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;
import javax.swing.table.DefaultTableCellRenderer;
public class TestTableLoad01 {
public static void main(String[] args) {
new TestTableLoad01();
}
public TestTableLoad01() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
MyTableModel model = new MyTableModel();
JTable table = new JTable(model);
table.setDefaultRenderer(Date.class, new TimeCellRenderer());
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);
TableSwingWorker worker = new TableSwingWorker(model);
worker.execute();
}
});
}
public class TimeCellRenderer extends DefaultTableCellRenderer {
private DateFormat df;
public TimeCellRenderer() {
df = new SimpleDateFormat("HH:mm:ss");
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Date) {
value = df.format(value);
}
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
return this;
}
}
public class MyTableModel extends AbstractTableModel {
private String[] columnNames = new String[]{"Date", "Row"};
private List<RowData> data;
public MyTableModel() {
data = new ArrayList<>(25);
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex == 0 ? Date.class : Integer.class;
}
#Override
public String getColumnName(int col) {
return columnNames[col];
}
#Override
public int getColumnCount() {
return columnNames.length;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int col) {
RowData value = data.get(row);
return col == 0 ? value.getDate() : value.getRow();
}
public void addRow(RowData value) {
int rowCount = getRowCount();
data.add(value);
fireTableRowsInserted(rowCount, rowCount);
}
public void addRows(RowData... value) {
addRows(Arrays.asList(value));
}
private void addRows(List<RowData> rows) {
int rowCount = getRowCount();
data.addAll(rows);
fireTableRowsInserted(rowCount, getRowCount() - 1);
}
}
public class RowData {
private Date date;
private int row;
public RowData(int row) {
this.date = new Date();
this.row = row;
}
public Date getDate() {
return date;
}
public int getRow() {
return row;
}
}
public class TableSwingWorker extends SwingWorker<MyTableModel, RowData> {
private final MyTableModel tableModel;
public TableSwingWorker(MyTableModel tableModel) {
this.tableModel = tableModel;
}
#Override
protected MyTableModel doInBackground() throws Exception {
// This is a deliberate pause to allow the UI time to render
Thread.sleep(2000);
System.out.println("Start polulating");
for (int index = 0; index < 1000000; index++) {
RowData data = new RowData(index);
publish(data);
Thread.yield();
}
return tableModel;
}
#Override
protected void process(List<RowData> chunks) {
System.out.println("Adding " + chunks.size() + " rows");
tableModel.addRows(chunks);
}
}
}
My problem is:
I have a button that will add components to a JList when it is clicked. Each row of the list is composed by two jtextFields. The first is the ID of the row. Every time I press the "Add button", a new row appears, the ID is incremented, and the person who is using the application will write whatever he want on the second jTextField (forward the ID field).
Then it will have a scroll pane for when there are more than 4 rows.
And I want a Remove Button too. To be possible to remove some rows.
Can you help me with this? I don't know how to create a list like this...
Thanks!
Simply use JTable with two columns. This will allow you to establish two columns, one for the ID and one for the value. This will allow you make the second column editable.
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.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
public class TestTable04 {
public static void main(String[] args) {
new TestTable04();
}
private int id = 0;
public TestTable04() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
final RecordTableModel model = new RecordTableModel();
JTable table = new JTable(model);
JButton add = new JButton("Add");
add.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
model.add(new Record(++id));
}
});
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(add, BorderLayout.SOUTH);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Record {
private int id;
private String value;
public Record(int id) {
this.id = id;
}
public int getID() {
return id;
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public class RecordTableModel extends AbstractTableModel {
private List<Record> lstRecords;
public RecordTableModel() {
lstRecords = new ArrayList<>(24);
}
public void add(Record record) {
lstRecords.add(record);
fireTableRowsInserted(lstRecords.size() - 1, lstRecords.size() - 1);
}
public void remove(Record record) {
if (lstRecords.contains(record)) {
int index = lstRecords.indexOf(record);
remove(index);
}
}
public void remove(int index) {
lstRecords.remove(index);
fireTableRowsDeleted(index, index);
}
#Override
public int getRowCount() {
return lstRecords.size();
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public Class<?> getColumnClass(int columnIndex) {
Class clazz = String.class;
switch (columnIndex) {
case 0:
clazz = Integer.class;
break;
}
return clazz;
}
#Override
public String getColumnName(int column) {
String name = null;
switch (column) {
case 0:
name = "ID";
break;
case 1:
name = "Value";
break;
}
return name;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
Record record = lstRecords.get(rowIndex);
Object value = null;
switch (columnIndex) {
case 0:
value = record.getID();
break;
case 1:
value = record.getValue();
break;
}
return value;
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 1;
}
#Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
Record record = lstRecords.get(rowIndex);
switch (columnIndex) {
case 1:
record.setValue(aValue == null ? null : aValue.toString());
fireTableCellUpdated(rowIndex, columnIndex);
break;
}
}
}
}
Check out How to use tables for more details
You can try creating a custom ListCellRenderer and use JList's setRenderer method to assign it. Override the getListCellRenderer() method of ListCellRenderer to return a JPanel containing two components, a JLabel for the id, and a JTextfield for the user input. You may need to keep a reference to the JTextField elsewhere so that you can retrieve the user input.
To implement the add and remove methods, I would recommend backing the JList with a ListModel. Add and remove to the ListModel will also update the JList UI.