1

所以我正在制作一个图形纸牌游戏。每张卡片都是一个 JPanel,带有一个按钮和两个与之关联的图像。我有一个翻转方法,这是我在点击卡片时在动作监听器中调用的第一件事。

public void flip()
{
    if(b1.getIcon() == card2) b1.setIcon(card1);
    else b1.setIcon(card2);
    revalidate();
    repaint();
}

但是,由于某种原因,卡片在我调用此方法后的某个时间点才会翻转(意味着图标不会改变)。例如,当我在调用翻转后放置一个 Thread.sleep 时,我会假设我的程序会在翻转完成后暂停,但事实并非如此!它在尚未切换图像的情况下进入睡眠状态,并且仅在睡眠时间结束后才切换它们。

当我让人类在这个纸牌游戏中玩 AI 时,这会导致一些重大问题,因为 AI 事件发生在纸牌在屏幕上翻转之前发生,即使我在执行任何 AI 代码之前调用了 flip()。谁能告诉我这里发生了什么?

4

2 回答 2

4

重绘是对 Swing 重绘的请求,而不是对重绘的调用。在幕后 repaint() 正在将重绘作业发布到事件队列中。与重绘工作一起,鼠标移动、鼠标点击、键盘活动等也发布在那里。Swing 线程来并定期从该队列执行作业以重绘 UI,将鼠标和键盘事件传递给组件等。事实上,swing 线程非常频繁地传递这些事件,但这仅取决于您的 UI 正在做多少工作在那个 Swing 线程上。当它发出鼠标点击、键盘事件和重绘时,它无法从该队列中读取。因此,如果您的代码需要很长时间才能响应任何事件,则 swing 及时响应新事件的能力会降低或一起被阻止。

这就是为什么如果您使用 Swing 事件调度线程通过网络执行阻塞服务调用,您的 UI 将停止绘制,直到该调用返回。这就是为什么将长时间运行的作业(如网络调用)移出 Swing 线程并在调用返回时使用 SwingUtilities.invokeLater() 以便您可以在事件线程上更新 UI 的原因。(invokeLater() 通过将作业发布到我们上面讨论过的 Swing 事件队列来做到这一点)。

这也是为什么在 Swing 事件线程上调用 sleep 是一个糟糕的主意。如果您将该线程置于睡眠状态,则它无法为队列中的事件提供服务。并且您请求的 repaint() 不会在 Swing 线程休眠时完成。

首先删除你的睡眠电话。第二。在执行更多逻辑之前,不要编写依赖于绘制完成的代码。重绘和重新验证是特殊调用,Swing 将合并请求以重绘/重新验证,因此它不会连续重绘 10 次(浪费重要的 cpu 时间)。

在您让调用您的线程离开它第一次调用您的方法之前,Swing 无法重绘您的面板。线程越早离开该方法,您的 repaint() 就会越早发生。

您还没有解释为什么要立即进行重绘,因此我无法就如何构建代码给您任何建议,因此您不必关心它。但我告诉你,你不应该关心重绘还没有发生。

于 2012-11-20T16:03:30.873 回答
1

完全同意@chubbsondubs。我只是想补充一点,如果您的目标是在调用翻转后的某个时间发生某些事件,请考虑使用摇摆计时器。正如您痛苦地意识到的那样,调用 Sleep 不会产生预期的效果。但如果相反,你

  • 创建一个 1 秒的计时器
  • 创建计时器触发时要执行的操作
  • 启动计时器

然后你可以创建你想要的延迟而不阻塞事件调度线程。

public void flip()
{
    if(b1.getIcon() == card2) b1.setIcon(card1);
    else b1.setIcon(card2);
    revalidate();
    repaint();

    javax.swing.Timer t = new javax.swing.Timer(1000, new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          // do something interesting
        }
     });
    t.repeats(false);
    t.start();
}
于 2012-11-20T16:24:06.767 回答