Let's say I have this JTable:
private JScrollPane scrollPane1;
private JTable table;
private DefaultTableModel model;
DefaultTableModel model = new DefaultTableModel();
model.addColumn("Item");
model.addColumn("ID");
model.addColumn("Price");
model.addColumn("Category");
this.scrollPane1 = new JScrollPane();
this.scrollPane1.setBounds(33, 518, 604, 300);
this.contentPane.add(this.scrollPane1);
table = new JTable(model);
this.scrollPane1.setViewportView(table);
I have an Item class, so when I create a new instance of Item, I add the items info to the JTable:
Item item = new Item("Shovel", 1, 123, "Tools");
model.addRow(new Object[]{item.getItem(), item.getID(), item.getPrice(), item.getCategory()});
So far so good.
But if I update the Item, for example change the price from 5 to 10, it is not updated in the JTable. So if I for example do:
item.setPrice(10);
It doesnt update in the JTable. I've tried calling model.fireTableDataChanged(); and table.repaint(); but none of them works. It seems like the value in the cell is not associated with the objects value? How would I do this? (First time working with JTables).
Any suggestions? Don't be bothered by the lack of methods etc., I just put this together quickly for this post.
EDIT: My Item objects are stored in a HashMap.
Actually, you are not modifying any cell in the table, the item object is not associated with your table directly, your making a now values in the table by using the same values from the item object.
what you has to do is to update the cell itself then by overriding the below method from AbstractTableModel class:
#Override
public void setValueAt(Object value, int row, int col) { //
Then call
model.fireTableDataChanged();
Related
I have a table with x num of rows, I have a second table with the same number of rows but different columns and metadata, they have different table models. but each row represents the same object (a song).
I want to synchronize row sorting between the two tables so for example if I sort on column 2 of table 1 then rows of the table will be sorted in the same order. But currently, I just have sorted by matching sort keys so sort on the same column (but because different data get different results)
e.g
Starting point
Table 1
1 tom
2 jane
3 fred
4 steve
5 jim
Table 2
1 oranges
2 apples
3 pears
4 lemons
5 plums
If I sort by table 1, column 2 Ascending I want to get
Table 1
2 jane
5 jim
3 fred
4 steve
1 tom
Table 2
2 apples
5 plums
3 pears
4 lemons
1 oranges
but I get
Table 1
2 jane
5 jim
3 fred
4 steve
1 tom
Table 2
2 apples
4 lemons
1 oranges
3 pears
5 plums
My sorting is done by calling setSortKeys() on table 2 to the getSortKeys() of table 1 and vice versa. I can see why it doesn't work, I am telling table 2 to sort by column 2 ascending the same as table 1 but of course these columns have different data in each table. But I can't work out a way to get table 2 to sort to the final order of table 1 instead.
One solution would be for both tables to share the same table model and just show the columns relevant to their table, but that would require a major rework of the code, so I am hoping for a more specific solution just to resolve the sorting issue.
I am using Java 11, and swingx latest version 1.6.4 (i know very old) but this delegates sorting to standard Java (earlier version that I was previously using had its own sorting) so not really a swingx question.
The real world situation, within my application is as follows, each row represents a song, and the tabs show metadata for that song. the tabs under the edit menu all share same model and all work using the setSortKeys() method described above. So here i have sorted on Mood Aggressive column
Edit metadata tab
and if I go to another tab, we see the rows are sorted in same order
Another Edit metadata tab, sorted same order
but if I go to the Edit ID3 tab, we see the rows have been sorted in different order.
ID3 Edit tab sorted different order
This is because ID3 Edit tab shows the metadata in different format (ID3) and has different table model so column x represent in the model stores different data.
Note because all models store the rowno in first column, sorting my the rowno column works for all tabs.
So from a user point of view they are just viewing different tabs of the same table, and therefore would expect sort to be consistent for the tabs
I came up with the following approach which translates rowIndex for the second table using rowSorter of the first table.
TableOneModel tableOneData = new TableOneModel( /* omitted */ );
JTable tableOne = new JTable(tableOneData);
TableRowSorter<TableOneModel> sorterOne = new TableRowSorter<>(tableOneData);
tableOne.setRowSorter(sorterOne);
TableTwoModel tableTwoData = new TableTwoModel(
/* omitted */,
sorterOne);
JTable tableTwo = new JTable(tableTwoData);
The model for the first table, TableOneModel, is a subclass of AbstractTableModel implementing the required methods:
private static class TableOneModel extends AbstractTableModel {
private final String[] columnNames;
private final Object[][] data;
public TableOneModel(String[] columnNames, Object[][] data) {
this.columnNames = columnNames;
this.data = data;
}
public int getRowCount() { return data.length; }
public int getColumnCount() { return columnNames.length; }
public Object getValueAt(int rowIndex, int columnIndex) {
return data[rowIndex][columnIndex];
}
}
The model for second table, TableTwoModel, stores the reference to the rowSorter of the first table to do the translation of row indexes:
private static class TableTwoModel extends TableOneModel
implements RowSorterListener {
private final RowSorter<? extends TableModel> otherSorter;
public TableTwoModel(String[] columnNames, Object[][] data,
RowSorter<? extends TableModel> sorter) {
super(columnNames, data);
this.otherSorter = sorter;
installListeners();
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return super.getValueAt(
otherSorter.convertRowIndexToModel(rowIndex),
columnIndex);
}
private void installListeners() {
otherSorter.addRowSorterListener(this);
}
#Override
public void sorterChanged(RowSorterEvent e) {
fireTableDataChanged();
}
}
When the sorting order of the first table changes, the second table model calls fireTableDataChanged() to notify the view it needs to update all the data.
Edit: As Paul mentioned in the comment, the sort order of the second table should also change the first table. So the sync should work both ways.
In the updated version, both tables use TableTwoModel and the first table identifies itself as the leading one. (Just as I've been writing the update, I realised this wasn't necessary.) Thus, the implementation of TableTwoModel remains basically unchanged. I changed sorterChanged in TableTwoModel to call fireTableDataChanged() only for SORTED event type that is when the sorting of the table is complete. It's a little optimisation.
The tricky part was to sync/reset RowSorter of the tables. However, the solution proved to be pretty simple. This is achieved by installing RowSorterListener to each row sorter. If the event type is SORT_ORDER_CHANGED and the list of sort keys of this RowSorter is non-empty, the sort keys of the other are set to null. Thus, only one table is sorted and the other follows the sort order of the sorted one.
Here is what I meant in the comments:
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableRowSorter;
import java.awt.*;
public class TablesExample extends JPanel {
static class MyTableModel extends AbstractTableModel {
private String[] columnNames = {"Row Id",
"Person",
"Fruit"};
private Object[][] data = {
{"1", "Tom", "Orange"},
{"2", "Jane", "Apple"},
{"3", "Fred", "Pear"},
{"4", "Steve", "Lemon"},
{"5", "Jim", "Plum"}
};
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return data.length;
}
public String getColumnName(int col) {
return columnNames[col];
}
public Object getValueAt(int row, int col) {
return data[row][col];
}
}
public static void main(String[] args) {
JFrame frame = new JFrame("Tables Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.setPreferredSize(new Dimension(500, 100));
TablesExample newContentPane = new TablesExample();
newContentPane.setOpaque(true);
MyTableModel model = new MyTableModel();
TableRowSorter<MyTableModel> sorter = new TableRowSorter<>(model);
JTable table = new JTable(model);
table.setRowSorter(sorter);
TableColumn column2 = table.getColumnModel().getColumn(2);
column2.setMinWidth(0);
column2.setMaxWidth(0);
column2.setWidth(0);
JScrollPane scrollPane = new JScrollPane();
scrollPane.setViewportView(table);
tabbedPane.add("Persons", scrollPane);
JTable table2 = new JTable(model);
table2.setRowSorter(sorter);
TableColumn column1 = table2.getColumnModel().getColumn(1);
column1.setMinWidth(0);
column1.setMaxWidth(0);
column1.setWidth(0);
JScrollPane scrollPane2 = new JScrollPane();
scrollPane2.setViewportView(table2);
tabbedPane.add("Fruits", scrollPane2);
frame.setContentPane(tabbedPane);
frame.pack();
frame.setVisible(true);
}
}
Have a prototype working.
So using swingx we implement a subclass of TableSortController and override toggleSortOrder() and set this as the rowSorter of the main table
public void toggleSortOrder(int column)
{
.........
setSortKeys(newKeys);
for(int i=0; i < getModelWrapper().getRowCount(); i++)
{
SecondTable.instanceOf().getRealModel()
.setValueAt(convertRowIndexToView(i), i, ID3TagNames.INDEX_SYNC_SORT);
}
newKeys = new ArrayList<>();
SecondTable.instanceOf().getTable().getRowSorter().setSortKeys(newKeys);
newKeys.add(new SortKey(ID3TagNames.INDEX_SYNC_SORT, this.getFirstInCycle()));
SecondTable.instanceOf().getTable().getRowSorter().setSortKeys(newKeys);
}
Logic is does a normal sort on main table, then sets hidden column on second table to store the view index of each row. Then remove any existing sort on second table, then sort by hidden column.
Note the two calls to setSortKey() are needed because if you sort by one column on main table, and then do another sort in both cases will be sorting second table by INDEX_SYNC_SORT ascending and hence the following code in superclass DefaultRowSorter.setSortKeys()
will prevent a sort being done because the sortkey will be the same as previous sort
if (!this.sortKeys.equals(old)) {
fireSortOrderChanged();
if (viewToModel == null) {
// Currently unsorted, use sort so that internal fields
// are correctly set.
sort();
} else {
sortExistingData();
}
}
For now in this prototype we have a default sort controller on the SecondTable as we don't want this to do the special processing as well. But probably want to sort on both and therefore would need the toggleSort() code to check what table they are linked to and act accordingly.
I have a Jtable, whose first column uses a JComboBox as editor. The combobox model contains data objects fetched from a sql database.
If I manually enter a value inside the combobox and then leave the editor, the entered value is lost. This doens't happen if the value is selected from the popup, or if the JComboBox's model is instantiated with simple Strings instead of bean objects.
Note that I need to add row dinamically, but this seems irrelevant since the issue appears both in the default row and in the added rows.
This is a working NetBeans sample project that reproduces my issue: https://drive.google.com/open?id=0B89FsS48-Yy4V09YRVozRzJGMkk
Here is the relevant code:
public NewJFrame() {
initComponents();
//bean objects used to populate the combobox:
Item item1 = new Item("one", 1);
Item item2 = new Item("two", 2);
Item item3 = new Item("three", 3);
JComboBox<Item> comboBox = new JComboBox<>(new Item[]{item1, item2, item3});
comboBox.setEditable(true);
DefaultCellEditor defaultCellEditor = new DefaultCellEditor(comboBox);
defaultCellEditor.setClickCountToStart(1);
jTable1.getColumnModel().getColumn(0).setCellEditor(defaultCellEditor);
}
private void addRowButtonActionPerformed(java.awt.event.ActionEvent evt) {
((DefaultTableModel) jTable1.getModel()).addRow(new Object[]{null, null});
}
Update: when the JComboBox in the table is populated via a model instead of the constructor like this,
items = new Item[]{item1, item2, item3};
JComboBox<Item> comboBox = new JComboBox<>();
comboBox.setModel(new DefaultComboBoxModel<>(items));
a manually inserted value is kept displayed, only if no selection has been made before; that is, if I first select a choice and then edit the choice, when leaving the combobox the previously selected item reappears.
None of the reported behaviours occur in a JComboBox outside the table, so this led me to think it's something related to the CellEditor.
Update 2: here's a bug report of this issue from the year 2000! They said they solved it back then but this is far from solved after 15 years.
I have a JTable whose cells are editable. However if i edit a cell and refresh the table. The changes are not saved. This is how i have defined the table:
String [] columnNames = {"Application number",
"Name",
"date",
"fileLoc",
"Country"};
//Data for the table
String [][] data = table.tableArray;//tableArray is an array of array of strings.
TableModel model = new DefaultTableModel(data, columnNames);
JTable mainTable = new JTable(model);
model.isCellEditable(data.length,columnNames.length);
contentPane.add(new JScrollPane (mainTable));
I've had a look online but can't seem to find any advice on saving the changes made to a cell. Any help would be very welcome!
I guess i'm not refreshing the table as such. I use frame.dispose()
and then create a new frame with the table in.
then you lost all changes made in the current DefaultTableModel
don't top create a new JFrame with a new DefaultTableModel and a new JTable
all changes from TableCellEditor are changes dispayed in JTables view
JTable (with its model) is prepared for this job, don't to reacreate these Objects on runtime
DefaultTableModel has implemented all notifiers, there no needed to override any others events, nor to fireXxxXxx() programatically, but those events are required for AbstractTableModel
define add this code line for your JTablemainTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
DefaultTableModel respesenting your required 2D array
I have read one of the question in this website. and since I had the same problem with the one who asked the same question as mine, I want to do a follow up question. HOW DO YOU PUT THIS INTO CODE:
Ask the master table what its selected row is (getSelectedRow()).
Convert this view row index to a model row index (convertRowIndexToModel()).
Ask the model for the data at this index, ands extract the ID of the data. The model should be a class that you wrote, extending AbstractTableModel.
Then get the data to display in the three sub-tables from this ID, and change the model (or the data contained in the model) of these three tables.
Thanks in advance. i am quite having a hard time in this part of my program. since i only know about
tablePersonalProperty.setModel(DbUtils.resultSetToTableModel(rs));
when displaying all the items from the table. what i need is to DISPLAY the items with the same id from what i have chosen from the main table...
Before we can help you write code, we need more information.
Do your tables both have exactly the same columns?
Are you using your own custom data model already? If not, then you probably need to try that on your own. I can't write this for you since I don't know what you need to include in your model. If you are using netbeans, then you can use the form designer to help you write the table model. Just look at the properties of the JTable after you add it to the JFrame of JPanel. I ended up creating my own anyway, but the code that Netbeans generated helped get me started.
This sample code will help you to do what you are looking for, it show how to move table row from one table to another in a click event in rows,
public class InsertRows{
public static void main(String[] args) {
new InsertRows();
}
public InsertRows(){
final JTable table, table2;
final DefaultTableModel model, model2;
JFrame frame = new JFrame("Inserting rows in the table!");
String data[][] = {{"Vinod","100"},{"Raju","200"},{"Ranju","300"}};
String col[] = {"Name","code"};
Object[][] selrowData = {};
model = new DefaultTableModel(data,col);
model2 = new DefaultTableModel(selrowData,col);
GridLayout gl = new GridLayout(2,1);
table = new JTable(model);
table2 = new JTable(model2);
//Insert first position
model.insertRow(0,new Object[]{"Ranjan","50"});
//Insert 4 position
model.insertRow(3,new Object[]{"Amar","600"});
//Insert last position
model.insertRow(table.getRowCount(),new Object[]{"Sushil","600"});
ListSelectionModel cellSelectionModel = table.getSelectionModel();
cellSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
cellSelectionModel.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
String selectedData = null;
String selectedData2 = null;
Object[][] val = {};
int selectedRow = table.getSelectedRow();
int selectedColumns = table.getColumnCount();
model2.insertRow(0,new Object[]{(String) table.getValueAt(selectedRow, selectedColumns-selectedColumns),(String) table.getValueAt(selectedRow, selectedColumns-1) });
}
});
frame.setLayout(gl);
frame.add(new JScrollPane(table));
frame.add(new JScrollPane(table2));
frame.setSize(600,600);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
JAVA
NETBEANS
// resultsTable, myModel
JTable resultsTable;
DefaultTableModel myModel; //javax.swing.table.DefaultTableModel
myModel = (DefaultTableModel) resultsTable.getModel();
// event of clicking on item of table
String value = (String) myModel.getValueAt(resultsTable.getSelectedRow(), columnIndex)
I use JTable and DefaultTableModel to view a table of various info
and I want to get a value of a certain column of the selected index of the table.
The code I wrote above works fine except when:
I use the sort of the GUI (click on the field name I want to sort on the table)
The table is properly sorted but after that when I select a row, it gets
the value of the row that was there before the sort.
This means that after sorting (using the JTable's GUI)
the 'myModel' and 'resultsTable' objects have different row indexes.
How do I synchronize those two?
You need to use the 'convertXXX' methods on the JTable see the JavaDoc
int row = resultsTable.getSelectedRow();
if (row != -1) {
row = table.convertRowIndexToModel(row);
String value = (String) myModel.getValueAt(row, columnIndex)
A problem with using the JTable.getValueAt() is to get the column you want. When the columns are moved around in the GUI the indexes "change" to match the view. By using the AbstractTableModel.getValueAt() and the JTable.convertXXX() (as outlined by Guillaume) it's just a matter of using the column indexes for the model when retrieving data.
Except from the solution Guillaume gave (Thanks)
I did this:
// resultsTable, myModel
JTable resultsTable;
DefaultTableModel myModel; //javax.swing.table.DefaultTableModel
myModel = (DefaultTableModel) resultsTable.getModel();
// event of clicking on item of table
String value = (String) **resultsTable**.getValueAt(resultsTable.getSelectedRow(), columnIndex)
I used the resultsTable Object instead of the myModel Object to get the value.