Dynamically adding rows to JTable - Why do they appear at once? - java

In the example, I'm seeking to add a table to my GUI and then dynamically add rows to it (to show the progress). What I don't understand is why all the rows are appearing at once. I mean, the the table's changing, isn't it? Can someone please give me an explanation?
import java.awt.Component;
public class Main {
public static void main(String[] args) {
// Show GUI
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
GUI gui = new GUI();
gui.setVisible(true);
DefaultTableModel model = new DefaultTableModel(
new String[] { "Column 1", "Column 2" }, 0);
JTable table = new JTable(model);
gui.add(table);
gui.validate();
for (int i = 0; i < 10; i++) {
System.out.println("Row " + i);
model
.addRow(new String[] { "Row", String.valueOf(i) });
// model.fireTableDataChanged();
try {
Thread.sleep(250);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
}
class GUI extends JFrame {
private static final long serialVersionUID = 1L;
public GUI() {
setTitle("GUI");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 350, 100);
setLocationRelativeTo(null);
JPanel cp = new JPanel();
cp.setBorder(new EmptyBorder(10, 10, 10, 10));
setContentPane(cp);
}
}

Reiterating Kleopatra : Don't Sleep the EDT
You can instead use a javax.swing.Timer as seen in this answer
EDIT
I didn't want to mess with your code too much (just because it looks weird to me) but I changed it somewhat to add the Timer
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableModel;
public class Main {
static JTable table;
static GUI gui;
static Processor p = null;
public static void main(String[] args) {
// Show GUI
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
gui = new GUI();
p = new Processor() {
#Override
public void execute() {
final JTable table = new JTable(p.getTableModel());
final JScrollPane scrollPane = new JScrollPane(table);
gui.getContentPane().add(scrollPane, BorderLayout.CENTER);
gui.setLocationRelativeTo(null);
gui.setVisible(true);
Timer timer = new Timer(100, new ActionListener(){
public void actionPerformed(ActionEvent e) {
p.processRow();
table.scrollRectToVisible(table.getCellRect(table.getRowCount() - 1, 0, true));
}
});
timer.start();
}
};
p.execute();
}
});
}
}
class GUI extends JFrame {
private static final long serialVersionUID = 1L;
public GUI() {
setTitle("GUI");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 350, 400);
JPanel contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(10, 10, 10, 10));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
}
}
interface Callback {
void execute();
}
abstract class Processor implements Callback {
private final String[] cols = {"COL", "COL", "COL", "COL", "COL"};
private DefaultTableModel tableModel;
int numRows;
int numCols;
int a, b, c, d, e;
Processor() {
a = 1; b = 2; c = 3; d = 4; e = 4;
numRows = 1000;
tableModel = new DefaultTableModel(cols, numCols);
}
public DefaultTableModel getTableModel() {
return tableModel;
}
public void processRow() {
tableModel.addRow(new Object[]{a, b, c, d, e});
a++; b++; c++; d++; e++;
}
}

As pointed out by kleopatra and peeskillet, my initial example suffered from a stupid mistake. It's worth noting that peeskillet and I were following different approaches, though. In my example, the columns meant to represent connection attempts (more or less) that can take an unknown amount of time and that can actually fail (in that case, and only in that case, the next column would come into play and so on). Therefore, it wouldn't have made sense for me to add the rows at once (which was probably what made my example look weird to peeskillet). I've solved the task using a SwingWorker. As pointed out by kleopatra, there was a another mistake, which is now fixed. Here's my code:
package SwingWorkerExampleCopy;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableModel;
import java.awt.BorderLayout;
public class SwingWorkerExampleCopy {
public static void main(String[] args) {
// Show GUI
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
GUI gui = new GUI();
DefaultTableModel tableModel = new DefaultTableModel();
// Use a SwingWorker
Worker worker = new Worker(tableModel);
worker.execute();
JTable table = new JTable(tableModel);
table.setEnabled(false);
// table.setTableHeader(null);
JScrollPane scrollPane = new JScrollPane(table);
gui.getContentPane()
.add(scrollPane, BorderLayout.CENTER);
}
});
}
}
class GUI extends JFrame {
private static final long serialVersionUID = 1L;
public GUI() {
setTitle("GUI");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 350, 400);
setLocationRelativeTo(null);
setVisible(true);
JPanel contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(10, 10, 10, 10));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
}
}
class Worker extends SwingWorker<DefaultTableModel, Object[]> {
private final static int numRows = 10;
private final static int numCols = 10;
private DefaultTableModel model;
Worker(DefaultTableModel model) {
this.model = model;
model.setColumnCount(numCols);
}
#Override
protected DefaultTableModel doInBackground() throws Exception {
// Add row
for (int row = 0; row < numRows; row++) {
// Build columns
for (int col = 0; col < numCols; col++) {
if (col == 0) {
publish(new Object[] { new String("Row " + row), row,
col });
} else {
// Simulate a slow source
Thread
.sleep(new Random().nextInt((250 - 50) + 1) + 50);
Boolean isSuccessful = false;
// Simulate a return value
if (new Random().nextBoolean()) {
isSuccessful = true;
}
publish(new Object[] {
new String((isSuccessful == true ? "x" : "o")), row,
col });
if (isSuccessful == true) {
break;
}
}
}
}
return model;
}
#Override
protected void process(List<Object[]> chunks) {
for (Object[] chunk : chunks) {
// chunk[0]: cell value
// chunk[1]: number
// chunk[2]: column
if ((int) chunk[2] == 0) {
Object[] row = new Object[numCols];
row[0] = (Object) chunk[0];
model.addRow(row);
} else {
model.setValueAt((Object) chunk[0], (int) chunk[1],
(int) chunk[2]);
}
}
}
}

Because while your code is running, no other events (such as repaint events) can execute - you're blocking the event thread until you're done.
You may be able to call repaint directly, but the UI will still be unresponsive to input while your code is running. You'd be better off running the loop in a separate worker thread, and using invokeLater or invokeAndWait to perform the updates to the UI when needed.

Related

Get click position in grid. Java Swing

I got this code witch creates a clickable grid that shows the mouse position, altough i am not able to get the position in the grid in where the mouse is clicked, trying to be both X and Y position. Any ideas? This is how the grid looks:
Code:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.MatteBorder;
public class TestGrid02 {
public TestGrid02() {
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 static final int ROWS = 20;
private static final int COLUMNS = 20;
private static GridBagConstraints gbc;
public TestPane() {
setLayout(new GridBagLayout());
gbc = new GridBagConstraints();
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLUMNS; col++) {
gbc.gridx = col;
gbc.gridy = row;
CellPane cellPane = new CellPane();
Border border = null;
if (row < ROWS-1) {
if (col < COLUMNS-1) {
border = new MatteBorder(1, 1, 0, 0, Color.GRAY);
} else {
border = new MatteBorder(1, 1, 0, 1, Color.GRAY);
}
} else {
border = new MatteBorder(1, 1, 1, 0, Color.GRAY);
}
cellPane.setBorder(border);
add(cellPane, gbc);
}
}
}
}
public class CellPane extends JPanel {
private Color defaultBackground;
public CellPane() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
defaultBackground = getBackground();
setBackground(Color.RED);
}
#Override
public void mouseExited(MouseEvent e) {
setBackground(defaultBackground);
}
#Override
public void mouseClicked(MouseEvent e){
//Here is where it is supposed to be
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(30, 30);
}
}
}
In the CellPane class, witch is intended to be the one that listens to the mouse it is supposed to be the function that i need, at the mouseClicked listener, however i have tried with e.getX() or e.getLocationOnScreen() and these values were changing everytime i click in the same grid.
Well, that looks familiar 🤣
So, the basic idea would be to pass in the cell it's coordinates (ie, row/column) value via the constructor, for example...
public class CellPane extends JPanel {
private Color defaultBackground;
private Point cellCoordinate;
public CellPane(Point cellCoordinate) {
this.cellCoordinate = cellCoordinate;
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
defaultBackground = getBackground();
setBackground(Color.RED);
}
#Override
public void mouseExited(MouseEvent e) {
setBackground(defaultBackground);
}
#Override
public void mouseClicked(MouseEvent e) {
//Here is where it is supposed to be
System.out.println("Did click cell # " + getCellCoordinate().x + "x" + getCellCoordinate().y);
}
});
}
public Point getCellCoordinate() {
return cellCoordinate;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(30, 30);
}
}
The cell itself doesn't really care, not does it have any reasonable information available to it to determine how it's been laid out, so your best bet is to "tell" it the information you want it to represent.
For me, I'd just pass in the GridBagLayout row/col information, for example...
gbc.gridx = col;
gbc.gridy = row;
CellPane cellPane = new CellPane(new Point(col, row));
This way you remove all concept (and the issues associated with it) of how the cell is laid out
This approach (using buttons and action listeners) is better IMO. It uses the getButtonRowCol method to return a string indicating the button's location in an array (the buttonArray).
Use b.setBorderPainted(false); (uncomment that code line) to get rid of the borders around each button. Look to the values passed to the constructor of the GridLayout to remove the space between buttons.
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
public class GameGridLayout {
int size = 40;
int iconSize = 10;
JButton[][] buttonArray = new JButton[size][size];
ActionListener actionListener;
JLabel output = new JLabel("Click somewhere on the GUI");
GameGridLayout() {
JPanel gui = new JPanel(new BorderLayout(2,2));
gui.setBorder(new EmptyBorder(4,4,4,4));
gui.add(output,BorderLayout.PAGE_END);
JPanel gameContainer = new JPanel(new GridLayout(0,size,2,2));
gui.add(gameContainer);
actionListener = e -> output.setText(getButtonRowCol((JButton)e.getSource()));
for (int ii=0; ii<size*size; ii++) {
JButton b = getButton();
gameContainer.add(b);
buttonArray[ii%size][ii/size] = b;
}
JFrame f = new JFrame("GameGridLayout");
f.add(gui);
f.pack();
f.setMinimumSize(f.getSize());
f.setLocationByPlatform(true);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setVisible(true);
}
private String getButtonRowCol(JButton button) {
StringBuilder sb = new StringBuilder();
for (int xx=0; xx<size; xx++) {
for (int yy=0; yy<size; yy++) {
if (button.equals(buttonArray[xx][yy])) {
sb.append("User selected button at: ");
sb.append(xx+1);
sb.append(",");
sb.append(yy+1);
break;
}
}
}
return sb.toString();
}
private JButton getButton() {
JButton b = new JButton();
b.setIcon(new ImageIcon(
new BufferedImage(iconSize,iconSize,BufferedImage.TYPE_INT_ARGB)));
b.setRolloverIcon(new ImageIcon(
new BufferedImage(iconSize,iconSize,BufferedImage.TYPE_INT_RGB)));
b.setMargin(new Insets(0,0,0,0));
//b.setBorderPainted(false);
b.setContentAreaFilled(false);
b.addActionListener(actionListener);
return b;
}
public static void main(String[] args) {
Runnable r = () -> new GameGridLayout();
SwingUtilities.invokeLater(r);
}
}

JButton not re-enabling when selecting a row in JTable

I'm trying to set a button in enabled(false) when I create it, and when I select any row on the Jtable, that button goes enabled(true).
Logic is pretty simple here, but for some reason, it doesn't quite work, the button never gets into enabled(true).
JButton btnIniciarReparacin = new JButton("INICIAR REPARACI\u00D3N");
btnIniciarReparacin.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
tiempoStart = (int) (System.currentTimeMillis() / 1000L);
btnIniciarReparacin.setEnabled(false);
}
});
btnIniciarReparacin.setFont(new Font("SansSerif", Font.BOLD, 13));
btnIniciarReparacin.setBackground(new Color(231, 111, 81));
btnIniciarReparacin.setBounds(129, 625, 254, 50);
frame.getContentPane().add(btnIniciarReparacin);
int row = table.getSelectedRow();
//Comprobamos si hemos cogido algo de la tabla y si los botones están encendidos
if (table.isRowSelected(row)) {
btnIniciarReparacin.setEnabled(true);
} else {
btnIniciarReparacin.setEnabled(false);
}
Swing GUI's work by your adding listeners to events and then responding to state changes within the listener.
You appear to be checking the row selection state in code where you create your components, and that will never work since it only checks the state once, and before the user has had a chance to make a selection. Instead, you need to use a listener on your JTable, more specifically a ListSelectionListener that you add to the JTable's selection model that you get via table.getSelectionModel().addListSelectionListener(...)
Also note that this:
int row = table.getSelectedRow();
if (table.isRowSelected(row)) {
btnIniciarReparacin.setEnabled(true);
} else {
btnIniciarReparacin.setEnabled(false);
}
can be shortened to:
int row = table.getSelectedRow();
btnIniciarReparacin.setEnabled(table.isRowSelected(row));
e.g. something like:
table.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
public void valueChanged(ListSelectionEvent event) {
int row = table.getSelectedRow();
btnIniciarReparacin.setEnabled(table.isRowSelected(row));
}
});
For example, if the button deletes a row:
import java.awt.BorderLayout;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
#SuppressWarnings("serial")
public class Test01 extends JPanel {
private static final String[] COLUMNS = {"One", "Two", "Three", "Four"};
private DefaultTableModel tableModel = new DefaultTableModel(COLUMNS, 0);
private JTable table = new JTable(tableModel);
private JButton deleteRowButton = new JButton("Delete Row");
public Test01() {
int tableRows = 20;
for (int i = 0; i < tableRows; i++) {
Integer[] row = new Integer[COLUMNS.length];
for (int j = 0; j < row.length; j++) {
row[j] = (int) (100 * Math.random());
}
tableModel.addRow(row);
}
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
int row = table.getSelectedRow();
deleteRowButton.setEnabled(table.isRowSelected(row));
}
});
deleteRowButton.addActionListener(e -> {
int row = table.getSelectedRow();
tableModel.removeRow(row);
});
deleteRowButton.setEnabled(false);
JPanel bottomPanel = new JPanel();
bottomPanel.add(deleteRowButton);
setLayout(new BorderLayout());
add(new JScrollPane(table));
add(bottomPanel, BorderLayout.PAGE_END);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
Test01 mainPanel = new Test01();
JFrame frame = new JFrame("Test01");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
}

Java - Problem during the creation of a stock JPanel

I am attempting to create a JPanel that houses in a GridLayout a number of JLabels to the left JTextFields on the right. Unfortunately, nothing is shown even if the Components are reported as correctly added. Where is the problem?
public class AlternateGL_JPanel extends JPanel {
protected JPanel layoutPanel;
protected int rows, columns;
public AlternateGL_JPanel(int rows, int columns) {
this.rows = rows;
this.columns = columns;
// ===== MAIN FRAME DEFINITION =====
setBorder(new EmptyBorder(0, 0, 0, 0));
setLayout(new GridLayout(this.rows, this.columns));
// setBounds(10, 10, 500, 500);
// ===== INNER PANEL =====
this.layoutPanel = new JPanel(); //This is the nested panel
layoutPanel.setLayout(new GridLayout(this.rows, this.columns));
super.add(layoutPanel, BorderLayout.PAGE_START);
}
// =========================================================
// TODO | Superclass: JPanel
public void add(JComponent component) {
layoutPanel.add(component);
}
}
/** A <b>ManyTextAndInsertText</b> is a {#link JPanel} that houses a number of {#link JLabel}s to the left
* and {#link JTextField}s on the right.
*
*/
public class ManyTextAndInsertText extends AlternateGL_JPanel {
private JLabel[] texts;
private JTextField[] insertTexts;
public ManyTextAndInsertText(String[] descriptions) {
super(descriptions.length, 2);
this.texts = new JLabel[descriptions.length];
this.insertTexts = new JTextField[descriptions.length];
for(int i=0 ; i<descriptions.length ; i++)
{
texts[i] = new JLabel(descriptions[i]);
insertTexts[i] = new JTextField();
for(int j=0 ; j<this.getComponentCount() ; j++)
System.out.println("\t" + this.getComponent(j).toString());
this.add(texts[i]);
for(int j=0 ; j<this.getComponentCount() ; j++)
System.out.println("\t" + this.getComponent(j).toString());
this.add(insertTexts[i]);
}
}
public class TestManyTextes extends JFrame {
private JPanel contentPane;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
TestManyTextes frame = new TestManyTextes();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public TestManyTextes() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new ManyTextAndInsertText(new String[] { "First text: " , "Second text: "});
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
}
I greatly simplified your code and created this GUI.
Instead of extending Swing components, I used Swing components.
You would get a nicer looking form using a GridBagLayout, rather than a GridLayout.
Here's the runnable example.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
public class TestManyTexts {
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
new TestManyTexts();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public TestManyTexts() {
JFrame frame = new JFrame("Test Many Texts");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ManyTexts panel = new ManyTexts(new String[] { "First text: ",
"Second text: ", "Third Text:", "Forth Text" });
frame.add(panel.getPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public class ManyTexts {
private JPanel panel;
private JTextField[] fields;
public ManyTexts(String[] labels) {
createPanel(labels);
}
private void createPanel(String[] labels) {
panel = new JPanel(new GridLayout(0, 2));
panel.setBorder(new EmptyBorder(5, 5, 5, 5));
fields = new JTextField[labels.length];
for (int i = 0; i < labels.length; i++) {
JLabel label = new JLabel(labels[i]);
panel.add(label);
fields[i] = new JTextField(15);
panel.add(fields[i]);
}
}
public JPanel getPanel() {
return panel;
}
public JTextField[] getFields() {
return fields;
}
}
}

JTable when removing all rows I get java.lang.ArrayIndexOutOfBoundsException: -1 if that row was selected

The appropiate way to remove all rows from a table is to use:
DefaultTableModel tableModel = (DefaultTableModel) tablaPedidos.getModel();
if (tableModel.getRowCount() > 0) {
for (int i = tableModel.getRowCount() - 1; i > -1; i--) {
tableModel.removeRow(i);
}
}
However in my application I have a series of tabbedPanes for example tabbedPane 1 and tabbedPane 2, the tabbedPane 2 contains a JTable. My goal is to clean the JTable when I switch between panes.
However I am getting a strange functionality, it works fine only when none of the rows of the JTable are selected, when one row was left selected in the table and then I switch between tabbedPanes after I enter in tabbedPane 2 I get an exception in the ListSelectionListener.
//If a row in the table was left selected when I switched between tabbedPanes I get a ArrayIndexOutOfBoundsException exception when running this code
table.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
public void valueChanged(ListSelectionEvent event) {
if(!event.getValueIsAdjusting()) {
String numeroPedido = table.getValueAt(table.getSelectedRow(), 0).toString();
}
}
});
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.Vector.elementData(Vector.java:730)
at java.util.Vector.elementAt(Vector.java:473)
at javax.swing.table.DefaultTableModel.getValueAt(DefaultTableModel.java:649)
I just don't get it what's the influence of having a row selected to get this exception.
Example code:
Container.java, that contains both of the TabbedPanes
package testClearTable;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.JTabbedPane;
public class Container extends JFrame {
private JPanel contentPane;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Container frame = new Container();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Container() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 926, 556);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP);
contentPane.add(tabbedPane, BorderLayout.CENTER);
TabbedPane1 tabbedPane1 = new TabbedPane1();
tabbedPane.addTab("TabbedPane1", null, tabbedPane1, null);
TabbedPane2 tabbedPane2 = new TabbedPane2();
tabbedPane.addTab("TabbedPane2", null, tabbedPane2, null);
ChangeListener changeListener = new ChangeListener() {
public void stateChanged(ChangeEvent changeEvent) {
JTabbedPane sourceTabbedPane = (JTabbedPane) changeEvent.getSource();
int index = sourceTabbedPane.getSelectedIndex();
if(sourceTabbedPane.getTitleAt(index).equals("TabbedPane2")) {
Random r = new Random();
int randomNumber = r.nextInt(101);
TabbedPane2.updateTable(randomNumber);
System.out.println("Update table.");
}
System.out.println("Tab changed to: " + sourceTabbedPane.getTitleAt(index));
}
};
tabbedPane.addChangeListener(changeListener);
}
}
TabbedPane1.java
package testClearTable;
import javax.swing.JPanel;
public class TabbedPane1 extends JPanel {
/**
* Create the panel.
*/
public TabbedPane1() {
}
}
TabbedPane2.java
package testClearTable;
import java.util.ArrayList;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
public class TabbedPane2 extends JPanel {
private static JTable table;
/**
* Create the panel.
*/
public TabbedPane2() {
setLayout(new MigLayout("", "[][grow]", "[][grow]"));
JScrollPane scrollPane = new JScrollPane();
add(scrollPane, "cell 1 1,grow");
table = new JTable();
scrollPane.setViewportView(table);
table.setModel(new DefaultTableModel(
new Object[][] {
},
new String[] {
"Col 1", "Col 2", "Col 3"
}
));
addDummyData();
table.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
public void valueChanged(ListSelectionEvent event) {
if(!event.getValueIsAdjusting()) {
String numeroPedido = table.getValueAt(table.getSelectedRow(), 0).toString();
}
}
});
}
public void addDummyData() {
for(int i = 0 ; i < 10; i++) {
Object [] fila = new Object[table.getModel().getColumnCount()];
fila[0] = String.valueOf(i);
fila[1] = String.valueOf(i);
fila[2] = String.valueOf(i);
((DefaultTableModel) table.getModel()).addRow(fila);
}
}
public static void addDummyData2(int k) {
for(int i = 0 ; i < k; i++) {
Object [] fila = new Object[table.getModel().getColumnCount()];
fila[0] = String.valueOf(i);
fila[1] = String.valueOf(i);
fila[2] = String.valueOf(i);
((DefaultTableModel) table.getModel()).addRow(fila);
}
}
private static void clearTable() {
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
if (tableModel.getRowCount() > 0) {
for (int i = tableModel.getRowCount() - 1; i > -1; i--) {
tableModel.removeRow(i);
}
}
}
public static void updateTable(int i) {
clearTable();
addDummyData2(i);
}
}
Maybe call JTable.clearSelection() before removing all the data?
The problem comes from the addListSelectionListener . The ListSelectionEvent has been triggered and after cleaning the table and writing again there is no row selected.
public void valueChanged(ListSelectionEvent event) {
if(!event.getValueIsAdjusting()) {
if(tablaPedidos.getSelectedRow() > -1) {
When adding the verification to only perform an action when there is an actual row selected I dont get the exception.
What I don't understand why clearing and writing again is triggering the ListSelectionEvent.

JTable autoscroll methods are one row too high

Here is an example table of what I have been using:
JScrollPane scrollPane = new JScrollPane();
contentPane.add(scrollPane);
table = new JTable();
DefaultTableModel tableModel = new DefaultTableModel(new Object[]{"Row 1","Row 2"},0);
table.setModel(tableModel);
scrollPane.setViewportView(table);
I've attempted to use the following autoscrolling methods both with the same result.
// Method 1
JScrollBar vertical = scrollPane.getVerticalScrollBar();
vertical.setValue(vertical.getMaximum());
// Method 2
table.scrollRectToVisible(table.getCellRect(table.getRowCount(), 0, true));
Both examples go to the near-bottom of the table, up until the last (bottom) row that the user has to scroll down to see, only to happen again when another row is added.
EDIT: Example code to replicate the problem
import java.awt.*;
import javax.swing.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JScrollPane;
import javax.swing.table.DefaultTableModel;
public class ScrollTableEx extends JFrame {
private static final long serialVersionUID = 1L;
public int i = 100;
public JScrollPane scrollPane;
public JPanel contentPane;
public JTable table;
public JButton add;
public DefaultTableModel model = new DefaultTableModel(new Object[] { "Int (+)", "Int (-)" }, 0);
public static void main(String[] args) {
ScrollTableEx frame = new ScrollTableEx();
frame.setVisible(true);
}
public ScrollTableEx() {
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
setSize(500,400);
scrollPane = new JScrollPane();
table = new JTable();
table.setCellSelectionEnabled(true);
table.setModel(model);
scrollPane.setViewportView(table);
for (int i = 0; i < 100; i++) {
model.addRow(new Object[] { i, i * -1 });
Rectangle goodRect = table.getCellRect(model.getRowCount() - 1, 0, true);
table.scrollRectToVisible(goodRect);
}
JButton add = new JButton("Add");
add.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
Rectangle goodRect = table.getCellRect(model.getRowCount() - 1, 0, true);
table.scrollRectToVisible(goodRect);
model.addRow(new Object[] { i, i * -1 });
i++;
}
});
add(add, BorderLayout.NORTH);
add(scrollPane, BorderLayout.CENTER);
}
}
The problem is, JTable and TableModel are both 0 indexed. That is, the last value is actually rowCount - 1.
So when you use table.getCellRect(table.getRowCount(), 0, true), it's actually returning a Rectangle of the right position, just with a 0 height, because the row doesn't actually exist.
Instead you want to use table.getCellRect(table.getRowCount() - 1, 0, true)
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Rectangle;
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.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
public class ScrollTable {
public static void main(String[] args) {
new ScrollTable();
}
public ScrollTable() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
final DefaultTableModel model = new DefaultTableModel(new Object[]{"Test"}, 0);
for (int index = 0; index < 100; index++) {
model.addRow(new Object[]{index});
}
final JTable table = new JTable(model);
JButton add = new JButton("Add");
add.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
model.addRow(new Object[]{model.getRowCount()});
Rectangle badRect = table.getCellRect(model.getRowCount(), 0, true);
Rectangle goodRect = table.getCellRect(model.getRowCount() - 1, 0, true);
System.out.println("bad = " + badRect);
System.out.println("goodRect = " + goodRect);
table.scrollRectToVisible(goodRect);
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.add(add, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
I would avoid using .setBounds where possible. It does not take into consideration the variety of differences that exist between different computers and OS's and while it might look perfectly fine when you are developing, when you move it another system, it could produce undeseriable results - IMHO

Categories

Resources