2014-05-08 73 views
2

首先,道歉这是多久。JLabel.setText()仅设置循环中最后一个元素的文本

我试图做一个简单的轮盘赌游戏,允许用户添加玩家,为这些玩家下注,并旋转轮盘,代表一个简单的JLabel,更新它的文本,每个数字它通过。

但是,我遇到了一个bug,我遇到了很多麻烦:JLabel只更新我的循环中最后一个元素的文本。

基本上,我的解决办法是这样的:

当用户按下标有“旋转”按钮(假设用户已经加入到游戏中),我调用一个方法,从一个叫SpinWheelService类,它是可观察到单这反过来又调用notifyObservers()方法:

public void actionPerformed(ActionEvent e) { 

    String cmd = e.getActionCommand(); 
    String description = null; 

    if (ADD_PLAYER.equals(cmd)) { 

     addDialog(); 

    } else if (PLACE_BET.equals(cmd)) { 

     betDialog(); 

    } else if (SPIN.equals(cmd)) { 

     SpinWheelService.sws.setSpinWheelService(); 

    } else if (DISPLAY.equals(cmd)) { 
     System.out.println("Display selected!"); 
    } 

} 

这里是我SpinWheelService类:

package model; 

import java.util.*; 

public class SpinWheelService extends Observable { 

    public static SpinWheelService sws = new SpinWheelService(); 

    public void setSpinWheelService() { 
     setChanged(); 
     notifyObservers(); 
    } 

} 

SpinWheelService注册的唯一的听众是这个班,其中GameEngine是我的游戏引擎,处理内部游戏逻辑,WheelCallbackImpl是更新视图类:

class SpinWheelObserver implements Observer { 

GameEngine gameEngine; 
ArrayList<SimplePlayer> players; 
WheelCallbackImpl wheelCall; 

int n; 

public SpinWheelObserver(GameEngine engine, WheelCallbackImpl wheel, ArrayList<SimplePlayer> playerList) { 
    players = playerList; 
    gameEngine = engine; 
    wheelCall = wheel; 
} 

public void update(Observable sender, Object arg) { 

    // check if any players are present 
    if (players.size() == 0) { 
     System.out.println("Empty player array!"); 
     return; 
    } 

    do { 

     gameEngine.spin(40, 1, 300, 30, wheelCall); 

     n = wheelCall.playback(); 

    } while (n== 0); 


} 

} 

这里说明的要点是我gameEngine.spin()方法,这是这样的:

public class GameEngineImpl implements GameEngine { 

private List<Player> playerList = new ArrayList<Player>(); 

// method handles the slowing down of the roulette wheel, printing numbers at an incremental delay 
public void delay(int millis) { 
    try { 
     Thread.sleep(millis); 
    } catch (InterruptedException e) { 
     System.out.println("Sleep method failed."); 
    } 
} 

public void spin(int wheelSize, int initialDelay, int finalDelay, 
    int delayIncrement, WheelCallback callback) { 

    Random rand = new Random(); 
    int curNo = rand.nextInt(wheelSize) + 1; 
    int finalNo = 0; 

    assert (curNo >= 1); 

    // while loop handles how long the wheel will spin for 
    while (initialDelay <= finalDelay) { 
     delay(initialDelay); 
     initialDelay += delayIncrement; 

     // handles rotating nature of the wheel, ensures that if it reaches wheel size, reverts to 1 
     if (curNo > wheelSize) { 
      curNo = 1; 
      callback.nextNumber(curNo, this); 
      curNo++; 
     } 

     assert (curNo <= wheelSize); 
     callback.nextNumber(curNo, this); 
     curNo++; 

     finalNo = curNo - 1; 
    } 

    calculateResult(finalNo); 

    callback.result(finalNo, this); 

} 

方法callback.nextNumber(库尔诺,此):

public void nextNumber(int nextNumber, GameEngine engine) { 

    String strNo = Integer.toString(nextNumber); 

    assert (nextNumber >= 1); 

    System.out.println(nextNumber); 

    wcWheel.setCounter(strNo); 
} 

凡,wcWheel是我的我的观点,这包含方法setCounter()单一实例:

public void setCounter(String value) { 
    label.setText(value); 
} 

对不起,我的解释是如何错综复杂的,但基本上它归结为是setCounter()肯定是被称为,但似乎只在最终号码上调用setText()方法。所以我留下的是一个空的标签,在整个轮盘完成旋转之前不会显示数字。

我确定setCounter()在事件派发线程上运行,我怀疑这是一个并发问题,但我不知道如何更正它。

我试图包含所有相关的代码,但如果我错过了任何东西,请提及它,我也会发布它。

我在我的智慧结束在这里,所以如果任何人都会有足够的帮助,那将是如此之大。

谢谢!

+0

所有这些代码是作为Spin“按钮处理函数的一部分在AWT线程上执行的吗?还是你在单独的线程上运行? – stridecolossus

+0

另外,你说你确定'setCounter'正在被调用 - 你有调试它以查看它被调用的时间和频率,以及/或者在被调用时抛出一个'println'来查看行为? – stridecolossus

+0

我确定它被调用,因为我曾经有一个println语句可以打印'值',这是在终端相应更新,它只是''setText()'方法正在播放 – user2893128

回答

4

您的while沿着Thread.sleep()循环会阻止并重新绘制或更改UI,直到循环结束。

取而代之,您需要执行javax.swing.Timer来计算延迟时间,并为计时器数量保留计数器以停止计时。你可以看到更多的How to Use Swing Timers

的基本结构是

Timer (int delayInMillis, ActionListener listener) 

其中delayInMillisActionEvent的点火之间的毫秒的延迟。 listener收听此事件。所以每次事件被触发时,监听器的actionPerfomed被调用。所以你可能会这样做:

Timer timer = new Timer(delay, new ActionListener()(
    @Override 
    public void actionPerformed(ActionEvent e) { 
     if (count == 0) { 
      ((Timer)e.getSource()).stop(); 
     } else { 
      //make a change to your label 
      count--; 
     } 
    } 
)); 

你可以拨打timer.start()启动定时器。每delay毫秒,标签将更改为您需要的值,直到某些任意计数达到0,然后计时器停止。然后,您可以将计数变量设置为任何需要的值,如果您想随机选择,则取决于车轮的旋转难度:D

+0

那么我会在while循环中用'timer'替换'delay'吗?或者完全摆脱while循环? – user2893128

+0

我不知道你的程序里面和外面,但我会说它看起来很安全,你在'spin'方法中声明了这个定时器。你不需要while循环等等。你不需要'delay()'方法。 –

+0

无论你想要发生每一个“延迟”毫秒,把它放在'else' –

1

在我看来,您的整个游戏必须在动作处理程序中阻止直到while循环完成?因此,标签的文本将得到更新,但只有最后一次更新将在AWT线程再次运行时可见。

2

我想你没有发布所有需要知道问题的相关代码。

但最有可能的问题是由于您在EDT(Event Dispatching Thread)中运行循环和JLabel.setText()

请注意,更新UI组件(例如JLabel的文本)也会在EDT中发生,因此,当您的循环在EDT中运行时,仅在循环结束后您才能更新文本,并且您从您的事件监听器。然后,由于您修改了JLabel的文本,它将被刷新/重新绘制,您将看到您设置的最后一个值。

举例说明这一点。在下面的示例中的事件监听器上的环从0循环到9,设置标签的文本,但你只能看到最后的9集:

JPanel p = new JPanel(); 
final JLabel l = new JLabel("-1"); 
p.add(l); 
JButton b = new JButton("Loop"); 
p.add(b); 

b.addActionListener(new ActionListener() { 
    @Override 
    public void actionPerformed(ActionEvent e) { 
     for (int i = 0; i < 10; i++) { 
      l.setText("" + i); 
      try { Thread.sleep(200); } catch (InterruptedException e1) {} 
     } 
    } 
}); 

提议的解决方案:使用javax.swing.Timer做循环的工作。 Swing的计时器调用它在EDT听众所以它是安全的。它更新Swing组件,一旦听者的回报,组件UI更新可以立即发生:

JPanel p = new JPanel(); 
final JLabel l = new JLabel("-1"); 
p.add(l); 
JButton b = new JButton("Loop"); 
p.add(b); 

b.addActionListener(new ActionListener() { 
    @Override 
    public void actionPerformed(ActionEvent e) { 
     new Timer(200, new ActionListener() { 
      int i = 0; 
      @Override 
      public void actionPerformed(ActionEvent e2) { 
       l.setText("" + i); 
       if (++i == 10) 
        ((Timer)e2.getSource()).stop(); 
      } 
     }).start(); 
    } 
}); 

在这个解决方案,你会看到标签的文本计数从0至9很好。

相关问题