You're almost there. But you seem to be misunderstanding what the Timer
is actually doing for you.
The Timer
is acting as a kind of pseudo loop, with a built in delay. That is, after each time period, it will execute. This means, that each time your ActionListener
is triggered, you want to execute the next step in your logic.
For example...

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;
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 JPanel contentPane = new JPanel(new GridBagLayout());
public TestPane() {
setLayout(new BorderLayout());
add(new JScrollPane(contentPane));
JButton startButton = new JButton("Start");
startButton.addActionListener(new ActionListener() {
private int row = 0;
private int count = 1000;
private DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
@Override
public void actionPerformed(ActionEvent e) {
row = 0;
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = gbc.REMAINDER;
gbc.weightx = 1;
contentPane.removeAll();
contentPane.revalidate();
contentPane.repaint();
Timer timer = new Timer(100, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
row++;
if (row >= count) {
((Timer)(e.getSource())).stop();
return;
}
JLabel label = new JLabel(LocalDateTime.now().format(formatter));
contentPane.add(label, gbc);
contentPane.revalidate();
contentPane.repaint();
// This is only required because the layout pass seems
// to be execute in different run cycle, so the label's
// bounds are not been updated yet. We force the layout
// pass so we can scroll to the label, but otherwise
// isn't needed
contentPane.doLayout();
Rectangle bounds = label.getBounds();
bounds.y += bounds.height;
contentPane.scrollRectToVisible(bounds);
}
});
timer.start();
}
});
add(startButton, BorderLayout.NORTH);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}