Paginating data in JTable - java

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.

Related

JavaFX bad design: Row identity in observable lists behind the TableView?

Suppose I am displaying very long table with TableView. Manual says, TableView
is designed to visualize an unlimited number of rows of data
So, since million of rows won't fit the RAM, I would introduce some caching. This means, that ObservableList#get() is allowed to return different instances for the same row index.
Is this true?
What about opposite? May I return the same instance for all row indices filled with different data?
I noticed, that this implies some problem with row editing. At which moment should I pass data to the store? Looks like TableView never calls ObservableList#set() but just mutates obtained instance.
Where to intercept?
UPDATE
Also imagine this very big table was updated at server side. Suppose, one million of records were added.
The only way to report about it -- is by firing observable list addition event, while an addition event also contains reference to all added rows. Which is nonsense -- why send data, which is not event displayed?
I think the intention of the statement in the Javadocs that you quote
is designed to visualize an unlimited number of rows of data
is meant to imply that the TableView imposes no (additional) constraints on the size of the table data: in other words that the view is essentially scalable at a constant memory consumption. This is achieved by the "virtualization": the table view creates cells only for the visible data, and reuses them for different items in the backing list as, e.g., the user scrolls. Since in a typical application the cells (which are graphical) consume far more memory than the data, this represents a big performance saving and allows for as many rows in the table as could feasibly be handled by the user.
There are still, of course, other constraints on the table data size that are not imposed by the table view. The model (i.e. observable list) needs to store the data and consequently memory constraints will (in the default implementation) impose a constraint on the number of rows in the table. You could implement a caching list (see below) to reduce the memory footprint, if needed. And as #fabian points out in the comments below the question, user experience is likely to impose constraints long before you reach that point (I'd recommend using pagination or some kind of filtering).
Your question about identity of elements retrieved from the list is pertinent in a caching implementation: it basically boils down to whether a list implementation is obliged to guarantee list.get(i) == list.get(i), or whether it is enough merely to guarantee list.get(i).equals(list.get(i)). To the best of my knowledge, TableView only expects the latter, so an implementation of ObservableList that caches a relatively small number of elements and recreates them as needed should work.
For proof of concept, here is an implementation of an unmodifiable caching observable list:
import java.util.LinkedList;
import java.util.function.IntFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.collections.ObservableListBase;
public class CachedObservableList<T> extends ObservableListBase<T> {
private final int maxCacheSize ;
private int cacheStartIndex ;
private int actualSize ;
private final IntFunction<T> generator ;
private final LinkedList<T> cache ;
public CachedObservableList(int maxCacheSize, int size, IntFunction<T> generator) {
this.maxCacheSize = maxCacheSize ;
this.generator = generator ;
this.cache = new LinkedList<T>();
this.actualSize = size ;
}
#Override
public T get(int index) {
int debugCacheStart = cacheStartIndex ;
int debugCacheSize = cache.size();
if (index < cacheStartIndex) {
// evict from end of cache:
int numToEvict = cacheStartIndex + cache.size() - (index + maxCacheSize);
if (numToEvict < 0) {
numToEvict = 0 ;
}
if (numToEvict > cache.size()) {
numToEvict = cache.size();
}
cache.subList(cache.size() - numToEvict, cache.size()).clear();
// create new elements:
int numElementsToCreate = cacheStartIndex - index ;
if (numElementsToCreate > maxCacheSize) {
numElementsToCreate = maxCacheSize ;
}
cache.addAll(0,
IntStream.range(index, index + numElementsToCreate)
.mapToObj(generator)
.collect(Collectors.toList()));
cacheStartIndex = index ;
} else if (index >= cacheStartIndex + cache.size()) {
// evict from beginning of cache:
int numToEvict = index - cacheStartIndex - maxCacheSize + 1 ;
if (numToEvict < 0) {
numToEvict = 0 ;
}
if (numToEvict >= cache.size()) {
numToEvict = cache.size();
}
cache.subList(0, numToEvict).clear();
// create new elements:
int numElementsToCreate = index - cacheStartIndex - numToEvict - cache.size() + 1;
if (numElementsToCreate > maxCacheSize) {
numElementsToCreate = maxCacheSize ;
}
cache.addAll(
IntStream.range(index - numElementsToCreate + 1, index + 1)
.mapToObj(generator)
.collect(Collectors.toList()));
cacheStartIndex = index - cache.size() + 1 ;
}
try {
T t = cache.get(index - cacheStartIndex);
assert(generator.apply(index).equals(t));
return t ;
} catch (Throwable exc) {
System.err.println("Exception retrieving index "+index+": cache start was "+debugCacheStart+", cache size was "+debugCacheSize);
throw exc ;
}
}
#Override
public int size() {
return actualSize ;
}
}
And here's a quick example using it, that has 100,000,000 rows in the table. Obviously this is unusable from a user experience perspective, but it seems to work perfectly well (even if you change the cache size to be smaller than the number of displayed cells).
import java.util.Objects;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class CachedTableView extends Application {
#Override
public void start(Stage primaryStage) {
CachedObservableList<Item> data = new CachedObservableList<>(100, 100_000_000, i -> new Item(String.format("Item %,d",i)));
TableView<Item> table = new TableView<>();
table.setItems(data);
TableColumn<Item, String> itemCol = new TableColumn<>("Item");
itemCol.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
itemCol.setMinWidth(300);
table.getColumns().add(itemCol);
Scene scene = new Scene(table, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
public Item(String name) {
setName(name) ;
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
#Override
public boolean equals(Object o) {
if (o.getClass() != Item.class) {
return false ;
}
return Objects.equals(getName(), ((Item)o).getName());
}
}
public static void main(String[] args) {
launch(args);
}
}
There's obviously quite a lot more to do if you want to implement the list so that it is modifiable; start by thinking about exactly what behavior you would need for set(index, element) if index is not in the cache... and then subclass ModifiableObservableListBase.
For editing:
I noticed, that this implies some problem with row editing. At which moment should I pass data to the store? Looks like TableView never calls ObservableList#set() but just mutates obtained instance.
You have three options that I can see:
If your domain objects use JavaFX properties, then the default behavior is to update the property when editing is committed. You can register listeners with the properties and update the backing store if they change.
Alternatively, you can register an onEditCommit handler with the TableColumn; this will get notified when an edit is committed in the table, and so you could update the store from this. Note that this will replace the default edit commit behavior, so you will also need to update the property. This gives you the opportunity to veto the update to the cached property if the update to the store fails for some reason, and is probably the option you want.
Thirdly, if you implement the editing cells yourself, instead of using default implementations such as TextFieldTableCell, you could invoke methods on the model directly from the controls in the cell. This is probably not desirable, as it violates the standard design patterns and avoids the usual editing notifications built into the table view, but it may be a useful option in some cases.
Also imagine this very big table was updated at server side. Suppose, one million of records were added.
The only way to report about it -- is by firing observable list addition event, while an addition event also contains reference to all added rows.
That's not true, as far as I can tell. ListChangeListener.Change has a getAddedSublist() method, but the API docs for this state it returns
a subList view of the list that contains only the elements added
so it should simply return getItems().sublist(change.getFrom(), change.getTo()). Of course, this simply returns a sublist view of the cached list implementation, so doesn't create the objects unless you explicitly request them. (Note that getRemoved() might potentially cause more problems, but there should be some way to work around that too.)
Finally, to bring this full circle, while the observable list implementation is doing work here of caching the elements and making the model "unlimited" in the number of rows it can support (up to Integer.MAX_VALUE), it wouldn't be possible to use this in the table view if the table view didn't implement "virtualization". A non-virtualized implementation of table view would create cells for each item in the list (i.e. it would call get(i) for 0 <= i < items.size(), creating a cell for each), place the cells in a scroll pane implementation, and the memory consumption would blow up even with the caching in the list. So "unlimited" in the Javadocs really does mean that any limit is deferred to implementation of the model.

Need to make a JTable with 1000 rows

I am working on a GUI application in Java for a client. One of the parts of that GUI needs to upload about a 1000 records each having 17 attributes simultaneously(i.e. it needs a 1000 X 17 table). Now Netbeans IDE 7.2.1 allows at most 100 rows at a time in a Jtable. Any suggestions how can i make one for displaying 1000 entries at a time. I considered having 10 tables one after other but that will leave a very messy coding to be done later at the back end!
Don't use an IDE to create your GUI. The IDE generates terrible code that creates a table with the null values for the number of rows that you want to create.
There is no restriction on the number of rows a table can hold. If you create the code manually you can do something simple like:
DefaultTableModel model = new DefaultTableModel(columnNames, 0);
JTable table = new JTable(model);
which will create an empty table with the column names that you specify
Then you can add rows individually using the DefaultTableModel.addRow(...) method.
Or you can add all the rows at one time by using the DefaultTableModel.setDataVector(...) method.
You can create your GUI with any IDE you like. There is no problem with Netbeans in this area. Netbeans, like any other editing tool, allows you perfectly well to create some class like i.e. MyModel that extends an AbstractTableModel, which has no GUI and you should use in order to separate your Model from the View and Controller that have a GUI.
Your JTable will then automatically call getValueAt(row, col) and getRowCount() in order to show the tiny subset of your 1.000 or maybe 1.000.000 lines that need to be displayed.
You must not necessarily load all 1000 records to any Vector or ArrayList. Just make getValueAt(row, col) read each row and return every column.
There is a high probability that your user will not scroll down every time and will not ask the TableModel to provide anything more than the 40-60 lines that should be visible after the first rendering.
This example shows the getValueAt, used on a scrollable ResultSet sr from a database:
#Override
public Object getValueAt(int row, int column) {
try {
sr.absolute(row + 1); // position your ResultSet.
switch (column) {
case 0:
return sr.getInt("...");
case 1:
return sr.getString("...");
case 2:
return sr.getString("...");
case 3:
return ......
default:
return ("-");
}
} catch (SQLException sex) {
........
}
}
This is what happens at the GUI side:
Is the JTable. Since you use a TableModel it will show you the JTable only at runtime.
Here is the place that allows you to bind your model to the auto-generated code of NetBeans.
This is the instance of your model. You could also write something like "new MyModel()" here.
The generated code is no monster either:
...
jTable1.setModel(etb);
jTable1.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
jTable1.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
myMouseClicked(evt);
}
});
jScrollPane1.setViewportView(jTable1);
...

Java tablemodel hashmap vs list

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);
}
}

Issue with default Sorting on Virtual Table & ViewerComparator

We have a Virtual Table in my Eclipse RCP application. We make a call to the backend to retrieve the data to be populated in the virtual table.
We want default sorting on the table on a single column. We use ViewerComparator to achieve sorting functionality. My problem is, I am not able to get this sorting working when the table loads with the data for the 1st time. But when I click on the column, everything works fine as expected.
This is how, I set the Comparator to the column
TableViewerColumn tvc = viewer.addColumn(100, SWT.LEFT, "Name");
viewer.setColumnComparator(tvc,
new Comparator<Person>() {
#Override
public int compare(Person o1,Person o2) {
double firstValue = Double.parseDouble(o1
.getAge());
double secondValue = Double.parseDouble(o2
.getAge());
return firstValue > secondValue ? 1 : -1;
}
});
setColumnComparator method in custom viewer
public void setColumnComparator(TableViewerColumn tvc, Comparator<T> cmp){
final MyViewerComparator c = new MyViewerComparator(cmp);
final TableColumn tc = tvc.getColumn();
setComparator(c);
getTable().setSortDirection(c.getDirection());
getTable().setSortColumn(tc);
refresh();
tc.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
<same code as above>
}
});
MyViewerComparator
class MyViewerComparator extends ViewerComparator{
Comparator<T> cmp;
boolean desc = true;
MyViewerComparator(Comparator<T> cmp){
this.cmp = cmp;
}
int getDirection(){
return desc?SWT.UP:SWT.DOWN;
}
void flipDirection(){
desc = !desc;
}
#Override
public int compare(Viewer viewer, Object e1, Object e2) {
if(e1 == null || e2==null){
return 0;
}
int rc = cmp.compare((T)e1, (T)e2);
if(desc)
return -rc;
return rc;
}
}
When the table loads the data for the 1st time, it goes inside the Bolded condition in the above code as one of the object is ALWAYS NULL
Note: This functionality works totally fine if I use a Standard table rather than VIRTUAL TABLE. I am not sure whether I can change it to use Standard table as we want the lazy load functionality as well..
ContentProvider used is: ObservableListContentProvider
Please advise..
A late answer that hopefully still helps others. I encountered exactly the same problem when using SWT.VIRTUAL with an ObservableListContentProvider in combination with sorting.
The original intent of SWT.VIRTUAL is that not all elements in the contents need to be fetched to show only part of the contents. A custom content provider needs to be implemented which only has to return the elements that need to be currently shown on the screen. You also have to tell the table the total number of elements in existence. In such a use case, a table cannot be sorted in the normal way with a ViewerComparator because not all elements are known. However SWT.VIRTUAL can also be used as a performance optimization for rendering a table with many elements. This seems to work fine with the non-observable ArrayContentProvider.
But when using ObservableListContentProvider I am seeing exactly the same issue as you have. Somehow it tries to be smart and update only the elements that have actually changed. Somewhere in the depths of it's implementation something goes wrong for virtual tables, I have no clue exactly what. But I do have a solution: don't use ObservableListContentProvider at all and simply refresh the table viewer. You can e.g. use a plain ArrayContentProvider and add the following listener to the IObservableList contents of the viewer:
new IListChangeListener() {
#Override
public void handleListChange(ListChangeEvent event) {
viewer.refresh();
}
};
I actually implemented my own "SimpleObservableListContentProvider" that does exactly this, but also takes care of switching table input by implementing the inputChanged method to remove this listener from the old input list and add it to the new one.

deleting datas in the table in GUI

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);
}
});

Categories