Java图形事件没有按照我期望的顺序发生

Java图形事件没有按照我期望的顺序发生,java,graphics,repaint,Java,Graphics,Repaint,所以我在做一个图形纸牌游戏。每张卡都是一个JPanel,带有一个按钮和两个与其相关联的图像。我有一个flip方法,这是我在操作侦听器中单击卡片时调用的第一个方法 public void flip() { if(b1.getIcon() == card2) b1.setIcon(card1); else b1.setIcon(card2); revalidate(); repaint(); } 然而,由于某种原因,直到我调用此方法后的某个时刻,卡才会翻转(意味着图

所以我在做一个图形纸牌游戏。每张卡都是一个JPanel,带有一个按钮和两个与其相关联的图像。我有一个flip方法,这是我在操作侦听器中单击卡片时调用的第一个方法

public void flip()
{
    if(b1.getIcon() == card2) b1.setIcon(card1);
    else b1.setIcon(card2);
    revalidate();
    repaint();
}
然而,由于某种原因,直到我调用此方法后的某个时刻,卡才会翻转(意味着图标不会改变)。例如,当我在调用flip后放置一个Thread.sleep时,我会假设我的程序会在flip完成后暂停,但它不会!它会在图像尚未切换的情况下睡眠,并且只在睡眠时间结束后的某个时间点切换图像


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

重新绘制是一个请求,是一个请求,是一个请求,而不是一个请求。在covers repaint()下,正在将重新绘制作业发布到事件队列中。除了重新绘制工作外,还有鼠标移动、鼠标单击、键盘活动等内容发布在那里。Swing线程经过并定期执行队列中的作业,以重新绘制UI、向组件传递鼠标和键盘事件等。事实上,Swing线程非常频繁地传递这些事件,但这取决于UI在该Swing线程上做了多少工作。当它发出鼠标点击、键盘事件和重画时,它无法从该队列中读取。因此,如果您的代码需要很长时间才能重新响应任何事件,swing及时响应新事件的能力就会降低或全部被阻止

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

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

首先取消你的睡眠呼叫。第二不要编写依赖于在执行更多逻辑之前完成绘制的代码。Repaint和revalidate是特殊的调用,Swing会将请求合并为Repaint/revalidate,这样它就不会连续重画10次(浪费重要的cpu时间)

Swing无法重新绘制面板,除非调用您的线程将其第一次调用的方法重新启用。线程越早离开该方法,重绘()就会发生得越早


您还没有解释为什么要立即进行重新绘制,所以我不能给您任何关于如何构造代码的建议,这样您就不会关心它了。但是我告诉你,你不应该在意重画还没有发生。

重画是一个要求重画的请求,而不是要求重画。在covers repaint()下,正在将重新绘制作业发布到事件队列中。除了重新绘制工作外,还有鼠标移动、鼠标单击、键盘活动等内容发布在那里。Swing线程经过并定期执行队列中的作业,以重新绘制UI、向组件传递鼠标和键盘事件等。事实上,Swing线程非常频繁地传递这些事件,但这取决于UI在该Swing线程上做了多少工作。当它发出鼠标点击、键盘事件和重画时,它无法从该队列中读取。因此,如果您的代码需要很长时间才能重新响应任何事件,swing及时响应新事件的能力就会降低或全部被阻止

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

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

首先取消你的睡眠呼叫。第二不要编写依赖于在执行更多逻辑之前完成绘制的代码。Repaint和revalidate是特殊的调用,Swing会将请求合并为Repaint/revalidate,这样它就不会连续重画10次(浪费重要的cpu时间)

Swing无法重新绘制面板,除非调用您的线程将其第一次调用的方法重新启用。线程越早离开该方法,重绘()就会发生得越早


您还没有解释为什么要立即进行重新绘制,所以我不能给您任何关于如何构造代码的建议,这样您就不会关心它了。但是我告诉你,你不应该在意重新油漆还没有发生。

完全同意@chubbsondubs。我只是想补充一下,如果你的目标是在调用翻转后的某个时间发生一些事件,考虑使用A。正如你痛苦地意识到的,呼叫睡眠并没有达到预期的效果。但如果相反,你

  • 创建一个计时器,比如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();
}

完全同意@chubbsondubs。我只是想补充一点,如果你的目标是这样做的话