I have a custom AbstractTableModel
That model stores the data in a HashMap. So for my method for getValueAt(int rowIndex, int columnIndex)
I do
new ArrayList<Object>(data.values()).get(index);
However my data has over 2000 entries, so doing this every single time whenever I have to get the data for my table creates a huge performance hit.
So what solution can you recommend?
Should I try using List to store all my data in instead of HashMap?What is the accepted standard for storing data when using table models?
Thanks to anyone for their suggestion, and I aplogize for what might be a stupid question, but I am not too great when it comes to tables and how to store data in them.
A HashMap doesn't generally make a good fit for a table model because the table needs the ability to access data at an row/col location.
A ArrayList of ArrayLists is a reasonable way to store a table model. This still gives you fast access. Getting to a particular row is a constant time lookup, and then getting the column is also a constant time lookup.
If you don't want the overhead of the lists, you can always store the data in a 2D array.
Yes, the code you sight is going to suck in performance terms - for every cell you render, you're creating a new ArrayList based on the values in your Map (you can do the math).
At the very least, do the list creation once, probably in the constructor of your table model, like this (which assumes you've got some arbitary object, that you don't mention in your question, as the values of the map):
public class MyTableModel extends AbstractTableModel
{
private static final int COLUMN_0 = 0;
private static final int COLUMN_1 = 1;
private List<MyObject> data;
public MyTableModel(Map<?, MyObject> data)
{
this.data = new ArrayList<MyObject>(data.values());
}
public Object getValueAt(int rowIndex, int columnIndex)
{
switch (columnIndex)
{
case COLUMN_0: return this.data.get(rowIndex).getColumn0();
case COLUMN_1: return this.data.get(rowIndex).getColumn1();
...
case COLUMN_N: return this.data.get(rowIndex).getColumnN();
}
throw new IllegalStateException("Unhandled column index: " + columnIndex);
}
}
Related
JTable stores rows which can be added, deleted, shuffled, dynamically. In my implementation row represent a Download, whose progress can be dynamically updated, by passing value of one of the unique attribute called id. But how do I map id with actual row?
Iterating over the column is not efficient approach. Is there any way to dynamically synchronize Hashmap<ID,Object[]> with JTable, such that given a key I can update corresponding row, and vice versa?
private dftTasks=new DefaultTableModel();
public void addTask(String type, String name, int progress, int sessionID) {
Object[] rowData={type,name,new Integer(progress),new Integer(sessionID)};
dftTasks.addRow(rowData);
}
public void updateProgress(int sessionID, int progress) {
int i = dftTasks.getRow(sessionID); //<--alternative to this method
dftTasks.setValueAt(new Integer(progress), i, 2); //2nd column=Progress
}
Create a class to encapsulate the data (eg type, name, progress, and id).
Store instance of (1) in a List, and if necessary any other data structure for quick access (eg a Map keyed by id). The order of this List is the row order of the table.
Extend AbstractTableModel, and implement the necessary methods to return the values from the List from (1) and (2) based upon the row/column.
When values of the instances from (2) are changed (eg, the progress updated), call fireXXX from within the implementation in (3) (the source of DefaultTableModel is a good example of how this is done)
You just answered your own question - use HashMap:
private dftTasks=new DefaultTableModel();
Hashmap<Integer,Object[]> map = new Hashmap<Integer,Object[]>();
public void addTask(String type, String name, int progress, int sessionID) {
Object[] rowData={type,name,new Integer(progress),new Integer(sessionID)};
map.put(progress,rowData);
dftTasks.addRow(rowData);
}
public void updateProgress(int sessionID, int progress) {
//int i = dftTasks.getRow(sessionID); //<--alternative to this method
Object[] i = map.get(sessionID);
dftTasks.setValueAt(new Integer(progress), i, 2); //2nd column=Progress
}
To do vice versa and listen for changes use TableModelListener to listen for the changes,
at the moment I have an app that allows me to display data in a Jtable and then when I double click the Jtable this open a little window to edit only 3 fields, comments, expiration date and description. I update the this values (whit a preparedStatement) the thing is that everytime that I make an update to the database my table just refresh itself, changing the dateFormat with the new value that I've just inserted in my other window but with a different format!. How is this possible?
I' don't understand this because the only I only set a model to the table when I press my "search" button which contains the following code:
ArrayList<FiltrosResumen> filtrosResumenList = MainFrame.dataBase.searchFiltroResumen(query);
FiltrosResumenTableModel resumenModel = new FiltrosResumenTableModel(filtrosResumenList);
this.resumenTable.setModel(resumenModel);
hideColumns(1);
I'm using a custom table model containing all the table Fields, so first as you can see I colect all the rows from the database into a ArrayList from a custom object "FiltrosResumen", then I pass this to the constructor from my customTable model "FiltrosResumenTableModel" which extends AbstractTableMode I'm not using any special renders the most important methods are
public Object getValueAt(int rowIndex, int columnIndex) {
switch (columnIndex) {
case 0:
return this.filtrosResumen.get(rowIndex).getIdFiltro();
//....
//case 9:
default:
return null;
}
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
FiltrosResumen filtroResumen = new FiltrosResumen();
switch (columnIndex) {
case 0:
filtroResumen = this.filtrosResumen.get(rowIndex);
filtroResumen.setIdFiltro(Long.parseLong(aValue.toString()));
this.fireTableCellUpdated(rowIndex, columnIndex);
break;}
//....
//case 9:
}
And the constructor
public FiltrosResumenTableModel(List<FiltrosResumen> filtrosResumen) {
this.filtrosResumen = filtrosResumen;
}
And as I stated before, the database does not interact directly whit the table since storing the query result in a ArrayList, and then sending this to the constructor of my customTableModel.
EDIT: In order to change the value from one of the rows items I send a FiltrosResumen Object in this way:
FiltrosResumenTableModel modelo = (FiltrosResumenTableModel) this.resumenTable.getModel();
resumen = modelo.getResumen(row);
EditResumenIF editConexionesIF = new EditResumenIF(resumen);
EDIT: Passing a the resumen object to a InternalFrame Constructor (EditResumenIF).So in this new InternalFrame (EditResumenIF) I assign the values to a JCalendar and a JTextField to change the values and then save them. Afther the same object received by the constructor to a method that does the query and then return a string, ( if the string it's empty it' means that the query was successful without any mistakes)
String error = MainFrame.dataBase.updateResumen(resumen, resumen.getIdFiltro());
How comes that my Table knows that the value changed?
The default renderer for a cell of type Object.class is "a label that displays the object's string value." Unless your implementation of TableModel override's getColumnClass() to return some other value, your result is not unexpected. You might compare this example using DefaultTableModel to your implementation.
Addendum: How does my table know that the value changed?
JTable is a TableModelListener; any change to the model is (or should be) propagated to the table. Absent a complete example, I'm guessing that you are using a second table, table2, to edit a copy of certain data obtained from the original, table1.
Verify that you are copying the data in getResumen() and not just copying a reference to the table1 model.
In your implementation of setValueAt() in the TableModel of table2, update the model of table1. The exact mechanism depends on your TableModel; two approaches are contrasted here.
Addendum: I'm not using another tableā¦I'm passing a reference to my internal frame.
The same principles would apply. As an alternative to directly coupling the models, let the table's model be a PropertyChangeListener to the internal frame, as shown here.
I am designing a system which assembles disperate data in a standard row/column type output.
Each column can:
Exist in an independent system.
Can be paginated.
Can be sorted.
Each column can contain millions of rows.
And the system:
Needs to be extensible so different tables of different columns can be outputted.
The final domain object is known (the row).
The key is constant across all systems.
My current implementation plan is to design two classes per column (or one class column that implements two interfaces). The interfaces would:
Implement a pagination and sorting.
Implement "garnishing"
The idea is that the table constructor would receive information about the current sort column and page. Which would then return a list of appropriate keys for the table. This information would be used to create a list of the domain object rows which would then be passed in turn to each of the column "garnishing" implementations so that each columns information could be added in turn.
I guess my question is - what design patterns would be recommended - or alternative design decisions would people use for assembling disperate data with common keys and variable columns.
I'm not sure if I completely understood what you're trying to do, but from what I gather, you want to store rows of arbitrary data in a way that will allow you to make structured tables from it later on. What I would do in this case (assuming you're using Java) is make a very simple Column interface that would just have a "value" property:
public interface Column {
String value;
}
Then, you could make columns by implementing Column:
public class Key implements Column {
String value = new String();
public Key(String keyValue){
this.value = keyValue;
}
}
So then you can make a class called DataRow (or whatever you like) whose objects would contain the actual data. For example, you could have a method in that class that would allow you to add data:
public class DataRow {
List<Column> data = new ArrayList<Column>();
public DataRow(String key){
this.setColumn(new Key(key));
}
public void setColumn(Column columnData) {
this.data.add(columnData);
}
public Column getColumn(Class column){
for(Column c : this.data){
if(c.getClass().equals(column)){
return c;
}
}
return null;
}
}
As you can see, you can call the method setColumn() by giving it a new Column object. This will allow you to add any data you like of any type to the DataRow Object. Then, to make some tables, you could have a function that takes a List of DataRows, and a List of classes, that would then return only the objects which have data from the row specified:
public List<DataRow> createTable(List<DataRow> data, List<Class<? extends Column>> columns){
List<DataRow> table = new ArrayList<DataRow>();
for(DataRow row : data){
DataRow ret = new DataRow(row.getColumn(Key.class).value);
for(Class column : columns){
if(row.getColumn(column.getClass()) != null )ret.setColumn(row.getColumn(column.getClass()));
}
table.add(ret);
}
return table;
}
This will allow you to "create" tables using your data, and the columns you want to include in the table.
Note that I wrote this code to convey an idea, and that it's pretty messy at the moment. But I hope this will help you in some small way.
I have question that how can I delete all datas from my jTable in GUI when a user entered a key?
thanks
You can set a new empty data model:
TableModel newModel = new DefaultTableModel();
jtable.setModel(newModel);
You need to understand that a JTable is a view of the data, while the actual data resides in the TableModel. If you need to clear out the table, then you need to clear out the TableModel.
If your TableModel is an AbstractTableModel, you must provide implementations of 3 methods:
public int getRowCount();
public int getColumnCount();
public Object getValueAt(int row, int column);
Frequently the actual data objects are stored in an additional data structure (e.g. a list), and then the AbstractTableModel queries that list.
List<DomainObject> objects = new ArrayList<DomainObject>();
public int getRowCount() { return objects.size(); }
// How many columns you make depends on what features of the objects you're exposing.
public int getColumnCount() { return NUMBER_OF_COLUMNS; }
public Object getValueAt(int row, int column) {
DomainObject object = objects.get(row);
... // pull out the property based on the column they pass in
}
// By exposing this method, you can allow your Controller code to reach into this model
// and delete all the rows.
public void clear() {
objects.clear()
}
What HH is suggesting you do is change the model of your JTable to reference an empty model, which will in effect clear out the table. However, the columns etc. will not be persisted correctly (the new DefaultTableModel has no idea what those column names would be).
After you've researched how the view and model fit together more, take a look at GlazedLists. It allows a very powerful way to create TableModels which provide dynamic views of your data, e.g. by filtering out rows that do not match certain criteria.
To sum up - you're not going to find a method on the JTable to clear out its contents, because that's the job of the TableModel. You need some way of ensuring that the TableModel's backing data structures are cleared out.
If you are using the DefaultTableModel then you can just use:
model.setRowCount(0);
This is better than creating a new DefaultTableModel. Creating a new TableModel causes the TableColumnModel to be recreated, which means all the TableColumns will be resize to default values and recreated in the order in which the columns exist in the model. The user may have changed these properties and shouldn't be forced to do it again.
If you are just deleting certain rows that contain a particulsar value, then you can use the DefaultTableModel.removeRow(...) method. Make sure you start by deleting row from the end of the model and count down to 0.
call removeAll of j_table method at addActionListener
button1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
j_table.removeAll();
data_model_table.setRowCount(0);
}
});
There is a jbutton in my Jpanel. When I clicked it, it loads up my Jtable, sometimes a query return so many records (500 rows). So I want to restrict it to 5 records.
When query return I want to count it; if it's higher than 5 then Jtable shows up only first 5 record, when user click Forward button it will shows up next 5 record. When user click Back button it will show previous 5 record.
How can I do this? Is there any example for this with TableModel?
I suggest implementing a "Paged" TableModel which provides a window onto the entire dataset and methods for moving forwards and backwards throughout the data. This way you do not require two Lists to store the data but rather a single List holding all data along with a marker to your current position; e.g.
public class ImmutablePagedTableModel extends AbstractTableModel {
private final List<MyBusinessObject> allData;
private final int pageSize;
private int pos;
public ImmutablePagedTableModel(List<MyBusinessObject> allData) {
// Copy construct internal list. Use ArrayList for random access look-up efficiency.
this.allData = new ArrayList<MyBusinessObject>(allData);
}
/**
* Returns true if the model has another page of data or false otherwise.
*/
public boolean hasNextPage() {
return pos + pageSize < allData.size();
}
/**
* Flips to the next page of data available.
*/
public void nextPage() {
if (hasNextPage()) {
pos += pageSize;
// All data in the table has effectively "changed", so fire an event
// causing the JTable to repaint.
fireTableDataChanged();
} else {
throw new IndexOutOfBoundsException();
}
}
public int getRowcount() {
return Math.min(pageSize, allData.size() - pos);
}
// TODO: Implement hasPreviousPage(), previousPage();
}
As 00rush mentions a more ambitious approach would be to use a SwingWorker to stream in the data in the background. You could still use the paged TableModel approach for this; you'd just need to ensure that appropriate TableModelEvents are fired as you append to the end of the allData list.
If you wish to load a large table, you may want to use a SwingWorker (details here) thread to load the table in the background. Loading a table with 500 rows should not be a problem. You can then put the data into a suitable object format and pass it to your TableModel.
If you decide to use a List for example, in your table model you could have two lists:
List allData
List viewData
int startIndex
The viewData list is what is referenced by the getValueAt(..) method in your implementation of the TableModel interface. The viewData list is always a subset (bound by startIndex, of length 5) of allData. When the user clicks "Next", your action listener could call a method on the Table model that increments startIndex by 5 (or whatever). You then regenerate your viewData instance so that it is the appropriate 5 row subset of allData, and call fireTableChanged(). This will be easy if you have extended AbstractTableModel in the first place.
This should be pretty straightforward to implement. I think its better than making a database call every time you want to get the next set of data. IMHO, its better to take a little bit more time upfront to preload the data.