如何立即更新Java GUI?(与Thread.sleep()冲突)

7
我正在制作记忆游戏,当你选择两张卡片并且它们匹配时,你就可以保留它们,否则你需要将它们翻回去。如果你记得已经选择过的卡片,你可以更好地猜测下两张卡片。
我的问题涉及到 repaint() 方法不能立即重绘。
当我翻转第二张卡片时,无论结果如何,我都想在丢弃或翻转卡片之前展示两张正面朝上的卡片。我通过调用 sleep() 来实现这一点。
当然,如果我使用 repaint() 来翻转卡片使其正面朝上,等待一秒钟,然后基于它们的值再次 repaint(),Java 会只重绘一次(我想念 C!)。
基本上,我想在 sleep() 之前强制调用更新。我阅读了其他一些线程,基本上说最好的方法是创建两个线程来保持逻辑和图形分离,然后你可以 sleep() 你的逻辑并保持你的 GUI 更新,但我在高中的第一年度 CS 课程中,我希望保持在课程的水平上(尽管我在夏天花了相当多的时间进行 Web 开发和 C 编码)。
因为我知道 StackOverflow 上的热心人们喜欢阅读代码,下面是我所指的程序部分。类 HitArea 是卡片对象,cards[] 数组包含一定数量的 HitArea(我还没有将 HitArea 类重命名)。activeCard1activeCard2 是我用来跟踪用户两个选择的 HitArea 实例,空白构造函数是一个保留的“不可见”HitArea,尽管稍后我会将其更改为 null。最后,cards.flip() 反转一个 toggle 布尔值,该值确定卡片是否正面朝上。
public void respond(HitArea choice)
{
    if(choice.inGame)
    {
        if(activeCard1.value == 0 && activeCard1.value == 0)
            activeCard1 = choice;
        else if((!(activeCard1.value == 0) && activeCard2.value == 0) && (activeCard1.id != choice.id))
        {
            activeCard2 = choice;
            check();
        }

    }
}
public void check()
{
    update();
    pause(250);
    if(activeCard2.value == activeCard1.value)
    {
        score += 2;
        activeCard1.inGame = false;
        activeCard2.inGame = false;
    }
    activeCard1.flip();
    activeCard2.flip();
    activeCard1 = new HitArea();
    activeCard2 = new HitArea();
}
public void pause(int milliseconds)
{
    try{
      Thread.currentThread().sleep(milliseconds);
    }
    catch(InterruptedException e){
        System.out.println("Exception: " + e);
    }
}

public void mousePressed(MouseEvent e)
{
    int x = e.getX();
    int y = e.getY();

    for (int i = 0; i < cardNum; i++)
        if(cards[i].boundsCheck( x, y ) )
        {
            repaint();
            cards[i].flip();
            respond(cards[i]);
        }
}

我毫不怀疑我的代码中存在一些问题,所以请随意指出。我认为我的基本结构还不错,我不想为这个项目创建多个线程(记住,这是基础!)。

1个回答

14
不要在主Swing线程(EDT)上调用Thread.sleep(...),永远不要这样做。而是使用Swing定时器。考虑使用JLabels来显示图像,然后可以通过简单地交换ImageIcons来“翻转”卡片。当第二张卡片被翻转时,如果没有匹配项,请启动一个延迟为xxxx毫秒的非重复Swing定时器,并在Timer的ActionListener的actionCommand方法中将两个JLabel都恢复为默认的ImageIcon。javax.swing.Timer教程可以在此处找到: 如何使用Swing计时器。关于使用g.drawString的评论:现在更容易了,因为您只需要更改JLabel的文本即可。但是,如果以后决定升级程序以显示图像,则已设置好所有内容。关于创建新的ActionListener类的问题:我会为此使用匿名内部类。例如:
  int delayTime = 2 * 1000;
  javax.swing.Timer myTimer = new Timer(delayTime, new ActionListener() {

     @Override
     public void actionPerformed(ActionEvent e) {
        // TODO: put in the code you want called in xxx mSecs.
     }
  });
  myTimer.setRepeats(false);
  myTimer.start();

3
我会将“Ever”加粗并斜体显示。 :) - fireshadow52
我只是展示一些 g.drawString(...) 的文本,没有图片。同样适用吗? - Eric Thoma
据我所知,我需要创建一个新的ActionListener类?我该怎么做?http://download.oracle.com/javase/1.4.2/docs/api/javax/swing/Timer.html - Eric Thoma
@EricThoma:请查看我上面的答案中的编辑2 - Hovercraft Full Of Eels
@HovercraftFullOfEels,但是SwingTimer也会为其延迟参数调用Thread.sleep吗?如果人们更想让EDT自己休眠怎么办?还是说...暂停工作线程中的操作并将结果传递给EDT? - Plain_Dude_Sleeping_Alone
1
@Plain_Dude_Sleeping_Alone:是的,Swing计时器可能会调用Thread.sleep,但是在EDT之外。正如我在答案中所指出的,你绝对不想在EDT上调用Thread.sleep,因为这会使GUI完全无响应——甚至无法通过单击顶部窗口右上角的操作系统关闭按钮来关闭它。如果你想在代码流的某个部分暂停GUI,则考虑使用模态对话框,例如JOptionPane。 - Hovercraft Full Of Eels

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接