I have two JTables aligned horizontally inside JScrollBar. When I select a cell in the first JTable I would like the corresponding cell on the other JTable to be aligned.
What I'm doing is that I use, each time a cell in the first JTable is selected, the JTable.getCellRect() method to get the the y position of each cell and then use JTable.scrollRectToVisible(firstJTableCell). The cell in the second JTable becomes then visible, but I would like to know how to align it with the selected cell in the first JTable.
I have a way to know which cell to select in the second JTable when a cell is selected in the first JTable. What I would like to know, is how to align two JTables cell programatically.
There are probably better ways to achieve the same result, essentally though, you can just use an AdjustmentListener and a ListSelectionListener to monitor changes to the JScrollPanes and JTables and sync those results back to the other table (based on the source of the original event)
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
public class SyncedTables {
public static void main(String[] args) {
new SyncedTables();
}
public SyncedTables() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JTable left;
private JTable right;
public TestPane() {
setLayout(new GridLayout(1, 2));
left = new JTable(createTableModel("L"));
right = new JTable(createTableModel("R"));
left.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
right.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
JScrollPane leftScrollPane = new JScrollPane(left);
JScrollPane rightScrollPane = new JScrollPane(right);
AdjustmentHandler ah = new AdjustmentHandler(leftScrollPane, rightScrollPane);
leftScrollPane.getVerticalScrollBar().addAdjustmentListener(ah);
leftScrollPane.getHorizontalScrollBar().addAdjustmentListener(ah);
rightScrollPane.getVerticalScrollBar().addAdjustmentListener(ah);
rightScrollPane.getHorizontalScrollBar().addAdjustmentListener(ah);
SelectionHandler sh = new SelectionHandler(left, right);
left.getSelectionModel().addListSelectionListener(sh);
right.getSelectionModel().addListSelectionListener(sh);
add(leftScrollPane);
add(rightScrollPane);
}
protected TableModel createTableModel(String prefix) {
DefaultTableModel model = new DefaultTableModel(0, 50);
for (int row = 0; row < 100; row++) {
Object[] rowData = new Object[50];
for (int col = 0; col < 50; col++) {
rowData[col] = prefix + "-" + row + "x" + col;
}
model.addRow(rowData);
}
return model;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
}
public class SelectionHandler implements ListSelectionListener {
private JTable left;
private JTable right;
private boolean ignore;
public SelectionHandler(JTable left, JTable right) {
this.left = left;
this.right = right;
}
#Override
public void valueChanged(ListSelectionEvent e) {
if (!ignore) {
ignore = true;
if (e.getSource() == left.getSelectionModel()) {
sync(left, right);
} else if (e.getSource() == right.getSelectionModel()) {
sync(left, right);
}
ignore = false;
}
}
protected void sync(JTable master, JTable slave) {
slave.clearSelection();
int[] selectedRows = master.getSelectedRows();
for (int row : selectedRows) {
slave.addRowSelectionInterval(row, row);
}
int[] selectedColumns = master.getSelectedColumns();
for (int cols : selectedColumns) {
slave.addColumnSelectionInterval(cols, cols);
}
}
}
public class AdjustmentHandler implements AdjustmentListener {
private JScrollPane left;
private JScrollPane right;
private boolean ignore;
public AdjustmentHandler(JScrollPane left, JScrollPane right) {
this.left = left;
this.right = right;
}
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
if (!ignore) {
ignore = true;
Container parent = ((Component) e.getSource()).getParent();
if (parent == left) {
sync(left, right);
} else if (parent == right) {
sync(right, left);
}
ignore = false;
}
}
protected void sync(JScrollPane master, JScrollPane slave) {
slave.getHorizontalScrollBar().setValue(master.getHorizontalScrollBar().getValue());
slave.getVerticalScrollBar().setValue(master.getVerticalScrollBar().getValue());
}
}
}
Related
See at the end my SSCCE code. What I'm trying to achieve is:
Equal number of columns in the data model and columns model
Using setAutoCreateColumnsFromModel(false) to avoid recreation of columns model when a column is added or removed by the data/table model
Ability to move columns
The large button adds a new column to the end. Each new column gets a unique identifier.
The header has a right click menu HeaderMenu to remove columns. A timer calls table.tableModel.addRow() to insert a new row on the top. The data for each column is generated by class Column. In this demo the value is simply a rows counter with column's identifier.
In the actual table (not this demo)
each column is a subclass of Column and generates meaningful data
the menu also contains insert left/right and replace. This is achieved using similar as in the demo add/remove methods and by moving a column to the desired position
the data model may contain a dozen of columns and over a million rows
rows may be added with a time interval between a millisecond to several seconds, i.e. performance matters
This demo demonstrates the problem with deletion of columns which generates errors like this:
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 2 >= 2
at java.util.Vector.elementAt(Vector.java:477)
at javax.swing.table.DefaultTableModel.getValueAt(DefaultTableModel.java:649)
at javax.swing.JTable.getValueAt(JTable.java:2720)
at javax.swing.JTable.prepareRenderer(JTable.java:5712)
at javax.swing.plaf.basic.BasicTableUI.paintCell(BasicTableUI.java:2114)
...
Please advise how to fix it. Here is the entire code:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
#SuppressWarnings("serial")
public class TableDemo extends JTable {
private static class Column {
private int rowsCounter = 0;
private final String identifier;
public Column(String identifier) {
this.identifier = identifier;
}
private String nextCellValue() {
return (rowsCounter++) + ", id: " + identifier;
}
}
private static class MyTableModel extends DefaultTableModel {
private final List<Column> columns = new ArrayList<>();
private int nextColumnIdentifier = 0;
private void addRow() {
Object[] row = columns.stream().map(Column::nextCellValue).toArray();
insertRow(0, row);
}
private TableColumn addColumn() {
String identifier = String.valueOf(nextColumnIdentifier++);
columns.add(new Column(identifier));
addColumn(identifier);
TableColumn tc = new TableColumn();
tc.setIdentifier(identifier);
tc.setHeaderValue(identifier);
tc.setModelIndex(columns.size() - 1);
return tc;
}
private void removeColumn(int idx) {
columns.remove(idx);
columnIdentifiers.remove(idx);
for (Object row : dataVector) {
((Vector<?>) row).remove(idx);
}
fireTableStructureChanged();
}
}
private static class HeaderMenu extends JPopupMenu {
private int columnViewIndex;
private HeaderMenu(final TableDemo table) {
JMenuItem item = new JMenuItem("Delete column");
item.addActionListener(e -> table.deleteColumn(columnViewIndex));
add(item);
final MouseAdapter ma = new MouseAdapter() {
boolean dragged = false;
#Override
public void mouseReleased(MouseEvent e) {
if (!dragged && e.getButton() == MouseEvent.BUTTON3) {
final Point p = e.getPoint();
SwingUtilities.invokeLater(() -> {
columnViewIndex = table.columnAtPoint(p);
show(e.getComponent(), p.x, p.y);
});
}
dragged = false;
}
#Override
public void mouseDragged(MouseEvent e) {
dragged = true;
}
};
table.getTableHeader().addMouseListener(ma);
table.getTableHeader().addMouseMotionListener(ma);
}
}
private MyTableModel tableModel = new MyTableModel();
private TableDemo() {
new HeaderMenu(this);
setModel(tableModel);
setAutoCreateColumnsFromModel(false);
setDefaultEditor(Object.class, null);
}
private void addColumn() {
TableColumn tc = tableModel.addColumn();
addColumn(tc);
}
void deleteColumn(int idxView) {
TableColumn tc = getColumnModel().getColumn(idxView);
tableModel.removeColumn(tc.getModelIndex());
removeColumn(tc);
}
private static void buildAndShowGui() {
TableDemo table = new TableDemo();
table.setPreferredScrollableViewportSize(new Dimension(800, 300));
table.setFillsViewportHeight(true);
JScrollPane tableScrollPane = new JScrollPane(table);
JButton buttonAdd = new JButton("Add column");
buttonAdd.addActionListener(e -> table.addColumn());
int gaps = 10;
JPanel panel = new JPanel(new BorderLayout(gaps, gaps));
panel.setBorder(BorderFactory.createEmptyBorder(gaps, gaps, gaps, gaps));
panel.add(buttonAdd, BorderLayout.NORTH);
panel.add(tableScrollPane, BorderLayout.CENTER);
JFrame frame = new JFrame(table.getClass().getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(panel);
frame.pack();
frame.setVisible(true);
new Timer().schedule(new TimerTask() {
#Override
public void run() {
SwingUtilities.invokeLater(() -> table.tableModel.addRow());
}
}, 500, 100);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> buildAndShowGui());
}
}
The problem is that after removal of a column from some modelIndex in the table/data model, the TableColumn#getModelIndex() of some of the columns in the columns model may become shifted by 1. Here is an example, suppose the table has 3 columns and the code below produces 0 1 2:
for (int i = 0; i < getColumnModel().getColumnCount(); i++) {
System.out.print(getColumnModel().getColumn(i).getModelIndex() + " ");
}
Then after removal of the column 1 from both data model and column model, the output of this code becomes: 0 2. Therefore JTable produces ArrayIndexOutOfBoundsException when accessing non-existing column 2 in the data model. This is why removal of the last column didn't generate an error.
The solution is to shift the modelIndex of the columns on the right side of the removed column. This can be done with a custom TableColumnModel:
private static class MyColumnsModel extends DefaultTableColumnModel {
private TableColumn deleteColumn(int idxView) {
if (selectionModel != null) {
selectionModel.removeIndexInterval(idxView, idxView);
}
TableColumn tc = tableColumns.remove(idxView);
tc.removePropertyChangeListener(this);
for (TableColumn tableColumn : tableColumns) {
if (tableColumn.getModelIndex() > tc.getModelIndex()) {
tableColumn.setModelIndex(tableColumn.getModelIndex() - 1);
}
}
return tc;
}
}
And with add / remove methods of the table as following:
private void addColumn() {
TableColumn tc = tableModel.addColumn();
addColumn(tc); // equal to columnsModel.addColumn(tc);
}
private void deleteColumn(int idxView) {
TableColumn tc = columnsModel.deleteColumn(idxView);
tableModel.removeColumn(tc.getModelIndex());
}
Here is the entire fixed code:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
#SuppressWarnings("serial")
public class TableDemo extends JTable {
private static class Column {
private int rowsCounter = 0;
private final String identifier;
public Column(String identifier) {
this.identifier = identifier;
}
private String nextCellValue() {
return (rowsCounter++) + ", id: " + identifier;
}
}
private static class MyTableModel extends DefaultTableModel {
private final List<Column> columns = new ArrayList<>();
private int nextColumnIdentifier = 0;
private void addRow() {
Object[] row = columns.stream().map(Column::nextCellValue).toArray();
insertRow(0, row);
}
private TableColumn addColumn() {
String identifier = String.valueOf(nextColumnIdentifier++);
columns.add(new Column(identifier));
addColumn(identifier);
TableColumn tc = new TableColumn();
tc.setIdentifier(identifier);
tc.setHeaderValue(identifier);
tc.setModelIndex(columns.size() - 1);
return tc;
}
private void removeColumn(int idx) {
columns.remove(idx);
columnIdentifiers.remove(idx);
for (Object row : dataVector) {
((Vector<?>) row).remove(idx);
}
fireTableStructureChanged();
}
}
private static class MyColumnsModel extends DefaultTableColumnModel {
private TableColumn deleteColumn(int idxView) {
if (selectionModel != null) {
selectionModel.removeIndexInterval(idxView, idxView);
}
TableColumn tc = tableColumns.remove(idxView);
tc.removePropertyChangeListener(this);
for (TableColumn tableColumn : tableColumns) {
if (tableColumn.getModelIndex() > tc.getModelIndex()) {
tableColumn.setModelIndex(tableColumn.getModelIndex() - 1);
}
}
return tc;
}
}
private static class HeaderMenu extends JPopupMenu {
private int columnViewIndex;
private HeaderMenu(final TableDemo table) {
JMenuItem item = new JMenuItem("Delete column");
item.addActionListener(e -> table.deleteColumn(columnViewIndex));
add(item);
final MouseAdapter ma = new MouseAdapter() {
boolean dragged = false;
#Override
public void mouseReleased(MouseEvent e) {
if (!dragged && e.getButton() == MouseEvent.BUTTON3) {
final Point p = e.getPoint();
SwingUtilities.invokeLater(() -> {
columnViewIndex = table.columnAtPoint(p);
show(e.getComponent(), p.x, p.y);
});
}
dragged = false;
}
#Override
public void mouseDragged(MouseEvent e) {
dragged = true;
}
};
table.getTableHeader().addMouseListener(ma);
table.getTableHeader().addMouseMotionListener(ma);
}
}
private MyTableModel tableModel = new MyTableModel();
private MyColumnsModel columnsModel = new MyColumnsModel();
private TableDemo() {
new HeaderMenu(this);
setModel(tableModel);
setColumnModel(columnsModel);
setAutoCreateColumnsFromModel(false);
setDefaultEditor(Object.class, null);
}
private void addColumn() {
TableColumn tc = tableModel.addColumn();
addColumn(tc); // equal to columnsModel.addColumn(tc);
}
private void deleteColumn(int idxView) {
TableColumn tc = columnsModel.deleteColumn(idxView);
tableModel.removeColumn(tc.getModelIndex());
}
private static void buildAndShowGui() {
TableDemo table = new TableDemo();
table.setPreferredScrollableViewportSize(new Dimension(800, 300));
table.setFillsViewportHeight(true);
JScrollPane tableScrollPane = new JScrollPane(table);
JButton buttonAdd = new JButton("Add column");
buttonAdd.addActionListener(e -> table.addColumn());
int gaps = 10;
JPanel panel = new JPanel(new BorderLayout(gaps, gaps));
panel.setBorder(BorderFactory.createEmptyBorder(gaps, gaps, gaps, gaps));
panel.add(buttonAdd, BorderLayout.NORTH);
panel.add(tableScrollPane, BorderLayout.CENTER);
JFrame frame = new JFrame(table.getClass().getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(panel);
frame.pack();
frame.setVisible(true);
new Timer().schedule(new TimerTask() {
#Override
public void run() {
SwingUtilities.invokeLater(() -> table.tableModel.addRow());
}
}, 500, 100);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> buildAndShowGui());
}
}
Is there any way to disable selection of multiple columns for a Swing JTable? I've disabled selection all together in the "Tid" column by overriding the selection intervals of the selection model:
myTable.getColumnModel().setSelectionModel(new DefaultListSelectionModel() {
private boolean isSelectable(int index0, int index1) {
return index1 != 0;
}
#Override
public void setSelectionInterval(int index0, int index1) {
if(isSelectable(index0, index1)) {
super.setSelectionInterval(index0, index1);
}
}
#Override
public void addSelectionInterval(int index0, int index1) {
if(isSelectable(index0, index1)) {
super.addSelectionInterval(index0, index1);
}
}
});
And my guess is that one can also disallow the selection of multiple columns by overriding methods in the selection model. But I can't really figure out how to accomplish that.
Allowed selection
Disallowed selection
First get the TableColumnModel from the JTable
TableColumnModel columnModel = table.getColumnModel();
Next, get the LstSeletionModel for the TableColumnModel
ListSelectionModel selectionModel = columnModel.getSelectionModel();
With this, you could set the selectionMode that the model will use, for example
selectionModel.setSelectionModel(ListSelectionModel.SINGLE_SELECTION)
See the JavaDocs for ListSelectionModel and TableColumnModel for more details
Runnable example....
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
DefaultTableModel model = new DefaultTableModel(0, 10);
for (int row = 0; row < 10; row++) {
String[] data = new String[10];
for (int col = 0; col < 10; col++) {
data[col] = row + "x" + col;
}
model.addRow(data);
}
JTable table = new JTable(model);
table.setColumnSelectionAllowed(true);
table.setRowSelectionAllowed(true);
table.getColumnModel().getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
add(new JScrollPane(table));
}
}
}
Actually, it was a simple enough addition to my already existing overrides that was needed.
#Override
public void setSelectionInterval(int index0, int index1) {
if (isSelectable(index0, index1)) {
if (index0==index1) { //The if condition needed.
super.setSelectionInterval(index0, index1);
}
}
}
I realised upon reviewing the JavaDoc and the DefaultListSelectionModel that the index0 and index1 were just what I was looking for - the column span. So by doing the call to the superclass if and only if the two column indices are equal, selection of multiple columns is not possible.
I am using AbstractTableModel.
I have used the code provided from this website(http://www.javalobby.org/articles/jtable/?source=archives) to auto generate new rows for the JTable when JTable reaches on last row.
Its working fine but I am not able to update the column names anymore when user clicks a button. So I have to remove auto generate row feature to update column names.
As far as I understand the code from the website, Its using a hidden column as a trigger to create new row. InteractiveTableModelListener is disabling something which I don't understand how to change/edit it?
How could I solve this?
Thanks for your time!!
This is a simple example of how it might be possible to provide "auto add at end" functionality
Basically, what this does is maps custom key bindings to provide customised key board navigation, in this example, this includes Tab, Shift+Tab, Enter, Left, Right, Up, Down which allows the ability to determine what should happen when these keys are pressed.
Mostly, Tab, Enter, Left, Up, Down are most relevant, I just added the others in as an example and to maintain consistency.
A feature that might be slightly more useful would be to provide a "row factory" which would be capable of producing the required data for a new blank row, instead of just inserting a bunch of nulls, or at least I would find it useful.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableModel;
public class TestTable {
public static void main(String[] args) {
new TestTable();
}
public TestTable() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
MutableDefaultTableModel model = new MutableDefaultTableModel(4, 4);
JTable table = new JTable(model);
KeyStroke tabKey = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0);
KeyStroke shiftTabKey = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK);
KeyStroke enterKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
KeyStroke arrowLeftKey = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0);
KeyStroke arrowRightKey = KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0);
KeyStroke arrowDownKey = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0);
KeyStroke arrowUpKey = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0);
InputMap im = table.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
im.put(tabKey, "nextCell");
im.put(enterKey, "nextCell");
im.put(arrowRightKey, "nextCell");
im.put(shiftTabKey, "previousCell");
im.put(arrowLeftKey, "previousCell");
im.put(arrowDownKey, "nextRow");
im.put(arrowUpKey, "previousRow");
ActionMap am = table.getActionMap();
am.put("nextCell", new NextCellAction(table, model));
am.put("previousCell", new PreviousCellAction(table, model));
am.put("nextRow", new NextRowAction(table, model));
am.put("previousRow", new PreviousRowAction(table, model));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MutableDefaultTableModel extends DefaultTableModel implements MutableTableModel {
public MutableDefaultTableModel(int rows, int cols) {
super(rows, cols);
}
#Override
public void insertNewRow(int row) {
Object[] rowData = new Object[getColumnCount()];
if (row < getRowCount()) {
insertRow(row, rowData);
} else {
addRow(rowData);
}
}
}
public interface MutableTableModel extends TableModel {
public void insertNewRow(int row);
}
public abstract class AbstractTableAction extends AbstractAction {
private JTable table;
private MutableTableModel model;
private boolean forceStopEditing;
public AbstractTableAction(JTable table, MutableTableModel model) {
this.table = table;
this.model = model;
}
public MutableTableModel getModel() {
return model;
}
public JTable getTable() {
return table;
}
public boolean isForceStopEditing() {
return forceStopEditing;
}
public void setForceStopEditing(boolean forceStopEditing) {
this.forceStopEditing = forceStopEditing;
}
public void stopCellEditing() {
TableCellEditor editor = getTable().getCellEditor();
if (editor != null) {
if (!editor.stopCellEditing() && isForceStopEditing()) {
editor.cancelCellEditing();
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
JTable table = getTable();
int row = table.getSelectedRow();
int col = table.getSelectedColumn();
int rowCount = table.getRowCount();
int colCount = table.getColumnCount();
stopCellEditing();
int cell[] = updateCellCoordinates(row, col);
row = cell[0];
col = cell[1];
if (col < 0) {
col = colCount - 1;
row--;
} else if (col >= colCount) {
col = 0;
row++;
}
if (row < 0) {
row = 0;
} else if (row >= rowCount) {
getModel().insertNewRow(row);
}
table.setRowSelectionInterval(row, row);
table.setColumnSelectionInterval(col, col);
}
protected abstract int[] updateCellCoordinates(int row, int col);
}
public class NextCellAction extends AbstractTableAction {
public NextCellAction(JTable table, MutableTableModel model) {
super(table, model);
}
#Override
protected int[] updateCellCoordinates(int row, int col) {
return new int[]{row, ++col};
}
}
public class PreviousCellAction extends AbstractTableAction {
public PreviousCellAction(JTable table, MutableTableModel model) {
super(table, model);
}
#Override
protected int[] updateCellCoordinates(int row, int col) {
return new int[]{row, --col};
}
}
public class NextRowAction extends AbstractTableAction {
public NextRowAction(JTable table, MutableTableModel model) {
super(table, model);
}
#Override
protected int[] updateCellCoordinates(int row, int col) {
return new int[]{++row, col};
}
}
public class PreviousRowAction extends AbstractTableAction {
public PreviousRowAction(JTable table, MutableTableModel model) {
super(table, model);
}
#Override
protected int[] updateCellCoordinates(int row, int col) {
return new int[]{--row, col};
}
}
}
I have following code:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;
import javax.swing.table.DefaultTableModel;
public class NewClass1 extends JFrame {
private JTable table;
private JScrollPane scrollPane;
private DefaultTableModel defaultTableModel;
public NewClass1() {
setLocationByPlatform(true);
setLayout(new BorderLayout());
setPreferredSize(new Dimension(600, 400));
setTitle("Table Issues");
setDefaultCloseOperation(EXIT_ON_CLOSE);
createTableModel();
table = new JTable(defaultTableModel);
scrollPane = new JScrollPane(table);
getContentPane().add(scrollPane, BorderLayout.CENTER);
pack();
}
private void createTableModel() {
Vector cols = new Vector();
cols.add("A");
Vector rows = new Vector();
for (int i = 0; i < 50; i++) {
Vector row = new Vector();
row.add((i + 1) + "");
rows.add(row);
}
defaultTableModel = new DefaultTableModel(rows, cols) {
Class[] types = new Class[]{
String.class
};
#Override
public Class getColumnClass(int columnIndex) {
return types[columnIndex];
}
#Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(new NimbusLookAndFeel());
} catch (Exception e) {
}
final NewClass1 nc = new NewClass1();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
nc.setVisible(true);
}
});
while (true) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int row = (int) (Math.random() * 50);
int move = (int) (Math.random() * 50);
nc.defaultTableModel.moveRow(row, row, move);
}
});
try{
Thread.sleep(1000);
}catch(Exception e){
}
}
}
}
Please run the above code and select row.
My problem is with row movement, row selection is not moving. It is staying at fixed position. Suppose I selected row with column value 25, selected row must be of column value 25 after row movements.
Please help me on this.
My real problem is, user will select row and clicks menu to perform action, meanwhile other threads may have moved rows, and performed action will be on other row than actual one.
The easiest way is to remember the selected row somewhere outside of the ListSelectionModel and adjust the selection whenever the TableModel changes. For example you could do this:
public class NewClass1 extends JFrame {
private JTable table;
private DefaultTableModel defaultTableModel;
private JScrollPane scrollPane;
private class SelectionHelper implements ListSelectionListener, TableModelListener {
private Object selectedRow;
#Override
public void valueChanged(ListSelectionEvent event) {
if (!event.getValueIsAdjusting()) return;
int selectedIndex = table.getSelectedRow();
if (selectedIndex >= 0) {
selectedRow = defaultTableModel.getDataVector().get(selectedIndex);
} else {
selectedRow = null;
}
}
#Override
public void tableChanged(TableModelEvent event) {
if (selectedRow == null) return;
int selectedIndex = defaultTableModel.getDataVector().indexOf(selectedRow);
table.getSelectionModel().setSelectionInterval(selectedIndex, selectedIndex);
}
}
public NewClass1() {
// ...
createTableModel();
table = new JTable(defaultTableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
SelectionHelper helper = new SelectionHelper();
table.getModel().addTableModelListener(helper);
table.getSelectionModel().addListSelectionListener(helper);
// ...
}
// ...
}
Note however, that you should adjust this code for production use, for example in regards to thread safety or portability (using the table and defaultTableModel attributes in the inner class is bad style).
I am running into a problem that has been discussed on here before: getting a JScrollPane containing a JTable to display the horizontal scrollbar as I desire. HERE is a post I tried following, but for some reason it does not seem to work with my situation (I think it is because I've set one of my columns to have a fixed width.
Here is a SSCCE:
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
#SuppressWarnings("serial")
public class TableScrollTest extends JFrame
{
public TableScrollTest() {
DefaultTableModel model = new DefaultTableModel(new Object[]{"key", "value"},0);
model.addRow(new Object[]{"short", "blah"});
model.addRow(new Object[]{"long", "blah blah blah blah blah blah blah"});
JTable table = new JTable(model) {
public boolean getScrollableTracksViewportWidth() {
return getPreferredSize().width < getParent().getWidth();
}
};
table.getColumn("key").setPreferredWidth(60);
table.getColumn("key").setMinWidth(60);
table.getColumn("key").setMaxWidth(60);
table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
JScrollPane scrollPane = new JScrollPane( table );
getContentPane().add( scrollPane );
}
public static void main(String[] args) {
TableScrollTest frame = new TableScrollTest();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setSize(200, 200);
frame.setResizable(false);
frame.setVisible(true);
}
}
In short, I have a two column table for displaying key/value pairs. The container holding the table is of fixed width, and the first column of the table is also of fixed width (since I know how long all the key names will be). The second column will contain values of varying string length. A horizontal scrollbar should only appear when there are values present which are too long to fit in the width allotted for the column.
Because the second value above has such a long length, the scrollbar should be visible. However, it's not, and everything I have tried has only succeeded in getting it visible always, which is not what I want... I only want it visible if "long" values are present. It seems like the getScrollableTracksViewportWidth() method being overridden in the table constructor checks what the preferred width of the table is... so somehow I need to direct the table to prefer a larger width based on the contents of that second column only... but I'm stumped.
Any ideas?
This is a hackey solution
Basically, what it does is calculates the "preferred" width of all the columns based on the values from all the rows.
It takes into consideration changes to the model as well as changes to the parent container.
Once it's done, it checks to see if the "preferred" width is greater or less than the available space and sets the trackViewportWidth variable accordingly.
You can add in checks for fixed columns (I've not bothered) which would make the process "slightly" faster, but this is going to suffer as you add more columns and rows to the table, as each update is going to require a walk of the entire model.
You could put some kind of cache in, but right about then, I'd be considering fixed width columns ;)
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.event.TableModelEvent;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
public class TableScrollTest extends JFrame {
public TableScrollTest() {
DefaultTableModel model = new DefaultTableModel(new Object[]{"key", "value"}, 0);
model.addRow(new Object[]{"short", "blah"});
model.addRow(new Object[]{"long", "blah blah blah blah blah blah blah"});
JTable table = new JTable(model) {
private boolean trackViewportWidth = false;
private boolean inited = false;
private boolean ignoreUpdates = false;
#Override
protected void initializeLocalVars() {
super.initializeLocalVars();
inited = true;
updateColumnWidth();
}
#Override
public void addNotify() {
super.addNotify();
updateColumnWidth();
getParent().addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
invalidate();
}
});
}
#Override
public void doLayout() {
super.doLayout();
if (!ignoreUpdates) {
updateColumnWidth();
}
ignoreUpdates = false;
}
protected void updateColumnWidth() {
if (getParent() != null) {
int width = 0;
for (int col = 0; col < getColumnCount(); col++) {
int colWidth = 0;
for (int row = 0; row < getRowCount(); row++) {
int prefWidth = getCellRenderer(row, col).
getTableCellRendererComponent(this, getValueAt(row, col), false, false, row, col).
getPreferredSize().width;
colWidth = Math.max(colWidth, prefWidth + getIntercellSpacing().width);
}
TableColumn tc = getColumnModel().getColumn(convertColumnIndexToModel(col));
tc.setPreferredWidth(colWidth);
width += colWidth;
}
Container parent = getParent();
if (parent instanceof JViewport) {
parent = parent.getParent();
}
trackViewportWidth = width < parent.getWidth();
}
}
#Override
public void tableChanged(TableModelEvent e) {
super.tableChanged(e);
if (inited) {
updateColumnWidth();
}
}
public boolean getScrollableTracksViewportWidth() {
return trackViewportWidth;
}
#Override
protected TableColumnModel createDefaultColumnModel() {
TableColumnModel model = super.createDefaultColumnModel();
model.addColumnModelListener(new TableColumnModelListener() {
#Override
public void columnAdded(TableColumnModelEvent e) {
}
#Override
public void columnRemoved(TableColumnModelEvent e) {
}
#Override
public void columnMoved(TableColumnModelEvent e) {
if (!ignoreUpdates) {
ignoreUpdates = true;
updateColumnWidth();
}
}
#Override
public void columnMarginChanged(ChangeEvent e) {
if (!ignoreUpdates) {
ignoreUpdates = true;
updateColumnWidth();
}
}
#Override
public void columnSelectionChanged(ListSelectionEvent e) {
}
});
return model;
}
};
table.getColumn("key").setPreferredWidth(60);
// table.getColumn("key").setMinWidth(60);
// table.getColumn("key").setMaxWidth(60);
// table.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
JScrollPane scrollPane = new JScrollPane(table);
getContentPane().add(scrollPane);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
TableScrollTest frame = new TableScrollTest();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.pack();
frame.setSize(200, 200);
frame.setResizable(true);
frame.setVisible(true);
}
});
}
}
It seems then that my goal is to force the cell renderer to automatically make cells fit long strings
This isn't the job of the renderer. You must manually set the width of the columns.
See Table Column Adjuster for one way to do this.
I won't necessarily accept this answer if something smarter is posted, but here is a solution I figured out on my own based on comments posted so far and THIS post. The only catch is that the width that I was coming up with was always ~1 pixel too short (in some look-and-feel's it was ok), so I added the line near the end width += 5 which seems to make it work ok, but it feels hacky to me. Would welcome any comments on this approach.
import java.awt.Component;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
#SuppressWarnings("serial")
public class TableScrollTest extends JFrame {
public TableScrollTest() {
DefaultTableModel model = new DefaultTableModel(new Object[]{"key", "value"},0);
model.addRow(new Object[]{"short", "blah"});
model.addRow(new Object[]{"long", "blah blah blah blah blah blah blah"});
JTable table = new JTable(model);
table.getColumn("key").setPreferredWidth(60);
table.getColumn("key").setMinWidth(60);
table.getColumn("key").setMaxWidth(60);
table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
int width = 0;
for (int row = 0; row < table.getRowCount(); row++) {
TableCellRenderer renderer = table.getCellRenderer(row, 1);
Component comp = table.prepareRenderer(renderer, row, 1);
width = Math.max (comp.getPreferredSize().width, width);
}
width += 5;
table.getColumn("value").setPreferredWidth(width);
table.getColumn("value").setMinWidth(width);
table.getColumn("value").setMaxWidth(width);
JScrollPane scrollPane = new JScrollPane( table );
getContentPane().add( scrollPane );
}
public static void main(String[] args) {
TableScrollTest frame = new TableScrollTest();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setSize(200, 200);
frame.setResizable(false);
frame.setVisible(true);
}
}