在一些快速重复的动作事件调用后,JLabel 文本未正确显示

JLabel text not being displayed correctly after some quickly repeated action event calls

我正在为我的 Tic Tac Toe 项目编写一个 GUI,我很喜欢玩像下面这样的小视觉效果。每次用户设置 cpu 播放器并单击忘记设置 cpu 强度级别的完成按钮时,我都会以与下面的代码示例相同的方式显示一条小警告消息。

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 Whosebug 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();
    }
}

尝试反复点击按钮。

点击前:

点击后:

至少在我的机器上。由于用户没有理由重复点击完成按钮,我不太担心,但仍然......我想我最终会禁用用户在那个短动画期间可以点击的每个按钮以避免意外行为。 我的问题:任何人都可以解释发生了什么,以及为什么我在快速点击 7-8 次后看到截断的文本?

更新之间的延迟设置为 1 毫秒(幸运的是它如此准确),因此整个动画在大约 30 毫秒内完成。

相反,尝试将其设置为 30 毫秒(约 运行 时间的 1 秒)

我也放弃了 maxTime 的概念,你只想一直循环直到你得到所有的文本,否则它有点毫无意义(恕我直言)

也许像...

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();

基于时间的动画

现在,如果您更喜欢基于时间的动画,它可能看起来像这样。

这设置为 运行 1 秒,它将使用“进度”计算来确定应显示多少文本

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();

“顺便说一下,尝试使用你的代码并反复点击按钮:我仍然得到相同的结果”

你在不断地创造新的Timers,它们将相互完善。相反,禁用按钮或停止当前 运行ning Timer,例如...

禁用按钮...

@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();
}

还有...

哦 ‍♂️ - 不要将标签文本用作标签要显示的初始文本 - 使用您可以控制的内容,否则您会将不完整的文本播种回动画中

private String greetingText = "Hi dear Whosebug 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();
}