How to Retrieve JTable Data as an Array - java

I've populated a JTable through a DefaultTableModel with the (Object[][] data, String[] headers) constructor. Users can edit the table, and I want to be able to load the new data back into an array (Object[][]). Note that I'd rather not just update the array bit by bit, but be able to just completely load a new array from the table. How can this be done?

I take that back, on 2nd thoughts, you dont need any typecasting - TableModel is an interface that has all the 3 method calls you need. :)
Summary: Get the model for the table, check its class and typecast it to appropriate class (Abstract or Default TableModel), and use its methods to load a newly created array. Some psuedoCode:
public Object[][] getTableData (JTable table) {
DefaultTableModel dtm = (DefaultTableModel) table.getModel();
int nRow = dtm.getRowCount(), nCol = dtm.getColumnCount();
Object[][] tableData = new Object[nRow][nCol];
for (int i = 0 ; i < nRow ; i++)
for (int j = 0 ; j < nCol ; j++)
tableData[i][j] = dtm.getValueAt(i,j);
return tableData;
}
Your headers should not have changed by user-edits. Hope that helps. Regards, - M.S.

I'd like to suggest a small improvement on Manidip Sengupta's answer. Rather than casting table.getModel() to the appropriate class, it is better to simply work with TableModel. This makes the code more re-usable too (it doesn't matter which implementation of TableModel is actually being worked with).
public Object[][] getTableData (JTable table) {
TableModel dtm = table.getModel();
int nRow = dtm.getRowCount(), nCol = dtm.getColumnCount();
Object[][] tableData = new Object[nRow][nCol];
for (int i = 0 ; i < nRow ; i++)
for (int j = 0 ; j < nCol ; j++)
tableData[i][j] = dtm.getValueAt(i,j);
return tableData;
}

I've populated a JTable through a DefaultTableModel with the (Object[][] data, String[] headers)
The DefaultTableModel is a dynamic model, which means rows and columns can be added dynamically. Arrays are not dynamic so when you create a DefaultTableModel using arrays, the data from the arrays is copied to a Vector of Vectors.
I want to be able to load the new data back into an array (Object[][]).
I'd rather not just update the array bit by bit
Unfortunately you will have to update the array cell by cell since the data is not stored in a 2D array.
Or, since the DefaultTableModel does use a Vector of Vectors to store the data you can use the getDataVector() method to access the data. Then you get each row from the Vector and invoke the List.toArray() method on the row Vector before adding it to your array.
Either way you will need to loop through the Vectors in the model.
If you want to use the 2D array as storage for the TableModel, then you will need to create a custom TableModel that uses the supplied array for the data storage. After implemting all the required methods of the TableModel interface you will need to provide a getTableDataArray() method to return the reference to the array.

Not with the existing default table model. As a rule of thumb, you should ever use the default table model. You should implement your own table model (MyTableModel). You can either implement TableModel or extend AbstractTableModel. I suggest the latter as it provides some nice utility methods. There are 2 ways to achieve what you are doing
populate your new data into a new instance of MyTableModel and call JTable.setModel().
A second way is to create a method in MyTableModel call eg, replaceData(T[][] data). Hold a reference to the model that the table is current displaying. Whenever you want to replace the data, call replaceData(). Assuming you are extending AbstractTableModel, then call fireTableChanged() to notify the table.

With the out-of-the-box JTable implementation, you can't. When you initialize a JTable with Object[][] rowData that rowData is used by an anonymous AbstractTableModel instance but it's not accesible as such from outside.
public JTable(final Object[][] rowData, final Object[] columnNames) {
this(new AbstractTableModel() {
public String getColumnName(int column) { return columnNames[column].toString(); }
public int getRowCount() { return rowData.length; }
public int getColumnCount() { return columnNames.length; }
public Object getValueAt(int row, int col) { return rowData[row][col]; }
public boolean isCellEditable(int row, int column) { return true; }
public void setValueAt(Object value, int row, int col) {
rowData[row][col] = value;
fireTableCellUpdated(row, col);
}
});
}
You could consider to subclass both JTable and AbstractTableModel and "overwrite" this constructor in JTable to create your own TableModel implementation that will hold that Object[][] reference and return it with Object[][] getRowData(). Or just keep the rowData as a field in the JTable subclass itself after calling super(...) in that constructor -- if you don't really care about MVC.
But you need to make sure that after the table is edited, the initial model is preserved and not replaced by a new model object (of course, of a different type) with setModel. If this is the case, what you want is impossible to achieve -- you will need to iterate through all the cells.

Related

How to Insert row data from database into specific columns of a JTable?

I have written a GUI Java program that manages a MySQL database. The user selects which columns (which tables and columns will be selected from the database) he/she wants to populate the JTable with.
I hard-coded the column names for the JTable so even if the user chooses to only display the data from a subset of columns, all of the column-names will be visible.
The problem is that when the user chooses columns in a different order than my JTable is anticipating, the data gets displayed in the wrong column.. It's a bit hard to explain so here's a screenshot of the genre data being loaded into the length column:
tableGenerator class:
package gui;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Vector;
public class TableGenerator
{
private ArrayList columnNames = new ArrayList();
private ArrayList data = new ArrayList();
private Vector columnNamesVector = new Vector();
private Vector dataVector = new Vector();
private int columns = 0;
private int rows = 0;
#SuppressWarnings("unchecked")
public TableGenerator(ResultSet rs)
{
try{
ResultSetMetaData md = rs.getMetaData();
columns = md.getColumnCount();
// Get column names
columnNames.add("Title");
columnNames.add("Year");
columnNames.add("Length");
columnNames.add("Genre");
columnNames.add("Actor");
columnNames.add("Producer");
columnNames.add("Director");
columnNames.add("Writer");
// Get row data
while (rs.next())
{
ArrayList row = new ArrayList(columnNames.size());
for (int i = 1; i <= columns; i++)
{
row.add(rs.getObject(i));
}
data.add( row );
rows++;
}
}
catch (SQLException e)
{
System.out.println( e.getMessage() );
}
// Create Vectors and copy over elements from ArrayLists to them
// Vector is deprecated but I am using them in this example to keep
// things simple - the best practice would be to create a custom defined
// class which inherits from the AbstractTableModel class
for (int i = 0; i < data.size(); i++)
{
ArrayList subArray = (ArrayList)data.get(i);
Vector subVector = new Vector();
for (int j = 0; j < subArray.size(); j++)
{
subVector.add(subArray.get(j));
}
dataVector.add(subVector);
}
for (int i = 0; i < columnNames.size(); i++ )
columnNamesVector.add(columnNames.get(i));
}
public Vector getColumns(){
return columnNamesVector;
}
public Vector getData(){
return dataVector;
}
public ArrayList getColumnNames(){
return columnNames;
}
public int getNumberOfRows(){
return rows;
}
}
I'm using the DefaultTableModel with some modifications.. :
model = new DefaultTableModel(rows, columns){
private static final long serialVersionUID = 1L;
#Override
public boolean isCellEditable(int row, int column) {
return false;
}
#Override
public Class<?> getColumnClass(int column) {
if (column < classes.length)
return classes[column];
return super.getColumnClass(column);
};};
Your query should always return the data for all columns. This means the data will be stored in the same manner in the TableModel.
You can then change the view for the columns to be displayed. That is you can remove TableColumns from the TableColumnModel of the JTable and only the data the user want to view will be displayed, even though it is still available in the model. Then means the user can click on any check box at any time and you don't need to redo the database query, only add the TableColumn back to the table.
Check out Table Column Manager for an example of this approach. This class uses a popup menu to manage the columns, but you can still use your check boxes. You just need to invoke the appropriate method of the TableColumnManager to hide/show a column. That is, assuming the labels of the check boxes match the headings in the table you can just use the check box text to hide/show a column.
The other approach is to NOT hard code the column names (if you build your query to only get specific columns) but instead get the column names from the meta data of the ResultSet. The TableFromDatabaseExample.java from Table From Database shows how this can be done. The code is generic so that appropriate renderers are used for Dates, Integers etc.

how to add items to a JTable using a loop

I came up with this loop here:
for(int i = 0; i < memberArrayList.size(); i++){
int j = 0;
memberTable.setValueAt(memberArrayList.get(i).getFirstName(), i, j);
j++;
memberTable.setValueAt(memberArrayList.get(i).getLastName(), i, j);
j++;
memberTable.setValueAt(memberArrayList.get(i).getEmail(), i, j);
j++;
memberTable.setValueAt(memberArrayList.get(i).getPhoneNumber(), i, j);
j++;
memberTable.setValueAt(memberArrayList.get(i).getLevel(), i, j);
}
It works but I'm sure there is a better way
The answer will depend on what type of TableModel you are using, for example, if you are using a DefaultTableModel you could use something like...
DefaultTableModel memberTable= ...;
for(int i = 0; i < memberArrayList.size(); i++){
Object[] rowData = new Object[]{
memberArrayList.get(i).getFirstName(),
memberArrayList.get(i).getLastName(),
memberArrayList.get(i).getEmail(),
memberArrayList.get(i).getPhoneNumber(),
memberArrayList.get(i).getLevel()
};
memberTable.addRow(rowData);
}
This assumes that you only want to add rows to the end of the table model ;)
Personally, I'd prefer to add each member to the table model as a single entity, but that's just me
Take a look at How to Use Tables for more details...
Swing components are using MVC pattern. JTable is the view, TableModels are the model that contains data.
you can subclass AbstractTableModel to achieve your goal.
public class MyTableModel extends AbstractTableModel {
// override these methods:
// getColumnCount, getRowCount, getValueAt
// and add data manipulation methods
}
then your table is created using MyTableModel
MyTableModel mtm = new MyTableModel(......);
JTable table = new JTable(mtm);
then call data manipulation methods in mtm. something like
mtm.addPerson(new Person(......));

How do I get an updated post-sort TableModel?

I have a JTable using setAutoCreateRowSorter(true) and a RowSorterListener attached, per below, because I need to perform some operations elsewhere in my application upon a sort of the JTable.
I find that whenever I click a column header to sort, the JTable redisplays the rows in the proper order and the listener is called, but the TableModel I pull out is always the original pre-sort table model.
table.getRowSorter().addRowSorterListener(new RowSorterListener() {
#Override
public void sorterChanged(RowSorterEvent rsevent) {
rsevent.getSource().getModel(); // Nope, original ordering here
table.getModel(); // Same thing
}
};
How do I get the new post-sort ordering of the rows, as is now displayed in the JTable?
The data in the TableModel never changes, only the view of the data changes.
If you want the data from the model in the order it is displayed in the table then you just use:
table.getValueAt(row, column);
In other words you need to iterate through all the rows and columns to get the data in the currently viewed order.
I think you can use table.convertRowIndexToModel(int ...), however I think that there's a better solution available.
If you define your own convertRowIndexToModel() that's a quick lookup (perhaps through a map, O(1)), that solution should be sufficient. If it's O(n), then that's what needs to be fixed.
Simply go through a loop of the rows, convert each one and do the lookup using the resulting index.
Here's 2 methods in TableSorter that should be of interest:
private Row[] getViewToModel() {
if (viewToModel == null) {
int tableModelRowCount = tableModel.getRowCount();
viewToModel = new Row[tableModelRowCount];
for (int row = 0; row < tableModelRowCount; row++) {
viewToModel[row] = new Row(row);
}
if (isSorting()) {
Arrays.sort(viewToModel);
}
}
return viewToModel;
}
and:
private int[] getModelToView() {
if (modelToView == null) {
int n = getViewToModel().length;
modelToView = new int[n];
for (int i = 0; i < n; i++) {
modelToView[modelIndex(i)] = i;
}
}
return modelToView;
}
Hi I know this is a really late answer, but I tried the code used on the comment of #LazyCubicleMonkey and it did work here is my code in getting the row when the jTable is sorted.
DefaultTableModel search_model = (DefaultTableModel) jTable.getModel();
search_model.removeRow(jTable.convertRowIndexToModel(row));
jTable.setModel = (search_model)

create TableModel and populate jTable dynamically

I want to store the results of reading lucene index into jTable, so that I can make it sortable by different columns. From index I am reading terms with different measures of their frequencies.
Table columns are these :
[string term][int absFrequency][int docFrequency][double invFrequency]
So i in AbstractTableModel I can define column names, but i dont know how to get the Object[][]data with results from the following method:
public static void FrequencyMap(Directory indexDir) throws Exception
{
List<ArrayList>redoviLista = new ArrayList<ArrayList>();
//final Map<String,TermRow> map = new TreeMap<String,TermRow>();
List<String>termList = new ArrayList<String>();
IndexReader iReader = IndexReader.open(indexDir);
FilterIndexReader fReader = new FilterIndexReader(iReader);
int numOfDocs = fReader.numDocs();
TermEnum terms = fReader.terms();
while (terms.next()){
Term term = terms.term();
String termText = term.text();
termList.add(termText);
//Calculating the frequencies
int df = iReader.docFreq(term);
double idf = 0.0F;
idf = Math.log10((double) numOfDocs / df);
double tfidf = (df*idf);
//Here comes important part
//Changes according to takoi's answer
ArrayList<Object> oneRow = new ArrayList<Object>();
oneRow.add(termText);
oneRow.add(df);
oneRow.add(idf);
oneRow.add(tfidf);
redoviLista.add(oneRow);
}
iReader.close();
// So I need something like this, and i Neeed this array to be stored out of this method
So I am kindda stuck here to proceed to implement AbstractTableModel and populate and display this table .... :/
Please help!
When you are inserting, deleting or updating data in your model, you need to notify the GUI of the changes. You can do this with the fire-methods in the AbstractTableModel.
I.e. if you add an element to your list, you also have to call fireTableRowsInserted(int firstRow, int lastRow) so that the visible layer can be updated.
Have a look at addElement(MyElement e) in the code below:
public class MyModel extends AbstractTableModel {
private static final String[] columnNames = {"column 1", "column 2"};
private final LinkedList<MyElement> list;
private MyModel() {
list = new LinkedList<MyElement>();
}
public void addElement(MyElement e) {
// Adds the element in the last position in the list
list.add(e);
fireTableRowsInserted(list.size()-1, list.size()-1);
}
#Override
public int getColumnCount() {
return columnNames.length;
}
#Override
public int getRowCount() {
return list.size();
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
switch(columnIndex) {
case 0: return list.get(rowIndex).getColumnOne();
case 1: return list.get(rowIndex).getColumnOne();
}
return null;
}
}
There is no need to create a custom TableModel for this. Just use the DefaultListModel.
The DefaultListModel allows you to dynamcially add rows to the model using the addRow(...) method and it automatically invokes the appropriate fireXXX method to tell the table to repaint itself.
The basic code would be:
DefaultTableModel model = new DefaultTableModel( columnNames );
while (...)
{
Vector row = new Vector();
row.add(...)
row.add(...)
model.addRow( row );
}
JTable table = new JTable( model );
You don't need to create a custom TableModel everytime. Sometimes you can start with the DefaultTableModel.
You dont have to create an Object[][]. Just make your redoviLista a list of lists:
redoviLista.add( new ArrayList<Object>(termText, df, idf, tfidf) ); #pseudocode
then you implement getValueAt like this:
getValueAt(int row, int column){
redoviLista.get(row).get(column);
}

Manipulating fields in the jTable Java

I have populated my jTable with the following Code. It has two columns, the first has variable name and the second is a list of its dependencies. The user can change the dependencies by selecting them from the list in the jTable.
When the user changes a value, I want to row to be added to another jTable (which would be no user editable. How would I do that?
The code to populate the table is
Vector<Vector> data = new Vector<Vector>();
for (String v : acn.getVariableNames()) {
Vector tmp = new Vector();
tmp.add(v);
ArrayList<String> temp = new ArrayList<String>();
for (String u : acn.getVariableDomain(v)) {
temp.add(u);
}
tmp.add(temp);
data.add(tmp);
}
Vector names = new Vector();
names.add("Variable");
names.add("Domain Value");
DefaultTableModel dt = new DefaultTableModel();
dt.setDataVector(data, names);
jTable2.setModel(dt);
jTable2.getColumnModel().getColumn(1).setCellEditor(new ChangeImpactEditor());
jTable2.getColumnModel().getColumn(1).setCellRenderer(new TableListRenderer());
The way i would do it is to override
public void setValueAt(Object aValue, int rowIndex, int columnIndex);
from your TableModel.
The setValue method get called by the JTable after the user has editted a value
In your overriden method you then can set the value in the other tablemodel

Categories

Resources