I've got a table with three columns. Columns are added to the table using these few lines of code below:
...
for (Map.Entry<String, Integer> column : this.columns.entrySet())
{
this.addColumn(column.getKey(), column.getValue());
}
...
public void addColumn(String name, int size)
{
this.columns.put(name, size); //<--- set the column size
this.defaultModel.addColumn(name); //<--- add the new column
}
this.columns is a Map<String, Integer> filled with column name and his size.
My goal is to set the column type for each columns of my table, for instance: I want the third column render as a checkbox because it's a boolean (indeed, not the default String field rendering). Took a look to how to use tables but I still haven't figured out how to deal with it specially "Using Custom Renderers" chapter and when it uses Array to determine the cell render (?) while storing rows inside the table. I use Vector instead of Arrays to manipulate data... I'm a bit confused...
How could I set a custom render on columns?
override getColumnClass() in DefaultTableModel as below:
this.defaultModel = new DefaultTableModel(){
#Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex==2 ? Boolean.class : Object.class;
}
}
Related
I'm familiarizing myself with JTable filtering for a project I'm working on to enhance my java knowledge.
I'm facing a problem with the values that are being returned once I click on the table after applying a filter by a String value read from a JTextField.
For example if I click a row from the original data, let's say (row 2), the values returned will be the full path and target, which are correct As shown in the Orignal table data image (Image 1)
However, after filtering let's say by taget as shown in filtered table data image (Image 2) the values returned are the path of src project name.
If not mistaken, it has something to do with the table model after filtering.
// Code to retrieve selected rows
public String[] getClickedProject(JTable table) {
String[] projectPathAndName = new String[2];
int selectRowIndex = table.getSelectedRow();
projectPathAndName[0] = table.getModel().getValueAt(selectRowIndex, 0).toString().trim();
projectPathAndName[1] = table.getModel().getValueAt(selectRowIndex, 1).toString().trim();
return projectPathAndName;
}
// Code to filter table
public void filterTableByProject(JTable table, String projectName) {
DefaultTableModel defaultTableModel = (DefaultTableModel) table.getModel();
TableRowSorter<DefaultTableModel> tableRowSorter = new TableRowSorter<>(defaultTableModel);
table.setRowSorter(tableRowSorter);
tableRowSorter.setRowFilter(RowFilter.regexFilter(projectName));
}
I would appreciate any help/pointers, or sample code
Thank you
I solved the problem by replacing table.getModel().getValueAt(selectRowIndex, 0).toString().trim(); with table.getValueAt(selectRowIndex, 0).toString().trim();
My analysis, correct me if I'm wrong please, since I was using table.getModel().getValueAt(row, column) to retrieve the value, it was using the original table model, whereas using table.getValueAt(row, column) automatically updates the table model and uses the filtered table
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 am experiencing a issue while trying to add new row in my JTable. My JTable is using DefaultTableModel, here is the code I use for adding a new row:
AddDialog diag = new AddDialog(MainWindow.getInstance(),"Add Entity",true,tab);
diag.setVisible(true);
if(diag.isSaved()) {
entity = diag.getEntity();
table = diag.getTableModel();
table.getEntities().add(entity);
if(tab instanceof TablePreview) {
tablePreview = (TablePreview)tab;
tableModel = (DefaultTableModel) (tablePreview.getTableView().getModel());
Object[] newRow = new Object[entity.getAttributes().size()];
int i=0;
for (Entry<String, Object> entry : entity.getAttributes().entrySet()) {
newRow[i++]=entry;
}
tableModel.addRow(newRow);
}else if(tab instanceof ChildTablePreview) {
System.out.println("Tab is instanceof ChildTablePreview");
}
}else {
System.out.println("Entity not saved!");
}
diag is instance of AddDialog which extends JDialog, and when I fill in the fields of the dialog and click save it creates an Entity class which I want to add to the table as a new row. The logic works fine, but when the row gets inserted into the table, for some reason table looks like this:
If anyone has any idea how I can fix this, I would really appreciate the help!
You need to use a custom cell renderer in your JTable.
How data appears is based on the class of the columns. The default renderer simply calls the .toString() function for the objects in the column. If the column contains a key value pair, its common for these to appear as key=value.
You need to set the renderer using the TableColumn method setCellRenderer. You can define this renderer to only display the value for the objects in that column.
I am having difficulties with deleting the actual data under a particular column which I am trying to delete.
I actually want to delete the column and its underlying data. I am able to insert new columns but when I delete
and insert again, the old columns which I previously deleted pop up again.
Any sort help is appreciated.
Thank you in advancce.
The data is stored in the TableModel.
Deleting the column from the ColumnModel will only prevent the view (the JTable) from showing it.
In order to remove it, you need to tell the TableModel to remove the column data as well.
Depending on you implementation, you could use JTable.setValueAt(value, row, column) or TableModel.setValueAt(value, row, column), which ever is more convenient.
This of course assumes you've implemented the setValueAt method
public void removeColumnAndData(JTable table, int vColIndex) {
MyTableModel model = (MyTableModel)table.getModel();
TableColumn col =table.getColumnModel().getColumn(vColIndex);
int columnModelIndex = col.getModelIndex();
Vector data = model.getDataVector();
Vector colIds = model.getColumnIdentifiers();
// Remove the column from the table
table.removeColumn(col);
// Remove the column header from the table model
colIds.removeElementAt(columnModelIndex);
// Remove the column data
for (int r=0; r<data.size(); r++) {
Vector row = (Vector)data.get(r);
row.removeElementAt(columnModelIndex);
}
model.setDataVector(data, colIds);
// Correct the model indices in the TableColumn objects
// by decrementing those indices that follow the deleted column
Enumeration<TableColumn> enum1 = table.getColumnModel().getColumns();
for (; enum1.hasMoreElements(); ) {
TableColumn c = (TableColumn)enum1.nextElement();
if (c.getModelIndex() >= columnModelIndex) {
c.setModelIndex(c.getModelIndex()-1);
}
}
model.fireTableStructureChanged();
}
/*MyDefaultTableModel class**/
class MyTableModel extends DefaultTableModel
{
String columns[];
int size;
public MyTableModel(String col[],int size)
{
super(col,size);
columns = col;
this.size=size;
}
public Vector getColumnIdentifiers()
{
return columnIdentifiers;
}
}
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.