I'm removing all rows of a JTable using this:
myTableModel.getDataVector().removeAllElements();
myTableModel.setRowCount(0);
But after deletion its footprint remains! please check this Screenshot:
This only happens when I delete all rows, and if there be even one row there will be bo problem!
Why this happens? how can I fix it?
Thanks
Using a DefaultTableModel, the only option you have is to use the functionality provided by the model, removeRow
removeRow will fire the appropriate events required to tell the JTable that it needs to update itself. The table has been optimised in such away as to improve the painting process
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
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) {
}
DefaultTableModel model = new DefaultTableModel();
JTable table = new JTable(model);
for (int index = 0; index < 10; index++) {
model.addColumn(index);
}
for (int row = 0; row < 1000; row++) {
String[] values = new String[10];
for (int col = 0; col < 10; col++) {
values[col] = row + "x" + col;
}
model.addRow(values);
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.add(new JButton(new DeleteRowsFromDefaultModel(model)), BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class DeleteRowsFromDefaultModel extends AbstractAction {
private final DefaultTableModel model;
public DeleteRowsFromDefaultModel(DefaultTableModel model) {
this.model = model;
putValue(NAME, "Delete All");
}
#Override
public void actionPerformed(ActionEvent e) {
model.setRowCount(0);
}
}
}
If you're really worried about performance, you will need to create your own model and create your own clear method that fires the appropriate events.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableModel;
public class TestTable1 {
public static void main(String[] args) {
new TestTable1();
}
public TestTable1() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
MyTabelModel model = new MyTabelModel();
JTable table = new JTable(model);
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.add(new JButton(new DeleteRowsFromMyModel(model)), BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class DeleteRowsFromMyModel extends AbstractAction {
private final MyTabelModel model;
public DeleteRowsFromMyModel(MyTabelModel model) {
this.model = model;
putValue(NAME, "Delete All");
}
#Override
public void actionPerformed(ActionEvent e) {
model.clear();
}
}
public class MyTabelModel extends AbstractTableModel {
private List<String[]> rows;
public MyTabelModel() {
rows = new ArrayList<>(1000);
for (int row = 0; row < 1000; row++) {
String[] values = new String[10];
for (int col = 0; col < 10; col++) {
values[col] = row + "x" + col;
}
rows.add(values);
}
}
#Override
public int getRowCount() {
return rows.size();
}
#Override
public int getColumnCount() {
return 10;
}
#Override
public String getColumnName(int column) {
return String.valueOf(column);
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
String[] row = rows.get(rowIndex);
return row[columnIndex];
}
public void clear() {
int old = getRowCount();
if (old > 0) {
rows.clear();
fireTableRowsDeleted(0, old - 1);
}
}
}
}
Related
In this code, perfs is a String[] and model is a DefaultTableModel associated to a JTable.
for(int i =0; i<100000; i++){
model.addRow(perfs);
}
I would like the rows to appear as they are added, but they only appear once the loop is over. Could this have something to do with te fact the table is in a JScrollPane ?
Here's the full code in case it might be useful
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class gwy implements ActionListener{
private JFrame frame;
private JPanel panel;
private JButton button;
String[] nomsColonnes = {"INSTANCE","Solvabilité","Profondeur(ms)","Largeur(ms)", "Heur. basique(ms)", "Heur. opérations variées", "Heur. distances au carré"};
private DefaultTableModel model = new DefaultTableModel(nomsColonnes, 0);
private JTable tableResultats = new JTable(model);
JScrollPane scrollPane;
public gwy(){
frame = new JFrame();
button = new JButton("Résoudre");
button.addActionListener(this);
scrollPane = new JScrollPane(tableResultats);
tableResultats.setFillsViewportHeight(true);
panel = new JPanel();
panel.setSize(70,50);
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10,10));
panel.setLayout(new BoxLayout(panel,BoxLayout.PAGE_AXIS));
panel.add(button);
panel.add(scrollPane);
frame.add(panel, BorderLayout.CENTER);
//frame.setSize(500,500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("GUI");
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args){
new gwy();
}
#Override
public void actionPerformed(ActionEvent e) {
for(int i =0; i<10000000; i++){
model.addRow(nomsColonnes);
}
}
}
The problem is, Swing is single threaded. This means that until your loops completes, it's blocking the Event Dispatching Thread from processing any new events, which would otherwise update the UI.
Swing is also not thread safe, meaning you should not update the UI, or any state the UI depends on, from out the context of the Event Dispatching Thread.
See Concurrency in Swing for more details.
A common way to achieve this would be to use a SwingWorker which can be used to create new rows in a seperate thread, but which can sync the updates back to the Event Dispatching Thread safely.
See Worker Threads and SwingWorker for more details
Runnable example
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private MyTableModel model;
public TestPane() {
setLayout(new BorderLayout());
model = new MyTableModel();
JTable table = new JTable(model);
model.addTableModelListener(new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.INSERT) {
int row = e.getFirstRow();
table.scrollRectToVisible(table.getCellRect(row, 0, true));
}
}
});
add(new JScrollPane(table));
JButton populate = new JButton("Populate");
populate.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
populate.setEnabled(false);
PopulateWorker worker = new PopulateWorker(model);
worker.execute();
}
});
add(populate, BorderLayout.SOUTH);
}
}
public class PopulateWorker extends SwingWorker<Void, MyRowData> {
private MyTableModel model;
public PopulateWorker(MyTableModel model) {
this.model = model;
}
public MyTableModel getModel() {
return model;
}
#Override
protected void process(List<MyRowData> chunks) {
for (MyRowData row : chunks) {
model.addRow(row);
}
}
#Override
protected Void doInBackground() throws Exception {
for (int index = 0; index < 10_000; index++) {
publish(new MyRowData(Integer.toString(index)));
// Decrease this to make it faster
Thread.sleep(125);
}
return null;
}
}
public class MyRowData {
private String identifer;
public MyRowData(String identifer) {
this.identifer = identifer;
}
public String getIdentifer() {
return identifer;
}
}
public class MyTableModel extends AbstractTableModel {
private String[] columnNames = new String[]{"INSTANCE"};
private List<MyRowData> data = new ArrayList<>(32);
#Override
public int getRowCount() {
return data.size();
}
#Override
public int getColumnCount() {
return columnNames.length;
}
#Override
public String getColumnName(int column) {
return columnNames[column];
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
MyRowData row = data.get(rowIndex);
switch (columnIndex) {
case 0: return row.getIdentifer();
}
return null;
}
public void addRow(MyRowData row) {
data.add(row);
fireTableRowsInserted(data.size() - 1, data.size() - 1);
}
}
}
nb:: There is a Thread.sleep inside the doInBackground method of the SwingWorker, this is very important. If you remove it, the worker will complete before the UI get's updated. Instead, you can modify the Thread.sleep to increase or decrease the speed at which the updates occur. Just beware, if it's low enough, you might end up with multiple rows appearing at the same time, depending on the thread load.
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 have JFrame with a JTable and JComboBox with choices
"Leave Code"
"Leave Description"
I need to filter the table data with respect to leave code when leave code is selected in the combo box and filter with respect to leavedesc when leave description is selected in the combo box.
This is the function that I've used to filter the data:
private void filtervalue(String filterString) {
TableModel model = tableLeave.getModel();
final TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(model);
tableLeave.setRowSorter(sorter);
if (filterString.length() == 0) {
sorter.setRowFilter(null);
} else {
sorter.setRowFilter(RowFilter.regexFilter("(?i)" + filterString));
}
}
But the problem is it will sort by leavedesc as well as leavecode, so no effect by the combo box. Please help me to implement column based filter.
Without a runnable example, I can't be 100% sure, but I would guess that setting the RowSorter each time you change the filter text is causing the table to be re-sorted.
Instead, set a TableRowSorter when you create the JTable and simply update the filter, for example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.RowFilter;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
public class TableFilter {
public static void main(String[] args) {
new TableFilter();
}
private JTable table;
private JComboBox filterBy;
private JTextField filterText;
public TableFilter() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
DefaultTableModel model = new DefaultTableModel(new Object[]{"Code", "Description"}, 0);
model.addRow(new Object[]{"A001", "Holidays"});
model.addRow(new Object[]{"B001", "Sick"});
model.addRow(new Object[]{"A002", "Zombitse"});
model.addRow(new Object[]{"C001", "Crazy bin"});
model.addRow(new Object[]{"C002", "Postal"});
model.addRow(new Object[]{"D002", "Job Interview"});
model.addRow(new Object[]{"D004", "it's sunny outside"});
table = new JTable(model);
table.setRowSorter(new TableRowSorter<TableModel>(model));
JPanel filterPane = new JPanel(new GridBagLayout());
filterBy = new JComboBox(new Object[]{"Nothing", "Code", "Description"});
filterText = new JTextField(20);
filterPane.add(filterBy);
filterPane.add(filterText);
filterBy.setSelectedIndex(0);
filterBy.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
updateFilter();
}
});
filterText.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
updateFilter();
}
#Override
public void removeUpdate(DocumentEvent e) {
updateFilter();
}
#Override
public void changedUpdate(DocumentEvent e) {
updateFilter();
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(filterPane, BorderLayout.NORTH);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected void updateFilter() {
Object selected = filterBy.getSelectedItem();
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) table.getRowSorter();
String text = "(?i)" + filterText.getText();
if ("Nothing".equals(selected)) {
sorter.setRowFilter(null);
} else {
int col = -1;
if ("Code".equals(selected)) {
col = 0;
} else if ("Description".equals(selected)) {
col = 1;
}
sorter.setRowFilter(RowFilter.regexFilter(text, col));
}
}
}
This example demonstrates real time updates, so as you type, the table will be filtered...
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());
}
}
}