I'm coding a GUI for my Tic Tac Toe project and I'm having fun toying with little visual effects like the one below. Every time the user sets a cpu player and clicks on a done button forgetting to set cpu strength level, I display a little warning message in the same way my code sample below does.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class TestingStuff implements ActionListener {
JLabel myLabel;
JFrame myFrame;
JButton myButton;
JPanel myPanel;
public TestingStuff() {
myFrame = new JFrame("Hello");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
myPanel.setBackground(Color.DARK_GRAY);
myFrame.add(myPanel, BorderLayout.CENTER);
myLabel = new JLabel("Hi dear stackoverflow coders!");
myLabel.setFont(new Font("MV Boli", Font.BOLD, 15));
myLabel.setForeground(Color.GREEN);
myPanel.add(myLabel);
myButton = new JButton("Click me like there's no tomorrow!");
myButton.addActionListener(this);
myFrame.add(myButton, BorderLayout.SOUTH);
myFrame.pack();
myFrame.setLocationRelativeTo(null);
myFrame.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
int timerDelay = 1;
Timer blinkTimer = new Timer(timerDelay, new ActionListener() {
private int count = 0;
private int maxTime = 30;
private String hello = myLabel.getText();
private int len = hello.length();
private String s;
public void actionPerformed(ActionEvent e) {
if (count * timerDelay >= maxTime) {
((Timer) e.getSource()).stop();
} else {
myLabel.setVisible(true);
if (count < len) {
s = hello.substring(0, count + 1);
myLabel.setText(s);
}
count++;
}
}
});
blinkTimer.start();
}
public static void main(String[] args) {
TestingStuff foo = new TestingStuff();
}
}
Try to repeatedly click on the button.
At least on my machine. Since there's no reason for the user to repeatedly click on the done button, I'm not too worried, but still... I think I'll end up disabling every button the user can click during that short animation in order to avoid unexpected behaviour. My question: can anybody explain what is happening and why I get to see that truncated text just after let's say 7-8 fast clicks?
The delay between updates is set to 1
millisecond (got luck getting that to be that accurate 😉), so you whole animation is done in around 30 milliseconds.
Instead, try setting it to something like 30 milliseconds (~1 second of runtime)
I'd also do away with the concept of maxTime
, you just want to keep looping until you've got all the text, otherwise it's a little bit pointless (IMHO)
Maybe something like...
int timerDelay = 30;
Timer blinkTimer = new Timer(timerDelay, new ActionListener() {
private int count = 0;
private String hello = myLabel.getText();
private String s;
public void actionPerformed(ActionEvent e) {
if (count >= hello.length()) {
System.out.println("Done");
((Timer) e.getSource()).stop();
} else {
System.out.println("Hello");
myLabel.setVisible(true);
s = hello.substring(0, count + 1);
myLabel.setText(s);
count++;
}
}
});
blinkTimer.start();
Now, if you prefer a time based animation, it might look something like this.
This is set to run for 1 second and it will use a "progression" calculation to determine how much of the text should be displayed
Timer blinkTimer = new Timer(5, new ActionListener() {
private String title = myLabel.getText();
private Duration runtime = Duration.ofSeconds(1);
private Instant startTime;
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = Instant.now();
}
Duration between = Duration.between(startTime, Instant.now());
double progress = between.toMillis() / (double)runtime.toMillis();
if (progress >= 1.0) {
((Timer) e.getSource()).stop();
// Just make sure the text is up-to-date
myLabel.setText(title);
} else {
int count = (int)(title.length() * progress);
String text = title.substring(0, count);
myLabel.setText(text);
}
}
});
blinkTimer.start();
You are constantly creating new Timer
s, they are going to complete with each other. Instead, either disable the button or stop the currently running Timer
, for example...
@Override
public void actionPerformed(ActionEvent e) {
myButton.setEnabled(false);
Timer blinkTimer = new Timer(5, new ActionListener() {
private String title = myLabel.getText();
private Duration runtime = Duration.ofSeconds(1);
private Instant startTime;
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = Instant.now();
}
Duration between = Duration.between(startTime, Instant.now());
double progress = between.toMillis() / (double) runtime.toMillis();
if (progress >= 1.0) {
((Timer) e.getSource()).stop();
// Just make sure the text is up-to-date
myLabel.setText(title);
myButton.setEnabled(true);
} else {
int count = (int) (title.length() * progress);
String text = title.substring(0, count);
myLabel.setText(text);
}
}
});
blinkTimer.start();
}
private Timer blinkTimer;
@Override
public void actionPerformed(ActionEvent e) {
if (blinkTimer != null) {
blinkTimer.stop();
blinkTimer = null;
}
blinkTimer = new Timer(5, new ActionListener() {
private String title = myLabel.getText();
private Duration runtime = Duration.ofSeconds(1);
private Instant startTime;
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = Instant.now();
}
Duration between = Duration.between(startTime, Instant.now());
double progress = between.toMillis() / (double) runtime.toMillis();
if (progress >= 1.0) {
((Timer) e.getSource()).stop();
// Just make sure the text is up-to-date
myLabel.setText(title);
myButton.setEnabled(true);
} else {
int count = (int) (title.length() * progress);
String text = title.substring(0, count);
myLabel.setText(text);
}
}
});
blinkTimer.start();
}
Oh 🤦♂️ - don't use the labels text as the initial text to be displayed by the label - use something which you can control, otherwise you're seeding incomplete text back into the animation
private String greetingText = "Hi dear stackoverflow coders!";
private Timer blinkTimer;
@Override
public void actionPerformed(ActionEvent e) {
if (blinkTimer != null) {
blinkTimer.stop();
blinkTimer = null;
}
blinkTimer = new Timer(5, new ActionListener() {
private String title = greetingText;
private Duration runtime = Duration.ofSeconds(5);
private Instant startTime;
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = Instant.now();
}
Duration between = Duration.between(startTime, Instant.now());
double progress = between.toMillis() / (double) runtime.toMillis();
System.out.println(hashCode() + " " + progress);
if (progress >= 1.0) {
((Timer) e.getSource()).stop();
// Just make sure the text is up-to-date
myLabel.setText(title);
myButton.setEnabled(true);
} else {
int count = (int) (title.length() * progress);
String text = title.substring(0, count);
myLabel.setText(text);
}
}
});
blinkTimer.start();
}