Deleting row from JTable after valueChanged event is triggered - java

I'm using ListSelectionListener to update my JTextField (countryTxt) from selected row.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
public class App {
JFrame frame = new JFrame();
JTable table = new JTable();
DefaultTableModel model = new DefaultTableModel(new Object[][] {},
new String[] { "Country", "City", "Street" });
JButton button = new JButton("Remove");
JTextField countryTxt = new JTextField();
int row;
public App() {
table.setModel(model);
data();
table.getSelectionModel().addListSelectionListener(
new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
row = table.getSelectedRow();
countryTxt.setText((String) model
.getValueAt(row, 0));
}
}
});
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
model.removeRow(row);
}
});
frame.add(countryTxt,BorderLayout.NORTH);
frame.add(new JScrollPane(table), BorderLayout.CENTER);
frame.add(button, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
frame.setLocationRelativeTo(null);
}
public void data() {
model.addRow(new String[] { "USA", "New York", "First street" });
model.addRow(new String[] { "Russia", "Moscow", "Second street" });
model.addRow(new String[] { "Japan", "Osaka", "Osaka street" });
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new App();
}
});
}
}
But when I select a row and click a button it trows me and ArrayIndexOutOfBounds exception. When I don't select a row in my table and click a button everything works fine. Obviously I can delete a row when valueChanged event is not triggered. So my question is: How to delete a row after valueChanged event is triggered. Thanks in advance.

Have a look at the javadoc of the getLeadSelectionIndex() method
Return the second index argument from the most recent call to setSelectionInterval(), addSelectionInterval() or removeSelectionInterval()
This is not what you expect. You better use the JTable#getSelectedRow() which of course still requires you to check whether it is different from -1 .

A few observations:
Selecting a row via keyboard or mouse updates the countryTxt field correctly.
You can use Control>-Tab to tab out of the table and back to your panel.
Don't use setBounds(); do use pack().
I tested your example without MigLayout, but I don't think that's relevant to your findings.

I had to track down a similar problem involving list removal a while ago. The main issue here is that the button listener's call to model.removeRow(row) was sending a valueChanged event to the model's selection listener, which would then attempt to update the text field using a nonexistent selection (i.e. a list index of -1). I've made these fixes to your code, and the relevant sections are marked with comments. This code allows items to be selected/deleted without throwing an exception.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
public class App {
JFrame frame = new JFrame();
DefaultTableModel model = new DefaultTableModel(new Object[][] {},
new String[] { "Country", "City", "Street" });
JTable table = new JTable(model);
JButton button = new JButton("Remove");
JTextField countryTxt = new JTextField();
public App() {
data();
table.getSelectionModel().addListSelectionListener(
new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
// get the current selected row
int i = table.getSelectedRow();
// if there is a selected row, update the text field
if(i >= 0) {
countryTxt.setText((String) model
.getValueAt(i, 0));
}
}
}
});
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
// get the current selected row
int i = table.getSelectedRow();
// if there's no selection, but there are some rows,
// we'll just delete the first row
if(i < 0 && model.getRowCount() > 0) {
i = 0;
}
// if we have a valid row to delete, do the deletion
if(i >= 0) {
countryTxt.setText("");
model.removeRow(i);
table.revalidate();
}
}
});
frame.add(countryTxt,BorderLayout.NORTH);
frame.add(new JScrollPane(table), BorderLayout.CENTER);
frame.add(button, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
frame.setLocationRelativeTo(null);
}
public void data() {
model.addRow(new String[] { "USA", "New York", "First street" });
model.addRow(new String[] { "Russia", "Moscow", "Second street" });
model.addRow(new String[] { "Japan", "Osaka", "Osaka street" });
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new App();
}
});
}
}

Related

JTable changing column order and then editing cell raising bug

I have a strange problem with JTable.
What I have
A two-column JTable. And the second column uses JComboBox as a cell editor.
Problem
When I edit that JComboBox cell with the origin column order it works fine. But When I change the column first, such as switching that 2 column(JComboBox column becomes fist), then I edited the JComboBox cell raising Bug. I must click that cell twice to fire the editing event, otherwise, JComboBox isn't activated.
What I tried
I tried to use TableCellListener, but it raises the exception, such as column index is -1;
My SSCCE:
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import javax.swing.AbstractAction;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
import java.awt.GridLayout;
import javax.swing.JTable;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JScrollPane;
public class MainTable extends JDialog {
private static final long serialVersionUID = 156332386872772726L;
private final JPanel contentPanel = new JPanel();
private DefaultTableModel tableModel;
private JTable table;
public static void main(String[] args) {
try {
MainTable dialog = new MainTable();
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
public MainTable() {
setBounds(100, 100, 450, 300);
getContentPane().setLayout(new BorderLayout());
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
getContentPane().add(contentPanel, BorderLayout.CENTER);
contentPanel.setLayout(new GridLayout(1, 0, 0, 0));
{
JScrollPane scrollPane = new JScrollPane();
contentPanel.add(scrollPane);
{
table = new JTable();
table.setAutoCreateRowSorter(true);
tableModel = new DefaultTableModel(new String[]{"first","second"},0);
table.setModel(tableModel);
{
//set comboBox to table
JComboBox<String> comboBox = new JComboBox<String>();
comboBox.addItem("Input");
comboBox.addItem("Output");
table.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(comboBox));
table.getColumnModel().addColumnModelListener(new TableColumnModelListener(){
#Override
public void columnAdded(TableColumnModelEvent arg0) {
}
#Override
public void columnMarginChanged(ChangeEvent arg0) {
}
#Override
public void columnMoved(TableColumnModelEvent arg0) {
table.requestFocusInWindow();
comboBox.setFocusable(true);
comboBox.requestFocus();
comboBox.grabFocus();
comboBox.requestFocusInWindow();//try to get focus, not worked.
}
#Override
public void columnRemoved(TableColumnModelEvent arg0) {
}
#Override
public void columnSelectionChanged(ListSelectionEvent arg0) {
}
});
}
// TableCellListener tcl = new TableCellListener(table, new AbstractAction(){
// #Override
// public void actionPerformed(ActionEvent e) {
// }
// });
tableModel.addRow(new Object[]{"1","Input"});
tableModel.addRow(new Object[]{"2","Output"});//init. add 2 rows
scrollPane.setViewportView(table);
}
}
{
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
getContentPane().add(buttonPane, BorderLayout.SOUTH);
{
JButton okButton = new JButton("Add");
okButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
int row = tableModel.getRowCount();
tableModel.addRow(new Object[]{row,"Input"});//click button to add a row
}
});
buttonPane.add(okButton);
getRootPane().setDefaultButton(okButton);
}
}
}
}
I must click that cell twice to fire the editing event, otherwise, JComboBox isn't activated.
This only happens the first time you try to edit the column after changing column order. It will also happens if you switch the column back to its original position.
It is like the table doesn't have focus so the first mouse click is giving the table focus.
So you could try to add a TableColumnModelListener to the TableColumModel. Then in the columnMoved event you can try using table.requestFocusInWindow()

SetCaretPosition in JTable

How can I set caret position in a JTable?
I know JTextField has setCaretPosition(n) function. But I cannot access JTextField in the JTable.
I would like the Table text caret position equal text length. It is possible to mouseclick event but it should be normal position.
My code:
public class TableTest extends javax.swing.JFrame
{
public TableTest()
{
javax.swing.JTable jTable1 = new javax.swing.JTable();
jTable1.setModel(new javax.swing.table.DefaultTableModel(
new Object[][]
{
{
"This is too long text!", "This is too long text!",
},
{
"This is too long text!", "This is too long text!",
}
},
new String[]
{
"Title 1", "Title 2",
}
));
add(jTable1);
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public static void main(String args[])
{
new TableTest().setVisible(true);
}
}
This table cells are shown:
[This is too...] but it should be [..long text!]
This table cells are shown: [This is too...] but it should be [..long text!]
This the renderer that displays the text in a cell. The default renderer is a JLabel and it will display ... when the text is truncated. If you want the ... at the start of the cell then you need to create a custom renderer.
Check out the Left Dot Renderer for a renderer that does this.
The default "startEditing" action, suggested here, is provided by BasicTableUI. It typically selects all the text, which you want to avoid. You can invoke setCaretPosition() in a custom Action bound to your preferred keystroke. The example below uses the name "myEditing" and the Space key.
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.table.DefaultTableModel;
import javax.swing.text.JTextComponent;
/**
* #see https://stackoverflow.com/a/38051001/230513
*/
public class TableTest {
private void display() {
JFrame f = new JFrame("TableTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DefaultTableModel model = new DefaultTableModel(
new String[][]{
{"This is too long text!", "This is too long text!",},
{"This is too long text!", "This is too long text!",}
},
new String[]{
"Title 1", "Title 2",}
) {
#Override
public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
};
JTable table = new JTable(model) {
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(320, 160);
}
};
table.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "myEditing");
table.getActionMap().put("myEditing", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
table.editCellAt(table.getSelectedRow(), table.getSelectedColumn());
Component editor = table.getEditorComponent();
if (editor != null) {
editor.requestFocus();
if (editor instanceof JTextComponent) {
JTextComponent jtc = (JTextComponent) editor;
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
jtc.setCaretPosition(jtc.getDocument().getLength());
}
});
}
}
}
});
table.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
f.add(new JScrollPane(table));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new TableTest()::display);
}
}
For reference, a similar approach for a mouse event is shown in this previous version.

JTable search and deleting rows

I am learning how to use JTable and I have issues with both my search and delete function.
From my codes, if I checked row 1 and row 3 and press the delete button, it will remove perfectly.
If I enter J in my search textfield and press the search button, John and Jane will be displayed
and now if I check the 2 rows and press the delete button then press the search button again,
John and Jane will be removed including Kate and Ann.
Please run the codes below if you guys still don't understand what I am saying.
follow the steps that I mentioned and you will see what's wrong.
help me. Thanks
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
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.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
public class TableExample extends JFrame {
private static final long serialVersionUID = 1L;
protected static final boolean DEBUG = false;
private JTable table;
TableModel model;
JPanel panel = new JPanel(new BorderLayout());
JButton button = new JButton("Delete");
JTextField searchTextField = new JTextField(15);
JButton searchBtn = new JButton("Search");
JPanel flowLayoutPanel = new JPanel(new FlowLayout());
TableRowSorter<TableModel> sorter;
public TableExample() {
String[] columnNames = {"Employer", "Company", "Salary", "Boolean"};
Object[][] data = {
{"Kate", "20",new Integer(5000), new Boolean(false)},
{"John", "35", new Integer(3000), new Boolean(false)},
{"Ann", "20", new Integer(4000), new Boolean(false)},
{"Jane", "12", new Integer(4000), new Boolean(false)},
{"May", "42", new Integer(4500), new Boolean(false)}
};
model = new DefaultTableModel(data, columnNames) {
#Override
public Class getColumnClass(int column) {
switch(column) {
case 0:
case 1: return String.class;
case 2: return Integer.class;
case 3: return Boolean.class;
default: return Object.class;
}
}
};
table = new JTable(model) {
public boolean isCellEditable(int row, int col) {
return true;
}
};
sorter = new TableRowSorter<TableModel>(model);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
table.setRowSorter(sorter);
table.setGridColor(Color.black);
JScrollPane scrollPane = new JScrollPane(table);
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
for(int row = 0; row < table.getRowCount(); ++row) {
if((Boolean) table.getValueAt(row, 3) == true) {
((DefaultTableModel) model).removeRow(row);
row--;
}
}
}
});
searchBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String text = searchTextField.getText();
if (text.length() == 0) {
sorter.setRowFilter(null);
} else {
sorter.setRowFilter(RowFilter.regexFilter(text));
}
}
});
flowLayoutPanel.add(searchTextField);
flowLayoutPanel.add(searchBtn);
panel.add(flowLayoutPanel,BorderLayout.NORTH);
panel.add(scrollPane , BorderLayout.CENTER);
panel.add(button, BorderLayout.PAGE_END);
getContentPane().add(panel);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
TableExample frame = new TableExample();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.pack();
frame.setLocation(150, 150);
frame.setVisible(true);
}
});
}
}
Since the row number changes when you filter the table. Use original row number to delete a correct row.
Use table.convertRowIndexToModel(row) to get the actual row number.
Maps the index of the row in terms of the view to the underlying TableModel. If the contents of the model are not sorted the model and view indices are the same.
sample code:
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
for(int row = 0; row < table.getRowCount(); ++row) {
if((Boolean) table.getValueAt(row, 3) == true) {
((DefaultTableModel) model).removeRow(table.convertRowIndexToModel(row));
row--;
}
}
}
});
Read more...

How to set column width in JTable/JXTable?

My application consists of several JTables resp. JXTables. The goal is to store column width and reload them on start up.
But the following code does nothing change on the column widths of the tables:
tblTasks.getColumn(1).setWidth(36);
and also this one does nothing:
tblTasks.getColumn(1).setPreferredWidth(36);
Any ideas ?
But the following code does nothing change on the column widths of the
tables:
tblTasks.getColumn(1).setWidth(36);
and also this one does nothing:
tblTasks.getColumn(1).setPreferredWidth(36);
Any ideas ?
.
proper constructor is JTable.getColumnModel().getColumn(0).setPreferredWidth(36);
for example
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Stack;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.WindowConstants;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
public class TableRowHeight {
private static final long serialVersionUID = 1L;
private JFrame frame = new JFrame("p*s*s*s*s*t*t");
private String[] columnNames = {"one", "two", "Playing with", "four", "five",};
private String[][] data = {
{"aaaaaa", "bbbbbb", "cccccc", "dddddd", "eeeeeee",},
{"bbbbbb", "cccccc", "dddddd", "eeeeeee", "aaaaaa",},
{"cccccc", "dddddd", "eeeeeee", "aaaaaa", "bbbbbb",},
{"dddddd", "eeeeeee", "aaaaaa", "bbbbbb", "cccccc",},
{"eeeeeee", "aaaaaa", "bbbbbb", "cccccc", "dddddd",}};
private JTable table = new JTable(new DefaultTableModel(data, columnNames));
private TableColumnModel tcm = table.getColumnModel();
private Stack<TableColumn> colDeleted = new Stack<TableColumn>();
private JButton restoreButton = new JButton("Restore Column Size");
private JButton hideButton = new JButton("Set Column Size to Zero");
private JButton deleteButton = new JButton("Delete Column");
private JButton addButton = new JButton("Restore Column");
public TableRowHeight() {
table.setRowMargin(4);
table.setRowHeight(30);
table.setFont(new Font("SansSerif", Font.BOLD + Font.PLAIN, 20));
JScrollPane scrollPane = new JScrollPane(table);
for (int i = 0; i < (tcm.getColumnCount()); i++) {
tcm.getColumn(i).setPreferredWidth(150);
}
table.setPreferredScrollableViewportSize(table.getPreferredSize());
restoreButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
tcm.getColumn(2).setPreferredWidth(100);
}
});
hideButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
tcm.getColumn(2).setPreferredWidth(000);
}
});
deleteButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (table.getColumnCount() > 0) {
TableColumn colToDelete = table.getColumnModel().getColumn(table.getColumnCount() - 1);
table.removeColumn(colToDelete);
table.validate();
colDeleted.push(colToDelete);
addButton.setEnabled(true);
} else {
deleteButton.setEnabled(false);
}
}
});
addButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (colDeleted.size() > 0) {
table.addColumn(colDeleted.pop());
table.validate();
deleteButton.setEnabled(true);
} else {
addButton.setEnabled(false);
}
}
});
JPanel btnPanel = new JPanel();
btnPanel.add(hideButton);
btnPanel.add(restoreButton);
btnPanel.add(deleteButton);
btnPanel.add(addButton);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(scrollPane, BorderLayout.CENTER);
frame.add(btnPanel, BorderLayout.SOUTH);
frame.pack();
frame.setLocation(150, 150);
frame.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
TableRowHeight frame = new TableRowHeight();
}
});
}
}
table.getColumn("Column Name").setMaxWidth(36);
What I did is setting the minimum width to a value for each column. For the preferred width of the last column I choose Integer.MAX_VALUE which will push all the other columns to their minimum size.
This is a snippet from my project:
ruleTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
ruleTable.getColumnModel().getColumn(0).setMinWidth(25);
ruleTable.getColumnModel().getColumn(1).setMinWidth(125);
ruleTable.getColumnModel().getColumn(2).setMinWidth(175);
ruleTable.getColumnModel().getColumn(2).setMaxWidth(Integer.MAX_VALUE);
Hope it will help you and maybe others... For you it may be a little late ;-P
table.getColumn("Column Name").setMinWidth(36);

JTable model listener detects inserted rows too soon (before they are drawn)

I have a JTable which can have rows dynamically added by the user. It sits in a JScrollPane, so as the number of rows gets large enough, the scroller becomes active. My desire is that when the user adds a new row, the scroller moves all the way to the bottom, so that the new row is visible in the scrollpane. I'm currently (SSCCE below) trying to use a table model listener to detect when the row is inserted, and force the scrollbar all the way down when the detection is made. However, it seems this detection is "too early," as the model has updated but the new row has not actually been painted yet, so what happens is the scroller moves all the way to the bottom just before the new row is inserted, and then the new row is inserted just below the end of the pane (out of visibility).
Obviously this approach is wrong somehow. What is the correct approach?
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
public class TableListenerTest {
private JFrame frame;
private JScrollPane scrollPane;
private JTable table;
private DefaultTableModel tableModel;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
TableListenerTest window = new TableListenerTest();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public TableListenerTest() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 450, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS));
JSplitPane splitPane = new JSplitPane();
frame.getContentPane().add(splitPane);
scrollPane = new JScrollPane();
scrollPane.setPreferredSize(new Dimension(100, 2));
splitPane.setLeftComponent(scrollPane);
tableModel = new DefaultTableModel(new Object[]{"Stuff"},0);
table = new JTable(tableModel);
scrollPane.setViewportView(table);
table.getModel().addTableModelListener(new TableModelListener() {
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.INSERT) {
JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
scrollBar.setValue(scrollBar.getMaximum());
}
}
});
JButton btnAddRow = new JButton("Add Row");
btnAddRow.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
tableModel.addRow(new Object[]{"new row"});
}
});
splitPane.setRightComponent(btnAddRow);
}
}
EDIT: Updated SSCCE below based on trashgod's request. This version still does not work, however, if I move the scrolling logic from the table model listener to the button listener as he did, then it does work!
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
public class TableListenerTest {
private JFrame frame;
private JScrollPane scrollPane;
private JTable table;
private DefaultTableModel tableModel;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
TableListenerTest window = new TableListenerTest();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public TableListenerTest() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 450, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS));
JSplitPane splitPane = new JSplitPane();
frame.getContentPane().add(splitPane);
scrollPane = new JScrollPane();
scrollPane.setPreferredSize(new Dimension(100, 2));
splitPane.setLeftComponent(scrollPane);
tableModel = new DefaultTableModel(new Object[]{"Stuff"},0);
table = new JTable(tableModel);
scrollPane.setViewportView(table);
table.getModel().addTableModelListener(new TableModelListener() {
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.INSERT) {
int last = table.getModel().getRowCount() - 1;
Rectangle r = table.getCellRect(last, 0, true);
table.scrollRectToVisible(r);
}
}
});
JButton btnAddRow = new JButton("Add Row");
btnAddRow.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
tableModel.addRow(new Object[]{"new row"});
}
});
splitPane.setRightComponent(btnAddRow);
}
}
This example uses scrollRectToVisible() to (conditionally) scroll to the last cell rectangle. As a feature you can click on the thumb to suspend scrolling and release to resume.
private void scrollToLast() {
if (isAutoScroll) {
int last = table.getModel().getRowCount() - 1;
Rectangle r = table.getCellRect(last, 0, true);
table.scrollRectToVisible(r);
}
}
Addendum: I tried scrollRectToVisible in my SSCCE, and it still exhibits the same problem.
This Action provides both mouse and keyboard control:
JButton btnAddRow = new JButton(new AbstractAction("Add Row") {
#Override
public void actionPerformed(ActionEvent e) {
tableModel.addRow(new Object[]{"new row"});
int last = table.getModel().getRowCount() - 1;
Rectangle r = table.getCellRect(last, 0, true);
table.scrollRectToVisible(r);
}
});
Addendum: Here's a variation on your example that illustrates a revised layout strategy.
/** #see https://stackoverflow.com/a/14429388/230513 */
public class TableListenerTest {
private static final int N = 8;
private JFrame frame;
private JScrollPane scrollPane;
private JTable table;
private DefaultTableModel tableModel;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
TableListenerTest window = new TableListenerTest();
window.frame.setVisible(true);
}
});
}
public TableListenerTest() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.X_AXIS));
tableModel = new DefaultTableModel(new Object[]{"Stuff"}, 0);
for (int i = 0; i < N; i++) {
tableModel.addRow(new Object[]{"new row"});
}
table = new JTable(tableModel) {
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(200, table.getRowHeight() * N);
}
};
scrollPane = new JScrollPane();
scrollPane.setViewportView(table);
JButton btnAddRow = new JButton(new AbstractAction("Add Row") {
#Override
public void actionPerformed(ActionEvent e) {
tableModel.addRow(new Object[]{"new row"});
int last = table.getModel().getRowCount() - 1;
Rectangle r = table.getCellRect(last, 0, true);
table.scrollRectToVisible(r);
}
});
frame.add(scrollPane);
frame.add(btnAddRow);
frame.pack();
}
}
However, it seems this detection is "too early,"
For emphasis (#trashgod already mentioned it in a comment): that's true - and expected and a fairly general issue in Swing :-)
The table - and any other view with any model, not only data but also selection, adjustment, ... - is listening to the model to update itself. So a custom listener is just yet another listener in the line (with serving sequence undefined). If it wants to do something that depends on the view state it has to make sure to do so after all internal update is ready (in this concrete context, the internal update includes the update of the adjustmentModel of the vertical scrollBar) Postponing the custom processing until the internals are done, is achieved by wrapping SwingUtilities.invokeLater:
TableModelListener l = new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.INSERT) {
invokeScroll();
}
}
protected void invokeScroll() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int last = table.getModel().getRowCount() - 1;
Rectangle r = table.getCellRect(last, 0, true);
table.scrollRectToVisible(r);
}
});
}
};
table.getModel().addTableModelListener(l);

Categories