JTable model listener detects inserted rows too soon (before they are drawn)
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();
}
}
JTable won't dynamically add rows
Your issue might be due to setting the scroll pane's size to 0. You should delete table.setPreferredScrollableViewportSize(table.getPreferredSize())
or set a preferred size of your table by table.setPreferredSize()
before you use table.getPreferredSize()
.
Large gap between JTables
Absent a complete example, it's hard to be sure what you're seeing. Given that one should avoid setPreferredSize()
, note that JTable
implements Scrollable
and can calculate it's own preferred size. Unfortunately, the arbitrary Dimension
calculated may be causing the problem. Overriding getPreferredScrollableViewportSize()
to return a multiple of getRowHeight()
, for example, may be a useful alternative.
Scroll to last added row in JTable
Scrolling to a newly inserted row in a potentially sorted table involves
- listening to changes in the table model
- converting the rowIndex of the event (it is in model coordiates) to view coordinates
- scrolling to the view position
In code:
final JTable table = new JTable();
TableModelListener l = new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
if (TableUtilities.isInsert(e)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int viewRow = table.convertRowIndexToView(e.getFirstRow());
table.scrollRectToVisible(table.getCellRect(viewRow, 0, true));
}
});
}
}
};
table.getModel().addTableModelListener(l);
Two important aspects:
- your model implemenation must fire the correct event, that is a insert, not a dataChanged
- invoking both index conversion and scrolling guarantees that the table (which is listening to the model as well) has updated all internal state according to the model change.
Unable to change location and size of JTable
The reason why the table component is not resizing is that you set a so called null layout manager with the setLayout(null)
. Setting this means that you should take care of locating and resizing components in your GUI.
This is what the layout managers do.
Once you use a layout manager, such as GroupLayout
, the example works as
expected.
package com.zetcode;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellRenderer;
public class HotelReservationSystemEx extends JFrame {
private JTable table;
public HotelReservationSystemEx() {
initUI();
}
private void initUI() {
String[] columns = {"Name", "Age", "Gender"};
String[][] data = {{"John", "18", "Male"},
{"Daisy", "19", "Female"},
{"Dave", "23", "Male"},
{"Jake", "30", "Male"}};
table = new JTable(data, columns) {
@Override
public boolean isCellEditable(int data, int columns) {
return false;
}
@Override
public Component prepareRenderer(TableCellRenderer r, int data, int columns) {
Component c = super.prepareRenderer(r, data, columns);
if ((data % 2 == 0)) {
c.setBackground(Color.WHITE);
} else {
c.setBackground(Color.LIGHT_GRAY);
}
return c;
}
};
//table.setFillsViewportHeight(true);
JScrollPane spane = new JScrollPane(table);
createLayout(spane);
setTitle("Hotel reservation system");
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createLayout(JComponent... arg) {
Container pane = getContentPane();
GroupLayout gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setAutoCreateContainerGaps(true);
gl.setHorizontalGroup(gl.createParallelGroup()
.addComponent(arg[0])
);
gl.setVerticalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
);
pack();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
HotelReservationSystemEx ex = new HotelReservationSystemEx();
ex.setVisible(true);
});
}
}
In addition, you are unnecessary creating a JPanel
and instantiating
the JTable
twice.
Here is the screenshot:
JTable sizing issue
JTable
can't returns properDimension
orPreferredSize
, there are three waystable.setPreferredScrollableViewportSize(table.getPreferredSize());
but notice for small JTables with a few Rows and Columns tooto calculate desired size for (part) of Columns and (part) Rows too, then pass this
Dimension
in formtable.setPreferredScrollableViewportSize(new Dimension(x, y));
override
getPreferredSize
forJScrollPane
then
JFrame.pack(before JFrame.setVisible(true))
to calculate desired Size on the screenJPanel
hasFlowLayout
implemented in API, I'd to suggest to change to BorderLayout, then JScrollPane in CENTER area can fill whole (available) area and will be resizable with JFrame, not possible to resize JComponent (together with its container) layed by FlowLayouthave to call
data.revalidate()
,data.repaint()
andShow.pack()
as last code lines instead of (remove this code line) Show.setVisible(true);rename
Show
tomyFrame
andshow
tomyButton
JTable height calculation
You can substitute your own implementation of Scrollable
to get a variety of effects. In particular, override getPreferredScrollableViewportSize()
and return a suitable multiple of the row height. Let N
be the number of rows after which you want the scrollbar to appear.
JTable table = new JTable(tableModel) {
@Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(SOME_WIDTH, N * table.getRowHeight());
}
};
Add the table to a scroll pane:
JScrollPane sp = new JScrollPane(table);
Add the pane to a panel having a layout that respects preferred size:
JPanel p = new JPanel(new GridLayout());
p.add(sp);
A table having more than N
rows will display scroll bars, as shown here.
I just want to find the actual table height [in the context of] unit tests from the huge project.
I don't know exactly how your total height calculation fails, but the result will inevitably change based on the user's platform, the chosen look & feel, and the component validation state; an erroneous approach is suggested here. Instead, attempt to meet the test requirement by ensuring that a paricular number of rows is visible using the approach outlined above.
Jtable - scroll to bottom with variable height rows
Is there a listener I can override, or an event I can listen to, that will happen after the table has been redrawn with the new rows at the bottom?
Is there an event I can listen to to know when the height has been reset on the table, so I can then scrollToBottom?
Yes. Found in a parent class of JTable, Component#addComponentListener( ComponentListener )
where I can override the componentResized
method.
JAR Bundler using OSXAdapter causing application to lag or terminate
After doing it, I'm not fully convinced a SwingWorker is a simpler (aka: better) solution - still requires additional thread synching (between the worker thread and the "outer" thread which passes in the file/names). Anyway (taking the opportunity to learn, and be it by errors :), below is a crude proof of concept example for the basic idea:
- implement the Controller as SwingWorker, which funnels the input from the outer thread into the EDT
- make it accept input (from the adapter, f.i.) via a method doWork(..) which queues the input for publishing
- implement doInBackground to succesively publish the input
open issues
- synch the access to the local list (not an expert in concurrency, but pretty sure that needs to be done)
- reliably detecting the end of the outer thread (here simply stops when the input queue is empty)
Feedback welcome :-)
public class GUI {
private JFrame frame = new JFrame();
private DefaultTableModel model = new DefaultTableModel();
private JTable table = new JTable(model);
private JScrollPane pane = new JScrollPane(table);
public GUI() {
model.addColumn("Name");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane);
frame.pack();
frame.setVisible(true);
}
public void addRow(String name) {
model.addRow(new Object[] { name });
}
/**
* Controller is a SwingWorker.
*/
public static class Controller extends SwingWorker<Void, String> {
private GUI gui;
private List<String> pending;
public Controller() {
gui = new GUI();
}
public void doWork(String newLine) {
if (pending == null) {
pending = new ArrayList<String>();
pending.add(newLine);
execute();
} else {
pending.add(newLine);
}
}
@Override
protected Void doInBackground() throws Exception {
while (pending.size() > 0) {
publish(pending.remove(0));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
/**
* @inherited <p>
*/
@Override
protected void process(List<String> chunks) {
for (String object : chunks) {
gui.addRow(object);
}
}
}
/**
* Simulating the adapter.
*
* Obviously, the real-thingy wouldn't have a reference
* to the controller, but message the doWork refectively
*/
public static class Adapter implements Runnable {
Controller controller;
public Adapter(Controller controller) {
this.controller = controller;
}
@Override
public void run() {
for (int i=0; i<10; i++)
{
controller.doWork("Line "+(i+1));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args)
{
System.err.println("Initializing controller");
new Adapter(new Controller()).run();
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger.getLogger(GUI.class.getName());
}
Related Topics
Converting an Array of Objects to an Array of Their Primitive Types
Platform Independent Paths in Java
Deserializing into a Hashmap of Custom Objects with Jackson
Issue Using Imageio.Write Jpg File: Pink Background
Finding the Second Highest Number in Array
What Is the Purpose of @Namedarg Annotation in Javafx 8
How to Compile & Run Java Program in Another Java Program
Get MAC Address on Local MAChine with Java
Java 8: Difference Between Method Reference Bound Receiver and Unbound Receiver
How to Use .Jar Files in Netbeans
Retrieve Version from Maven Pom.Xml in Code
Using Gzip Compression with Spring Boot/Mvc/Javaconfig with Restful
How to Change a Field Name in JSON Using Jackson
Reverse Each Individual Word of "Hello World" String with Java
How to Customize Parameter Names When Binding Spring MVC Command Objects
Performance of Traditional for Loop VS Iterator/Foreach in Java